@@ -399,6 +399,7 @@ final class RequestBagTests: XCTestCase {
399
399
XCTAssertEqual ( executor. nextBodyPart ( ) , . body( . byteBuffer( . init( bytes: 0 ... 3 ) ) ) )
400
400
// receive a 301 response immediately.
401
401
bag. receiveResponseHead ( . init( version: . http1_1, status: . movedPermanently) )
402
+ XCTAssertNoThrow ( try XCTUnwrap ( delegate. backpressurePromise) . succeed ( ( ) ) )
402
403
bag. succeedRequest ( . init( ) )
403
404
404
405
// if we now write our second part of the response this should fail the backpressure promise
@@ -407,6 +408,62 @@ final class RequestBagTests: XCTestCase {
407
408
XCTAssertEqual ( delegate. receivedHead? . status, . movedPermanently)
408
409
XCTAssertNoThrow ( try bag. task. futureResult. wait ( ) )
409
410
}
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
+ }
410
467
}
411
468
412
469
class MockRequestExecutor : HTTPRequestExecutor {
0 commit comments