Skip to content

Commit 1854ece

Browse files
tomerdfabianfett
authored andcommitted
fixup
1 parent 908046f commit 1854ece

File tree

3 files changed

+101
-62
lines changed

3 files changed

+101
-62
lines changed

Sources/AWSLambdaRuntime/Lambda+LocalServer.swift

Lines changed: 98 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15+
#if DEBUG
1516
import Dispatch
1617
import Logging
1718
import NIO
@@ -26,8 +27,6 @@ import NIOHTTP1
2627
// callback(.success("Hello, \(payload)!"))
2728
// }
2829
// }
29-
30-
#if DEBUG
3130
extension Lambda {
3231
/// Execute code in the context of a mock Lambda server.
3332
///
@@ -91,10 +90,10 @@ private enum LocalLambda {
9190
public typealias InboundIn = HTTPServerRequestPart
9291
public typealias OutboundOut = HTTPServerResponsePart
9392

94-
private static let queueLock = Lock()
95-
private static var queue = [String: Pending]()
93+
private var pending = CircularBuffer<(head: HTTPRequestHead, body: ByteBuffer?)>()
9694

97-
private var processing = CircularBuffer<(head: HTTPRequestHead, body: ByteBuffer?)>()
95+
private static var invocations = CircularBuffer<Invocation>()
96+
private static var invocationState = InvocationState.waitingForLambdaRequest
9897

9998
private let logger: Logger
10099
private let invocationEndpoint: String
@@ -109,72 +108,101 @@ private enum LocalLambda {
109108

110109
switch requestPart {
111110
case .head(let head):
112-
self.processing.append((head: head, body: nil))
111+
self.pending.append((head: head, body: nil))
113112
case .body(var buffer):
114-
var request = self.processing.removeFirst()
113+
var request = self.pending.removeFirst()
115114
if request.body == nil {
116115
request.body = buffer
117116
} else {
118117
request.body!.writeBuffer(&buffer)
119118
}
120-
self.processing.prepend(request)
119+
self.pending.prepend(request)
121120
case .end:
122-
let request = self.processing.removeFirst()
121+
let request = self.pending.removeFirst()
123122
self.processRequest(context: context, request: request)
124123
}
125124
}
126125

127126
func processRequest(context: ChannelHandlerContext, request: (head: HTTPRequestHead, body: ByteBuffer?)) {
128-
if request.head.uri.hasSuffix(self.invocationEndpoint) {
129-
if let work = request.body {
130-
let requestId = "\(DispatchTime.now().uptimeNanoseconds)" // FIXME:
131-
let promise = context.eventLoop.makePromise(of: Response.self)
127+
switch (request.head.method, request.head.uri) {
128+
// this endpoint is called by the client invoking the lambda
129+
case (.POST, let url) where url.hasSuffix(self.invocationEndpoint):
130+
guard let work = request.body else {
131+
return self.writeResponse(context: context, response: .init(status: .badRequest))
132+
}
133+
let requestID = "\(DispatchTime.now().uptimeNanoseconds)" // FIXME:
134+
let promise = context.eventLoop.makePromise(of: Response.self)
135+
promise.futureResult.whenComplete { result in
136+
switch result {
137+
case .failure(let error):
138+
self.logger.error("invocation error: \(error)")
139+
self.writeResponse(context: context, response: .init(status: .internalServerError))
140+
case .success(let response):
141+
self.writeResponse(context: context, response: response)
142+
}
143+
}
144+
let invocation = Invocation(requestID: requestID, request: work, responsePromise: promise)
145+
switch Self.invocationState {
146+
case .waitingForInvocation(let promise):
147+
promise.succeed(invocation)
148+
case .waitingForLambdaRequest, .waitingForLambdaResponse:
149+
Self.invocations.append(invocation)
150+
}
151+
// /next endpoint is called by the lambda polling for work
152+
case (.GET, let url) where url.hasSuffix(Consts.requestWorkURLSuffix):
153+
// check if our server is in the correct state
154+
guard case .waitingForLambdaRequest = Self.invocationState else {
155+
self.logger.error("invalid invocation state \(Self.invocationState)")
156+
self.writeResponse(context: context, response: .init(status: .unprocessableEntity))
157+
return
158+
}
159+
160+
// pop the first task from the queue
161+
switch Self.invocations.popFirst() {
162+
case .none:
163+
// if there is nothing in the queue,
164+
// create a promise that we can fullfill when we get a new task
165+
let promise = context.eventLoop.makePromise(of: Invocation.self)
132166
promise.futureResult.whenComplete { result in
133167
switch result {
134-
case .success(let response):
135-
self.writeResponse(context: context, response: response)
136-
case .failure:
168+
case .failure(let error):
169+
self.logger.error("invocation error: \(error)")
137170
self.writeResponse(context: context, response: .init(status: .internalServerError))
171+
case .success(let invocation):
172+
Self.invocationState = .waitingForLambdaResponse(invocation)
173+
self.writeResponse(context: context, response: invocation.makeResponse())
138174
}
139175
}
140-
Self.queueLock.withLock {
141-
Self.queue[requestId] = Pending(requestId: requestId, request: work, responsePromise: promise)
142-
}
176+
Self.invocationState = .waitingForInvocation(promise)
177+
case .some(let invocation):
178+
// if there is a task pending, we can immediatly respond with it.
179+
Self.invocationState = .waitingForLambdaResponse(invocation)
180+
self.writeResponse(context: context, response: invocation.makeResponse())
143181
}
144-
} else if request.head.uri.hasSuffix("/next") {
145-
switch (Self.queueLock.withLock { Self.queue.popFirst() }) {
146-
case .none:
147-
self.writeResponse(context: context, response: .init(status: .noContent))
148-
case .some(let pending):
149-
var response = Response()
150-
response.body = pending.value.request
151-
// required headers
152-
response.headers = [
153-
(AmazonHeaders.requestID, pending.key),
154-
(AmazonHeaders.invokedFunctionARN, "arn:aws:lambda:us-east-1:\(Int16.random(in: Int16.min ... Int16.max)):function:custom-runtime"),
155-
(AmazonHeaders.traceID, "Root=\(Int16.random(in: Int16.min ... Int16.max));Parent=\(Int16.random(in: Int16.min ... Int16.max));Sampled=1"),
156-
(AmazonHeaders.deadline, "\(DispatchWallTime.distantFuture.millisSinceEpoch)"),
157-
]
158-
Self.queueLock.withLock {
159-
Self.queue[pending.key] = pending.value
160-
}
161-
self.writeResponse(context: context, response: response)
162-
}
163-
164-
} else if request.head.uri.hasSuffix("/response") {
182+
// :requestID/response endpoint is called by the lambda posting the response
183+
case (.POST, let url) where url.hasSuffix(Consts.postResponseURLSuffix):
165184
let parts = request.head.uri.split(separator: "/")
166-
guard let requestId = parts.count > 2 ? String(parts[parts.count - 2]) : nil else {
185+
186+
guard let requestID = parts.count > 2 ? String(parts[parts.count - 2]) : nil else {
187+
// the request is malformed, since we were expecting a requestId in the path
167188
return self.writeResponse(context: context, response: .init(status: .badRequest))
168189
}
169-
switch (Self.queueLock.withLock { Self.queue[requestId] }) {
170-
case .none:
171-
self.writeResponse(context: context, response: .init(status: .badRequest))
172-
case .some(let pending):
173-
pending.responsePromise.succeed(.init(status: .ok, body: request.body))
174-
self.writeResponse(context: context, response: .init(status: .accepted))
175-
Self.queueLock.withLock { Self.queue[requestId] = nil }
190+
guard case .waitingForLambdaResponse(let invocation) = Self.invocationState else {
191+
// a response was send, but we did not expect to receive one
192+
self.logger.error("invalid invocation state \(Self.invocationState)")
193+
return self.writeResponse(context: context, response: .init(status: .unprocessableEntity))
194+
}
195+
guard requestID == invocation.requestID else {
196+
// the request's requestId is not matching the one we are expecting
197+
self.logger.error("invalid invocation state request ID \(requestID) does not match expected \(invocation.requestID)")
198+
return self.writeResponse(context: context, response: .init(status: .badRequest))
176199
}
177-
} else {
200+
201+
invocation.responsePromise.succeed(.init(status: .ok, body: request.body))
202+
self.writeResponse(context: context, response: .init(status: .accepted))
203+
Self.invocationState = .waitingForLambdaRequest
204+
// unknown call
205+
default:
178206
self.writeResponse(context: context, response: .init(status: .notFound))
179207
}
180208
}
@@ -207,10 +235,29 @@ private enum LocalLambda {
207235
var body: ByteBuffer?
208236
}
209237

210-
struct Pending {
211-
let requestId: String
238+
struct Invocation {
239+
let requestID: String
212240
let request: ByteBuffer
213241
let responsePromise: EventLoopPromise<Response>
242+
243+
func makeResponse() -> Response {
244+
var response = Response()
245+
response.body = self.request
246+
// required headers
247+
response.headers = [
248+
(AmazonHeaders.requestID, self.requestID),
249+
(AmazonHeaders.invokedFunctionARN, "arn:aws:lambda:us-east-1:\(Int16.random(in: Int16.min ... Int16.max)):function:custom-runtime"),
250+
(AmazonHeaders.traceID, "Root=\(Int16.random(in: Int16.min ... Int16.max));Parent=\(Int16.random(in: Int16.min ... Int16.max));Sampled=1"),
251+
(AmazonHeaders.deadline, "\(DispatchWallTime.distantFuture.millisSinceEpoch)"),
252+
]
253+
return response
254+
}
255+
}
256+
257+
enum InvocationState {
258+
case waitingForInvocation(EventLoopPromise<Invocation>)
259+
case waitingForLambdaRequest
260+
case waitingForLambdaResponse(Invocation)
214261
}
215262
}
216263

Sources/AWSLambdaRuntimeCore/LambdaRunner.swift

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ extension Lambda {
4646
func run(logger: Logger, handler: Handler) -> EventLoopFuture<Void> {
4747
logger.debug("lambda invocation sequence starting")
4848
// 1. request work from lambda runtime engine
49-
return self.runtimeClient.getNextInvocation(logger: logger).peekError { error in
49+
return self.runtimeClient.getNextInvocation(logger: logger).peekError { (error) in
5050
if case RuntimeError.badStatusCode(.noContent) = error {
5151
return
5252
}
@@ -67,13 +67,7 @@ extension Lambda {
6767
self.runtimeClient.reportResults(logger: logger, invocation: invocation, result: result).peekError { error in
6868
logger.error("could not report results to lambda runtime engine: \(error)")
6969
}
70-
}.flatMapErrorThrowing { error in
71-
if case RuntimeError.badStatusCode(.noContent) = error {
72-
return ()
73-
}
74-
throw error
75-
}
76-
.always { result in
70+
}.always { result in
7771
// we are done!
7872
logger.log(level: result.successful ? .debug : .warning, "lambda invocation sequence completed \(result.successful ? "successfully" : "with failure")")
7973
}

Sources/StringSample/main.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ struct Handler: EventLoopLambdaHandler {
2626
}
2727
}
2828

29-
try Lambda.withLocalServer {
30-
Lambda.run(Handler())
31-
}
29+
Lambda.run(Handler())
3230

3331
// MARK: - this can also be expressed as a closure:
3432

0 commit comments

Comments
 (0)