Skip to content

Remove dependency on DispatchWallTime (fix #384) #540

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 13 commits into from
Aug 5, 2025
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ import struct Foundation.Date
#endif

extension LambdaContext {
/// Returns the deadline as a Date for the Lambda function execution.
/// I'm not sure how usefull it is to have this as a Date, with only seconds precision,
/// but I leave it here for compatibility with the FoundationJSONSupport trait.
var deadlineDate: Date {
let secondsSinceEpoch = Double(Int64(bitPattern: self.deadline.rawValue)) / -1_000_000_000
return Date(timeIntervalSince1970: secondsSinceEpoch)
Date(timeIntervalSince1970: Double(self.deadline.milliseconds()) / 1000)
}
}
#endif // trait: FoundationJSONSupport
2 changes: 1 addition & 1 deletion Sources/AWSLambdaRuntime/Lambda+LocalServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@ internal struct LambdaHTTPServer {
"arn:aws:lambda:us-east-1:\(Int16.random(in: Int16.min ... Int16.max)):function:custom-runtime"
),
(AmazonHeaders.traceID, "Root=\(AmazonHeaders.generateXRayTraceID());Sampled=1"),
(AmazonHeaders.deadline, "\(DispatchWallTime.distantFuture.millisSinceEpoch)"),
(AmazonHeaders.deadline, "\(Duration.maxLambdaExecutionTime.milliseconds())"),
])

return LocalServerResponse(
Expand Down
4 changes: 1 addition & 3 deletions Sources/AWSLambdaRuntime/Lambda.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@ public enum Lambda {
requestID: invocation.metadata.requestID,
traceID: invocation.metadata.traceID,
invokedFunctionARN: invocation.metadata.invokedFunctionARN,
deadline: DispatchWallTime(
millisSinceEpoch: invocation.metadata.deadlineInMillisSinceEpoch
),
deadline: Duration(millisSinceEpoch: invocation.metadata.deadlineInMillisSinceEpoch),
logger: logger
)
)
Expand Down
20 changes: 10 additions & 10 deletions Sources/AWSLambdaRuntime/LambdaContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public struct LambdaContext: CustomDebugStringConvertible, Sendable {
let requestID: String
let traceID: String
let invokedFunctionARN: String
let deadline: DispatchWallTime
let deadline: Duration
Copy link
Member

@adam-fowler adam-fowler Aug 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to remove the Dispatch import at the top of the file

let cognitoIdentity: String?
let clientContext: String?
let logger: Logger
Expand All @@ -34,7 +34,7 @@ public struct LambdaContext: CustomDebugStringConvertible, Sendable {
requestID: String,
traceID: String,
invokedFunctionARN: String,
deadline: DispatchWallTime,
deadline: Duration,
cognitoIdentity: String?,
clientContext: String?,
logger: Logger
Expand Down Expand Up @@ -67,7 +67,7 @@ public struct LambdaContext: CustomDebugStringConvertible, Sendable {
}

/// The timestamp that the function times out.
public var deadline: DispatchWallTime {
public var deadline: Duration {
self.storage.deadline
}

Expand All @@ -92,7 +92,7 @@ public struct LambdaContext: CustomDebugStringConvertible, Sendable {
requestID: String,
traceID: String,
invokedFunctionARN: String,
deadline: DispatchWallTime,
deadline: Duration,
cognitoIdentity: String? = nil,
clientContext: String? = nil,
logger: Logger
Expand All @@ -109,30 +109,30 @@ public struct LambdaContext: CustomDebugStringConvertible, Sendable {
}

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

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

public var debugDescription: String {
"\(Self.self)(requestID: \(self.requestID), traceID: \(self.traceID), invokedFunctionARN: \(self.invokedFunctionARN), cognitoIdentity: \(self.cognitoIdentity ?? "nil"), clientContext: \(self.clientContext ?? "nil"), deadline: \(self.deadline))"
}

/// This interface is not part of the public API and must not be used by adopters. This API is not part of semver versioning.
/// The timeout is expressed relative to now
package static func __forTestsOnly(
requestID: String,
traceID: String,
invokedFunctionARN: String,
timeout: DispatchTimeInterval,
timeout: Duration,
logger: Logger
) -> LambdaContext {
LambdaContext(
requestID: requestID,
traceID: traceID,
invokedFunctionARN: invokedFunctionARN,
deadline: .now() + timeout,
deadline: Duration.millisSinceEpoch + timeout,
logger: logger
)
}
Expand Down
45 changes: 36 additions & 9 deletions Sources/AWSLambdaRuntime/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,44 @@ enum AmazonHeaders {
static let invokedFunctionARN = "Lambda-Runtime-Invoked-Function-Arn"
}

extension DispatchWallTime {
/// A simple set of additions to Duration helping to work with Unix epoch that does not require Foundation.
/// The Lambda execution environment uses UTC as a timezone, this struct must not manage timezones.
/// see: TZ in https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html
extension Duration {
/// Returns the time in milliseconds since the Unix epoch.
@usableFromInline
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)))
static var millisSinceEpoch: Duration {
var ts = timespec()
clock_gettime(CLOCK_REALTIME, &ts)
return .milliseconds(Int64(ts.tv_sec) * 1000 + Int64(ts.tv_nsec) / 1_000_000)
}

/// Hardcoded maximum execution time for a Lambda function.
@usableFromInline
static var maxLambdaExecutionTime: Duration {
// 15 minutes in milliseconds
// see https://docs.aws.amazon.com/lambda/latest/dg/configuration-timeout.html
.milliseconds(15 * 60 * 1000)
}

/// Returns the maximum deadline for a Lambda function execution.
/// This is the current time plus the maximum execution time.
/// This function is onwly used by the local server for testing purposes.
@usableFromInline
static var maxLambdaDeadline: Duration {
millisSinceEpoch + maxLambdaExecutionTime
}

/// Returns the Duration in milliseconds
@usableFromInline
func milliseconds() -> Int64 {
Int64(self / .milliseconds(1))
}

var millisSinceEpoch: Int64 {
Int64(bitPattern: self.rawValue) / -1_000_000
/// Create a Duration from milliseconds since Unix Epoch
@usableFromInline
init(millisSinceEpoch: Int64) {
self = .milliseconds(millisSinceEpoch)
}
}

Expand Down Expand Up @@ -103,7 +130,7 @@ extension AmazonHeaders {
// The version number, that is, 1.
let version: UInt = 1
// The time of the original request, in Unix epoch time, in 8 hexadecimal digits.
let now = UInt32(DispatchWallTime.now().millisSinceEpoch / 1000)
let now = UInt32(Duration.millisSinceEpoch.milliseconds() / 1000)
let dateValue = String(now, radix: 16, uppercase: false)
let datePadding = String(repeating: "0", count: max(0, 8 - dateValue.count))
// A 96-bit identifier for the trace, globally unique, in 24 hexadecimal digits.
Expand Down
Loading