@@ -399,6 +399,7 @@ final class RequestBagTests: XCTestCase {
399399 XCTAssertEqual ( executor. nextBodyPart ( ) , . body( . byteBuffer( . init( bytes: 0 ... 3 ) ) ) )
400400 // receive a 301 response immediately.
401401 bag. receiveResponseHead ( . init( version: . http1_1, status: . movedPermanently) )
402+ XCTAssertNoThrow ( try XCTUnwrap ( delegate. backpressurePromise) . succeed ( ( ) ) )
402403 bag. succeedRequest ( . init( ) )
403404
404405 // if we now write our second part of the response this should fail the backpressure promise
@@ -407,6 +408,62 @@ final class RequestBagTests: XCTestCase {
407408 XCTAssertEqual ( delegate. receivedHead? . status, . movedPermanently)
408409 XCTAssertNoThrow ( try bag. task. futureResult. wait ( ) )
409410 }
411+
412+ func testRaceBetweenConnectionCloseAndDemandMoreData( ) {
413+ let embeddedEventLoop = EmbeddedEventLoop ( )
414+ defer { XCTAssertNoThrow ( try embeddedEventLoop. syncShutdownGracefully ( ) ) }
415+ let logger = Logger ( label: " test " )
416+
417+ var maybeRequest : HTTPClient . Request ?
418+ XCTAssertNoThrow ( maybeRequest = try HTTPClient . Request ( url: " https://swift.org " ) )
419+ guard let request = maybeRequest else { return XCTFail ( " Expected to have a request " ) }
420+
421+ let delegate = UploadCountingDelegate ( eventLoop: embeddedEventLoop)
422+ var maybeRequestBag : RequestBag < UploadCountingDelegate > ?
423+ XCTAssertNoThrow ( maybeRequestBag = try RequestBag (
424+ request: request,
425+ eventLoopPreference: . delegate( on: embeddedEventLoop) ,
426+ task: . init( eventLoop: embeddedEventLoop, logger: logger) ,
427+ redirectHandler: nil ,
428+ connectionDeadline: . now( ) + . seconds( 30 ) ,
429+ requestOptions: . forTests( ) ,
430+ delegate: delegate
431+ ) )
432+ guard let bag = maybeRequestBag else { return XCTFail ( " Expected to be able to create a request bag. " ) }
433+
434+ let executor = MockRequestExecutor ( )
435+ bag. willExecuteRequest ( executor)
436+ bag. requestHeadSent ( )
437+ bag. receiveResponseHead ( . init( version: . http1_1, status: . ok) )
438+ XCTAssertFalse ( executor. signalledDemandForResponseBody)
439+ XCTAssertNoThrow ( try XCTUnwrap ( delegate. backpressurePromise) . succeed ( ( ) ) )
440+ XCTAssertTrue ( executor. signalledDemandForResponseBody)
441+ executor. resetDemandSignal ( )
442+
443+ // "foo" is forwarded for consumption. We expect the RequestBag to consume "foo" with the
444+ // delegate and call demandMoreBody afterwards.
445+ XCTAssertEqual ( delegate. hitDidReceiveBodyPart, 0 )
446+ bag. receiveResponseBodyParts ( [ ByteBuffer ( string: " foo " ) ] )
447+ XCTAssertFalse ( executor. signalledDemandForResponseBody)
448+ XCTAssertEqual ( delegate. hitDidReceiveBodyPart, 1 )
449+ XCTAssertNoThrow ( try XCTUnwrap ( delegate. backpressurePromise) . succeed ( ( ) ) )
450+ XCTAssertTrue ( executor. signalledDemandForResponseBody)
451+ executor. resetDemandSignal ( )
452+
453+ bag. receiveResponseBodyParts ( [ ByteBuffer ( string: " bar " ) ] )
454+ XCTAssertEqual ( delegate. hitDidReceiveBodyPart, 2 )
455+
456+ // the remote closes the connection, which leads to more data and a succeed of the request
457+ bag. succeedRequest ( [ ByteBuffer ( string: " baz " ) ] )
458+ XCTAssertEqual ( delegate. hitDidReceiveBodyPart, 2 )
459+
460+ XCTAssertNoThrow ( try XCTUnwrap ( delegate. backpressurePromise) . succeed ( ( ) ) )
461+ XCTAssertEqual ( delegate. hitDidReceiveBodyPart, 3 )
462+
463+ XCTAssertEqual ( delegate. hitDidReceiveResponse, 0 )
464+ XCTAssertNoThrow ( try XCTUnwrap ( delegate. backpressurePromise) . succeed ( ( ) ) )
465+ XCTAssertEqual ( delegate. hitDidReceiveResponse, 1 )
466+ }
410467}
411468
412469class MockRequestExecutor : HTTPRequestExecutor {
0 commit comments