Skip to content

Commit 3f49f3b

Browse files
committed
Add Deadline
Motivation: - As a developer I want to be able to know how much time I have left to execute my lambda, before it times out Changes: - storing the deadline as Int64 on Context and Invocation - added getRemainingTime() to Context which returns a TimeAmount - in the FoundationCompat library we should offer something based on Date - fixed MockLambdaServer to not return “keep-alive” anymore
1 parent a6af55c commit 3f49f3b

File tree

6 files changed

+49
-11
lines changed

6 files changed

+49
-11
lines changed

Sources/MockServer/main.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,12 @@ internal final class HTTPHandler: ChannelInboundHandler {
108108
case .json:
109109
responseBody = "{ \"body\": \"\(requestId)\" }"
110110
}
111+
let deadline = Int64(Date(timeIntervalSinceNow: 60).timeIntervalSince1970 * 1000)
111112
responseHeaders = [
112113
(AmazonHeaders.requestID, requestId),
113114
(AmazonHeaders.invokedFunctionARN, "arn:aws:lambda:us-east-1:123456789012:function:custom-runtime"),
114115
(AmazonHeaders.traceID, "Root=1-5bef4de7-ad49b0e87f6ef6c87fc2e700;Parent=9a9197af755a6419;Sampled=1"),
115-
(AmazonHeaders.deadline, String(Date(timeIntervalSinceNow: 60).timeIntervalSince1970 * 1000)),
116+
(AmazonHeaders.deadline, String(deadline)),
116117
]
117118
} else if request.head.uri.hasSuffix("/response") {
118119
responseStatus = .accepted

Sources/SwiftAwsLambda/Lambda.swift

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,20 +78,31 @@ public enum Lambda {
7878
}
7979

8080
public class Context {
81-
// from aws
81+
/// The request ID, which identifies the request that triggered the function invocation.
8282
public let requestId: String
83+
84+
/// The AWS X-Ray tracing header.
8385
public let traceId: String
86+
87+
/// The ARN of the Lambda function, version, or alias that's specified in the invocation.
8488
public let invokedFunctionArn: String
85-
public let deadline: String
89+
90+
/// The date that the function times out in Unix time milliseconds
91+
public let deadline: Int64
92+
93+
/// For invocations from the AWS Mobile SDK, data about the Amazon Cognito identity provider.
8694
public let cognitoIdentity: String?
95+
96+
/// For invocations from the AWS Mobile SDK, data about the client application and device.
8797
public let clientContext: String?
88-
// utility
98+
99+
/// a logger to log
89100
public let logger: Logger
90101

91102
internal init(requestId: String,
92103
traceId: String,
93104
invokedFunctionArn: String,
94-
deadline: String,
105+
deadline: Int64,
95106
cognitoIdentity: String? = nil,
96107
clientContext: String? = nil,
97108
logger: Logger) {
@@ -107,6 +118,17 @@ public enum Lambda {
107118
logger[metadataKey: "awsTraceId"] = .string(traceId)
108119
self.logger = logger
109120
}
121+
122+
@available(OSX 10.12, *)
123+
public func getRemainingTime() -> TimeAmount {
124+
func getTimeInMilliSeconds() -> Int64 {
125+
var ts: timespec = timespec(tv_sec: 0, tv_nsec: 0)
126+
clock_gettime(CLOCK_REALTIME, &ts)
127+
return Int64(ts.tv_sec * 1000) + Int64(ts.tv_nsec / 1_000_000)
128+
}
129+
130+
return .milliseconds(self.deadline - getTimeInMilliSeconds())
131+
}
110132
}
111133

112134
private final class Lifecycle {

Sources/SwiftAwsLambda/LambdaRunner.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ private extension Lambda.Context {
130130
self.init(requestId: invocation.requestId,
131131
traceId: invocation.traceId,
132132
invokedFunctionArn: invocation.invokedFunctionArn,
133-
deadline: invocation.deadlineDate,
133+
deadline: invocation.deadline,
134134
cognitoIdentity: invocation.cognitoIdentity,
135135
clientContext: invocation.clientContext,
136136
logger: logger)

Sources/SwiftAwsLambda/LambdaRuntimeClient.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ private extension HTTPClient.Response {
181181

182182
internal struct Invocation {
183183
let requestId: String
184-
let deadlineDate: String
184+
let deadline: Int64
185185
let invokedFunctionArn: String
186186
let traceId: String
187187
let clientContext: String?
@@ -192,7 +192,8 @@ internal struct Invocation {
192192
throw LambdaRuntimeClientError.invocationMissingHeader(AmazonHeaders.requestID)
193193
}
194194

195-
guard let unixTimeMilliseconds = headers.first(name: AmazonHeaders.deadline) else {
195+
guard let deadline = headers.first(name: AmazonHeaders.deadline),
196+
let unixTimeInMilliseconds = Int64(deadline) else {
196197
throw LambdaRuntimeClientError.invocationMissingHeader(AmazonHeaders.deadline)
197198
}
198199

@@ -205,7 +206,7 @@ internal struct Invocation {
205206
}
206207

207208
self.requestId = requestId
208-
self.deadlineDate = unixTimeMilliseconds
209+
self.deadline = unixTimeInMilliseconds
209210
self.invokedFunctionArn = invokedFunctionArn
210211
self.traceId = traceId
211212
self.clientContext = headers["Lambda-Runtime-Client-Context"].first

Tests/SwiftAwsLambdaTests/LambdaTest.swift

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

15+
import Logging
1516
import NIO
1617
@testable import SwiftAwsLambda
1718
import XCTest
@@ -212,6 +213,16 @@ class LambdaTest: XCTestCase {
212213
let result = Lambda.run(handler: EchoHandler(), configuration: configuration)
213214
assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes)
214215
}
216+
217+
@available(OSX 10.12, *)
218+
func testGetTimeRemaining() {
219+
let deadline = round(Date(timeIntervalSinceNow: 60).timeIntervalSince1970 * 1000)
220+
let context = Lambda.Context(requestId: "abc", traceId: "abc", invokedFunctionArn: "abc", deadline: Int64(deadline), logger: Logger(label: ""))
221+
222+
let remaining = context.getRemainingTime().nanoseconds
223+
// maximal allowed time offset 2 milliseconds
224+
XCTAssertLessThanOrEqual(abs(remaining - 60 * 1_000_000_000), 2_000_000)
225+
}
215226
}
216227

217228
private struct Behavior: LambdaServerBehavior {

Tests/SwiftAwsLambdaTests/MockLambdaServer.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,12 @@ internal final class HTTPHandler: ChannelInboundHandler {
140140
}
141141
responseStatus = .ok
142142
responseBody = result
143+
let deadline = Int64(Date(timeIntervalSinceNow: 60).timeIntervalSince1970 * 1000)
143144
responseHeaders = [
144145
(AmazonHeaders.requestID, requestId),
145146
(AmazonHeaders.invokedFunctionARN, "arn:aws:lambda:us-east-1:123456789012:function:custom-runtime"),
146147
(AmazonHeaders.traceID, "Root=1-5bef4de7-ad49b0e87f6ef6c87fc2e700;Parent=9a9197af755a6419;Sampled=1"),
147-
(AmazonHeaders.deadline, String(Date(timeIntervalSinceNow: 60).timeIntervalSince1970 * 1000)),
148+
(AmazonHeaders.deadline, String(deadline)),
148149
]
149150
case .failure(let error):
150151
responseStatus = .init(statusCode: error.rawValue)
@@ -181,7 +182,9 @@ internal final class HTTPHandler: ChannelInboundHandler {
181182
func writeResponse(context: ChannelHandlerContext, status: HTTPResponseStatus, headers: [(String, String)]? = nil, body: String? = nil) {
182183
var headers = HTTPHeaders(headers ?? [])
183184
headers.add(name: "Content-Length", value: "\(body?.utf8.count ?? 0)")
184-
headers.add(name: "Connection", value: self.keepAlive ? "keep-alive" : "close")
185+
if !self.keepAlive {
186+
headers.add(name: "Connection", value: "close")
187+
}
185188
let head = HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: status, headers: headers)
186189

187190
context.write(wrapOutboundOut(.head(head))).whenFailure { error in

0 commit comments

Comments
 (0)