@@ -42,10 +42,10 @@ struct LambdaRuntimeClientTests {
4242 . success( ( self . requestId, self . event) )
4343 }
4444
45- func processResponse( requestId: String , response: String ? ) -> Result < Void , ProcessResponseError > {
45+ func processResponse( requestId: String , response: String ? ) -> Result < String ? , ProcessResponseError > {
4646 #expect( self . requestId == requestId)
4747 #expect( self . event == response)
48- return . success( ( ) )
48+ return . success( nil )
4949 }
5050
5151 func processError( requestId: String , error: ErrorResponse ) -> Result < Void , ProcessErrorError > {
@@ -102,9 +102,9 @@ struct LambdaRuntimeClientTests {
102102 . success( ( self . requestId, self . event) )
103103 }
104104
105- func processResponse( requestId: String , response: String ? ) -> Result < Void , ProcessResponseError > {
105+ func processResponse( requestId: String , response: String ? ) -> Result < String ? , ProcessResponseError > {
106106 #expect( self . requestId == requestId)
107- return . success( ( ) )
107+ return . success( nil )
108108 }
109109
110110 mutating func captureHeaders( _ headers: HTTPHeaders ) {
@@ -197,10 +197,10 @@ struct LambdaRuntimeClientTests {
197197 . success( ( self . requestId, self . event) )
198198 }
199199
200- func processResponse( requestId: String , response: String ? ) -> Result < Void , ProcessResponseError > {
200+ func processResponse( requestId: String , response: String ? ) -> Result < String ? , ProcessResponseError > {
201201 #expect( self . requestId == requestId)
202202 #expect( self . event == response)
203- return . success( ( ) )
203+ return . success( nil )
204204 }
205205
206206 func processError( requestId: String , error: ErrorResponse ) -> Result < Void , ProcessErrorError > {
@@ -238,4 +238,91 @@ struct LambdaRuntimeClientTests {
238238 }
239239 }
240240 }
241+
242+ struct DisconnectAfterSendingResponseBehavior : LambdaServerBehavior {
243+ func getInvocation( ) -> GetInvocationResult {
244+ . success( ( UUID ( ) . uuidString, " hello " ) )
245+ }
246+
247+ func processResponse( requestId: String , response: String ? ) -> Result < String ? , ProcessResponseError > {
248+ // Return "delayed-disconnect" to trigger server closing the connection
249+ // after having accepted the first response
250+ . success( " delayed-disconnect " )
251+ }
252+
253+ func processError( requestId: String , error: ErrorResponse ) -> Result < Void , ProcessErrorError > {
254+ Issue . record ( " should not report error " )
255+ return . failure( . internalServerError)
256+ }
257+
258+ func processInitError( error: ErrorResponse ) -> Result < Void , ProcessErrorError > {
259+ Issue . record ( " should not report init error " )
260+ return . failure( . internalServerError)
261+ }
262+ }
263+
264+ struct DisconnectBehavior : LambdaServerBehavior {
265+ func getInvocation( ) -> GetInvocationResult {
266+ . success( ( " disconnect " , " 0 " ) )
267+ }
268+
269+ func processResponse( requestId: String , response: String ? ) -> Result < String ? , ProcessResponseError > {
270+ . success( nil )
271+ }
272+
273+ func processError( requestId: String , error: ErrorResponse ) -> Result < Void , ProcessErrorError > {
274+ Issue . record ( " should not report error " )
275+ return . failure( . internalServerError)
276+ }
277+
278+ func processInitError( error: ErrorResponse ) -> Result < Void , ProcessErrorError > {
279+ Issue . record ( " should not report init error " )
280+ return . failure( . internalServerError)
281+ }
282+ }
283+
284+ @Test (
285+ " Server closing the connection when waiting for next invocation throws an error " ,
286+ arguments: [ DisconnectBehavior ( ) , DisconnectAfterSendingResponseBehavior ( ) ] as [ any LambdaServerBehavior ]
287+ )
288+ func testChannelCloseFutureWithWaitingForNextInvocation( behavior: LambdaServerBehavior ) async throws {
289+ try await withMockServer ( behaviour: behavior) { port in
290+ let configuration = LambdaRuntimeClient . Configuration ( ip: " 127.0.0.1 " , port: port)
291+
292+ try await LambdaRuntimeClient . withRuntimeClient (
293+ configuration: configuration,
294+ eventLoop: NIOSingletons . posixEventLoopGroup. next ( ) ,
295+ logger: self . logger
296+ ) { runtimeClient in
297+ do {
298+
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+ }
307+ }
308+ // result is ignored here, we should never reach this line
309+ Issue . record ( " Connection reset test did not throw an error " )
310+
311+ } catch is CancellationError {
312+ Issue . record ( " Runtime client did not send connection closed error " )
313+ } catch let error as LambdaRuntimeError {
314+ logger. trace ( " LambdaRuntimeError - expected " )
315+ #expect( error. code == . connectionToControlPlaneLost)
316+ } catch let error as ChannelError {
317+ logger. trace ( " ChannelError - expected " )
318+ #expect( error == . ioOnClosedChannel)
319+ } catch let error as IOError {
320+ logger. trace ( " IOError - expected " )
321+ #expect( error. errnoCode == ECONNRESET || error. errnoCode == EPIPE)
322+ } catch {
323+ Issue . record ( " Unexpected error type: \( error) " )
324+ }
325+ }
326+ }
327+ }
241328}
0 commit comments