Skip to content

Commit 86ccbf4

Browse files
committed
improve test with a timeout
1 parent 6b07955 commit 86ccbf4

File tree

3 files changed

+86
-9
lines changed

3 files changed

+86
-9
lines changed

Sources/AWSLambdaRuntime/LambdaRuntimeClient.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ final actor LambdaRuntimeClient: LambdaRuntimeClientProtocol {
162162
@usableFromInline
163163
func nextInvocation() async throws -> (Invocation, Writer) {
164164

165-
try await withTaskCancellationHandler {
165+
try Task.checkCancellation()
166+
167+
return try await withTaskCancellationHandler {
166168
switch self.lambdaState {
167169
case .idle:
168170
self.lambdaState = .waitingForNextInvocation

Tests/AWSLambdaRuntimeTests/LambdaRuntimeClientTests.swift

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -295,22 +295,30 @@ struct LambdaRuntimeClientTests {
295295
logger: self.logger
296296
) { runtimeClient in
297297
do {
298-
let (_, writer) = try await runtimeClient.nextInvocation()
299-
try await writer.writeAndFinish(ByteBuffer(string: "hello"))
300298

301-
// continue to simulate traffic until the server reports it has closed the connection
302-
for _ in 1...1000 {
303-
let (_, writer2) = try await runtimeClient.nextInvocation()
304-
try await writer2.writeAndFinish(ByteBuffer(string: "hello"))
299+
// simulate traffic until the server reports it has closed the connection
300+
// or a timeout, whichever comes first
301+
// result is ignored here, either there is a connection error or a timeout
302+
let _ = try await timeout(deadline: .seconds(1)) {
303+
while true {
304+
let (_, writer) = try await runtimeClient.nextInvocation()
305+
try await writer.writeAndFinish(ByteBuffer(string: "hello"))
306+
}
305307
}
308+
// result is ignored here, we should never reach this line
309+
Issue.record("Connection reset test did not throw an error")
306310

307-
Issue.record("Expected connection error but got successful invocation")
308-
311+
} catch is CancellationError {
312+
print("++CancellationError")
313+
Issue.record("Runtime client did not send connection closed error")
309314
} catch let error as LambdaRuntimeError {
315+
print("++LambdaRuntimeError")
310316
#expect(error.code == .connectionToControlPlaneLost)
311317
} catch let error as ChannelError {
318+
print("++ChannelError")
312319
#expect(error == .ioOnClosedChannel)
313320
} catch let error as IOError {
321+
print("++IOError")
314322
#expect(error.errnoCode == ECONNRESET || error.errnoCode == EPIPE)
315323
} catch {
316324
Issue.record("Unexpected error type: \(error)")
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
// as suggested by https://github.com/vapor/postgres-nio/issues/489#issuecomment-2186509773
16+
func timeout<Success: Sendable>(
17+
deadline: Duration,
18+
_ closure: @escaping @Sendable () async throws -> Success
19+
) async throws -> Success {
20+
21+
let clock = ContinuousClock()
22+
23+
let result = await withTaskGroup(of: TimeoutResult<Success>.self, returning: Result<Success, any Error>.self) {
24+
taskGroup in
25+
taskGroup.addTask {
26+
do {
27+
try await clock.sleep(until: clock.now + deadline, tolerance: nil)
28+
return .deadlineHit
29+
} catch {
30+
return .deadlineCancelled
31+
}
32+
}
33+
34+
taskGroup.addTask {
35+
do {
36+
let success = try await closure()
37+
return .workFinished(.success(success))
38+
} catch let error {
39+
return .workFinished(.failure(error))
40+
}
41+
}
42+
43+
var r: Swift.Result<Success, any Error>?
44+
while let taskResult = await taskGroup.next() {
45+
switch taskResult {
46+
case .deadlineCancelled:
47+
continue // loop
48+
49+
case .deadlineHit:
50+
taskGroup.cancelAll()
51+
52+
case .workFinished(let result):
53+
taskGroup.cancelAll()
54+
r = result
55+
}
56+
}
57+
return r!
58+
}
59+
60+
return try result.get()
61+
}
62+
63+
enum TimeoutResult<Success: Sendable> {
64+
case deadlineHit
65+
case deadlineCancelled
66+
case workFinished(Result<Success, any Error>)
67+
}

0 commit comments

Comments
 (0)