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 @@ -21,7 +21,7 @@ import struct Foundation.Date

extension LambdaContext {
var deadlineDate: Date {
let secondsSinceEpoch = Double(Int64(bitPattern: self.deadline.rawValue)) / -1_000_000_000
let secondsSinceEpoch = Double(self.deadline.milliseconds()) / -1_000_000_000
return Date(timeIntervalSince1970: secondsSinceEpoch)
}
}
Expand Down
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.distantFuture.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
37 changes: 28 additions & 9 deletions Sources/AWSLambdaRuntime/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,36 @@ 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.
/// It provides a distant future value and a way to get the current time in milliseconds since the epoch.
/// 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)
}

var millisSinceEpoch: Int64 {
Int64(bitPattern: self.rawValue) / -1_000_000
/// Returns a Duration between Unix epoch and the distant future
@usableFromInline
static var distantFuture: Duration {
// Use a very large value to represent the distant future
millisSinceEpoch + Duration.seconds(.greatestFiniteMagnitude)
}

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

/// Create a Duration from milliseconds since Unix Epoch
@usableFromInline
init(millisSinceEpoch: Int64) {
self = .milliseconds(millisSinceEpoch)
}
}

Expand Down Expand Up @@ -103,7 +122,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