@@ -840,6 +840,81 @@ class HTTP1ClientChannelHandlerTests: XCTestCase {
840
840
channel. writeAndFlush ( request, promise: nil )
841
841
XCTAssertEqual ( request. events. map ( \. kind) , [ . willExecuteRequest, . requestHeadSent] )
842
842
}
843
+
844
+ class SlowHandler : ChannelOutboundHandler {
845
+ typealias OutboundIn = HTTPClientRequestPart
846
+ typealias OutboundOut = HTTPClientRequestPart
847
+
848
+ func write( context: ChannelHandlerContext , data: NIOAny , promise: EventLoopPromise < Void > ? ) {
849
+ context. eventLoop. scheduleTask ( in: . milliseconds( 300 ) ) {
850
+ promise? . succeed ( )
851
+ }
852
+ }
853
+ }
854
+
855
+ func testIdleWriteTimeoutOutsideOfRunningState( ) {
856
+ let embedded = EmbeddedChannel ( )
857
+ var maybeTestUtils : HTTP1TestTools ?
858
+ XCTAssertNoThrow ( maybeTestUtils = try embedded. setupHTTP1Connection ( ) )
859
+ print ( " pipeline " , embedded. pipeline)
860
+ guard let testUtils = maybeTestUtils else { return XCTFail ( " Expected connection setup works " ) }
861
+
862
+ var maybeRequest : HTTPClient . Request ?
863
+ XCTAssertNoThrow ( maybeRequest = try HTTPClient . Request ( url: " http://localhost/ " ) )
864
+ guard var request = maybeRequest else { return XCTFail ( " Expected to be able to create a request " ) }
865
+
866
+ // start a request stream we'll never write to
867
+ let streamPromise = embedded. eventLoop. makePromise ( of: Void . self)
868
+ let streamCallback = { @Sendable ( streamWriter: HTTPClient . Body . StreamWriter ) -> EventLoopFuture < Void > in
869
+ streamPromise. futureResult
870
+ }
871
+ request. body = . init( contentLength: nil , stream: streamCallback)
872
+
873
+ let delegate = NullResponseDelegate ( )
874
+ var maybeRequestBag : RequestBag < NullResponseDelegate > ?
875
+ XCTAssertNoThrow (
876
+ maybeRequestBag = try RequestBag (
877
+ request: request,
878
+ eventLoopPreference: . delegate( on: embedded. eventLoop) ,
879
+ task: . init( eventLoop: embedded. eventLoop, logger: testUtils. logger) ,
880
+ redirectHandler: nil ,
881
+ connectionDeadline: . now( ) + . seconds( 30 ) ,
882
+ requestOptions: . forTests(
883
+ idleReadTimeout: . milliseconds( 10 ) ,
884
+ idleWriteTimeout: . milliseconds( 2 )
885
+ ) ,
886
+ delegate: delegate
887
+ )
888
+ )
889
+ guard let requestBag = maybeRequestBag else { return XCTFail ( " Expected to be able to create a request bag " ) }
890
+
891
+ testUtils. connection. executeRequest ( requestBag)
892
+
893
+ XCTAssertNoThrow (
894
+ try embedded. receiveHeadAndVerify {
895
+ XCTAssertEqual ( $0. method, . GET)
896
+ XCTAssertEqual ( $0. uri, " / " )
897
+ XCTAssertEqual ( $0. headers. first ( name: " host " ) , " localhost " )
898
+ }
899
+ )
900
+
901
+ // close the pipeline to simulate a server-side close
902
+ // note this happens before we write so the idle write timeout is still running
903
+ try ! embedded. pipeline. close ( ) . wait ( )
904
+
905
+ // advance time to trigger the idle write timeout
906
+ // and ensure that the state machine can tolerate this
907
+ embedded. embeddedEventLoop. advanceTime ( by: . milliseconds( 250 ) )
908
+ }
909
+ }
910
+
911
+ class NullResponseDelegate : HTTPClientResponseDelegate {
912
+ typealias Response = Void
913
+
914
+ func didFinishRequest( task: AsyncHTTPClient . HTTPClient . Task < Void > ) throws {
915
+ ( )
916
+ }
917
+
843
918
}
844
919
845
920
class TestBackpressureWriter {
0 commit comments