Skip to content

Add Deadline #27

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Sources/MockServer/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,12 @@ internal final class HTTPHandler: ChannelInboundHandler {
case .json:
responseBody = "{ \"body\": \"\(requestId)\" }"
}
let deadline = Int64(Date(timeIntervalSinceNow: 60).timeIntervalSince1970 * 1000)
responseHeaders = [
(AmazonHeaders.requestID, requestId),
(AmazonHeaders.invokedFunctionARN, "arn:aws:lambda:us-east-1:123456789012:function:custom-runtime"),
(AmazonHeaders.traceID, "Root=1-5bef4de7-ad49b0e87f6ef6c87fc2e700;Parent=9a9197af755a6419;Sampled=1"),
(AmazonHeaders.deadline, String(Date(timeIntervalSinceNow: 60).timeIntervalSince1970 * 1000)),
(AmazonHeaders.deadline, String(deadline)),
]
} else if request.head.uri.hasSuffix("/response") {
responseStatus = .accepted
Expand Down
27 changes: 23 additions & 4 deletions Sources/SwiftAwsLambda/Lambda.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,20 +78,31 @@ public enum Lambda {
}

public class Context {
// from aws
/// The request ID, which identifies the request that triggered the function invocation.
public let requestId: String

/// The AWS X-Ray tracing header.
public let traceId: String

/// The ARN of the Lambda function, version, or alias that's specified in the invocation.
public let invokedFunctionArn: String
public let deadline: String

/// The timestamp that the function times out
public let deadline: DispatchWallTime

/// For invocations from the AWS Mobile SDK, data about the Amazon Cognito identity provider.
public let cognitoIdentity: String?

/// For invocations from the AWS Mobile SDK, data about the client application and device.
public let clientContext: String?
// utility

/// a logger to log
public let logger: Logger

internal init(requestId: String,
traceId: String,
invokedFunctionArn: String,
deadline: String,
deadline: DispatchWallTime,
cognitoIdentity: String? = nil,
clientContext: String? = nil,
logger: Logger) {
Expand All @@ -107,6 +118,14 @@ public enum Lambda {
logger[metadataKey: "awsTraceId"] = .string(traceId)
self.logger = logger
}

public func getRemainingTime() -> TimeAmount {
let deadline = self.deadline.millisSinceEpoch
let now = DispatchWallTime.now().millisSinceEpoch

let remaining = deadline - now
return .milliseconds(remaining)
}
}

private final class Lifecycle {
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftAwsLambda/LambdaRunner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ private extension Lambda.Context {
self.init(requestId: invocation.requestId,
traceId: invocation.traceId,
invokedFunctionArn: invocation.invokedFunctionArn,
deadline: invocation.deadlineDate,
deadline: DispatchWallTime(millisSinceEpoch: invocation.deadlineInMillisSinceEpoch),
cognitoIdentity: invocation.cognitoIdentity,
clientContext: invocation.clientContext,
logger: logger)
Expand Down
7 changes: 4 additions & 3 deletions Sources/SwiftAwsLambda/LambdaRuntimeClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ private extension HTTPClient.Response {

internal struct Invocation {
let requestId: String
let deadlineDate: String
let deadlineInMillisSinceEpoch: Int64
let invokedFunctionArn: String
let traceId: String
let clientContext: String?
Expand All @@ -192,7 +192,8 @@ internal struct Invocation {
throw LambdaRuntimeClientError.invocationMissingHeader(AmazonHeaders.requestID)
}

guard let unixTimeMilliseconds = headers.first(name: AmazonHeaders.deadline) else {
guard let deadline = headers.first(name: AmazonHeaders.deadline),
let unixTimeInMilliseconds = Int64(deadline) else {
throw LambdaRuntimeClientError.invocationMissingHeader(AmazonHeaders.deadline)
}

Expand All @@ -205,7 +206,7 @@ internal struct Invocation {
}

self.requestId = requestId
self.deadlineDate = unixTimeMilliseconds
self.deadlineInMillisSinceEpoch = unixTimeInMilliseconds
self.invokedFunctionArn = invokedFunctionArn
self.traceId = traceId
self.clientContext = headers["Lambda-Runtime-Client-Context"].first
Expand Down
13 changes: 13 additions & 0 deletions Sources/SwiftAwsLambda/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,16 @@ internal enum Signal: Int32 {
case ALRM = 14
case TERM = 15
}

internal extension DispatchWallTime {
init(millisSinceEpoch: Int64) {
let nanoSinceEpoch = UInt64(millisSinceEpoch) * 1_000_000
let seconds = UInt64(nanoSinceEpoch / 1_000_000_000)
let nanoseconds = nanoSinceEpoch - (seconds * 1_000_000_000)
self.init(timespec: timespec(tv_sec: Int(seconds), tv_nsec: Int(nanoseconds)))
}

var millisSinceEpoch: Int64 {
return Int64(bitPattern: self.rawValue) / -1_000_000
}
}
2 changes: 2 additions & 0 deletions Tests/SwiftAwsLambdaTests/LambdaTest+XCTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ extension LambdaTest {
("testBigPayload", testBigPayload),
("testKeepAliveServer", testKeepAliveServer),
("testNoKeepAliveServer", testNoKeepAliveServer),
("testDeadline", testDeadline),
("testGetRemainingTime", testGetRemainingTime),
]
}
}
53 changes: 53 additions & 0 deletions Tests/SwiftAwsLambdaTests/LambdaTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
//
//===----------------------------------------------------------------------===//

import Logging
import NIO
@testable import SwiftAwsLambda
import XCTest
Expand Down Expand Up @@ -212,6 +213,58 @@ class LambdaTest: XCTestCase {
let result = Lambda.run(handler: EchoHandler(), configuration: configuration)
assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes)
}

func testDeadline() {
let delta = Int.random(in: 1 ... 600)

let milli1 = Date(timeIntervalSinceNow: Double(delta)).millisSinceEpoch
let milli2 = (DispatchWallTime.now() + .seconds(delta)).millisSinceEpoch
XCTAssertEqual(Double(milli1), Double(milli2), accuracy: 2.0)

let now1 = DispatchWallTime.now()
let now2 = DispatchWallTime(millisSinceEpoch: Date().millisSinceEpoch)
XCTAssertEqual(Double(now2.rawValue), Double(now1.rawValue), accuracy: 2_000_000.0)

let future1 = DispatchWallTime.now() + .seconds(delta)
let future2 = DispatchWallTime(millisSinceEpoch: Date(timeIntervalSinceNow: Double(delta)).millisSinceEpoch)
XCTAssertEqual(Double(future1.rawValue), Double(future2.rawValue), accuracy: 2_000_000.0)

let past1 = DispatchWallTime.now() - .seconds(delta)
let past2 = DispatchWallTime(millisSinceEpoch: Date(timeIntervalSinceNow: Double(-delta)).millisSinceEpoch)
XCTAssertEqual(Double(past1.rawValue), Double(past2.rawValue), accuracy: 2_000_000.0)

let logger = Logger(label: "test")
let context = Lambda.Context(requestId: UUID().uuidString,
traceId: UUID().uuidString,
invokedFunctionArn: UUID().uuidString,
deadline: .now() + .seconds(1),
cognitoIdentity: nil,
clientContext: nil,
logger: logger)
XCTAssertGreaterThan(context.deadline, .now())

let expiredContext = Lambda.Context(requestId: UUID().uuidString,
traceId: UUID().uuidString,
invokedFunctionArn: UUID().uuidString,
deadline: .now() - .seconds(1),
cognitoIdentity: nil,
clientContext: nil,
logger: logger)
XCTAssertLessThan(expiredContext.deadline, .now())
}

func testGetRemainingTime() {
let logger = Logger(label: "test")
let context = Lambda.Context(requestId: UUID().uuidString,
traceId: UUID().uuidString,
invokedFunctionArn: UUID().uuidString,
deadline: .now() + .seconds(1),
cognitoIdentity: nil,
clientContext: nil,
logger: logger)
XCTAssertLessThanOrEqual(context.getRemainingTime(), .seconds(1))
XCTAssertGreaterThan(context.getRemainingTime(), .milliseconds(800))
}
}

private struct Behavior: LambdaServerBehavior {
Expand Down
7 changes: 5 additions & 2 deletions Tests/SwiftAwsLambdaTests/MockLambdaServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,12 @@ internal final class HTTPHandler: ChannelInboundHandler {
}
responseStatus = .ok
responseBody = result
let deadline = Date(timeIntervalSinceNow: 60).millisSinceEpoch
responseHeaders = [
(AmazonHeaders.requestID, requestId),
(AmazonHeaders.invokedFunctionARN, "arn:aws:lambda:us-east-1:123456789012:function:custom-runtime"),
(AmazonHeaders.traceID, "Root=1-5bef4de7-ad49b0e87f6ef6c87fc2e700;Parent=9a9197af755a6419;Sampled=1"),
(AmazonHeaders.deadline, String(Date(timeIntervalSinceNow: 60).timeIntervalSince1970 * 1000)),
(AmazonHeaders.deadline, String(deadline)),
]
case .failure(let error):
responseStatus = .init(statusCode: error.rawValue)
Expand Down Expand Up @@ -181,7 +182,9 @@ internal final class HTTPHandler: ChannelInboundHandler {
func writeResponse(context: ChannelHandlerContext, status: HTTPResponseStatus, headers: [(String, String)]? = nil, body: String? = nil) {
var headers = HTTPHeaders(headers ?? [])
headers.add(name: "Content-Length", value: "\(body?.utf8.count ?? 0)")
headers.add(name: "Connection", value: self.keepAlive ? "keep-alive" : "close")
if !self.keepAlive {
headers.add(name: "Connection", value: "close")
}
let head = HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: status, headers: headers)

context.write(wrapOutboundOut(.head(head))).whenFailure { error in
Expand Down
6 changes: 6 additions & 0 deletions Tests/SwiftAwsLambdaTests/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,9 @@ struct TestError: Error, Equatable, CustomStringConvertible {
self.description = description
}
}

internal extension Date {
var millisSinceEpoch: Int64 {
return Int64(self.timeIntervalSince1970 * 1000)
}
}