@@ -34,6 +34,27 @@ private func makeDefaultHTTPClient(
3434}
3535
3636final class AsyncAwaitEndToEndTests : XCTestCase {
37+ var clientGroup : EventLoopGroup !
38+ var serverGroup : EventLoopGroup !
39+
40+ override func setUp( ) {
41+ XCTAssertNil ( self . clientGroup)
42+ XCTAssertNil ( self . serverGroup)
43+
44+ self . clientGroup = getDefaultEventLoopGroup ( numberOfThreads: 1 )
45+ self . serverGroup = MultiThreadedEventLoopGroup ( numberOfThreads: 1 )
46+ }
47+
48+ override func tearDown( ) {
49+ XCTAssertNotNil ( self . clientGroup)
50+ XCTAssertNoThrow ( try self . clientGroup. syncShutdownGracefully ( ) )
51+ self . clientGroup = nil
52+
53+ XCTAssertNotNil ( self . serverGroup)
54+ XCTAssertNoThrow ( try self . serverGroup. syncShutdownGracefully ( ) )
55+ self . serverGroup = nil
56+ }
57+
3758 func testSimpleGet( ) {
3859 #if compiler(>=5.5.2) && canImport(_Concurrency)
3960 guard #available( macOS 10 . 15 , iOS 13 . 0 , watchOS 6 . 0 , tvOS 13 . 0 , * ) else { return }
@@ -394,6 +415,65 @@ final class AsyncAwaitEndToEndTests: XCTestCase {
394415 #endif
395416 }
396417
418+ func testConnectTimeout( ) {
419+ #if compiler(>=5.5.2) && canImport(_Concurrency)
420+ guard #available( macOS 10 . 15 , iOS 13 . 0 , watchOS 6 . 0 , tvOS 13 . 0 , * ) else { return }
421+ XCTAsyncTest ( timeout: 60 ) {
422+ #if os(Linux)
423+ // 198.51.100.254 is reserved for documentation only and therefore should not accept any TCP connection
424+ let url = " http://198.51.100.254/get "
425+ #else
426+ // on macOS we can use the TCP backlog behaviour when the queue is full to simulate a non reachable server.
427+ // this makes this test a bit more stable if `198.51.100.254` actually responds to connection attempt.
428+ // The backlog behaviour on Linux can not be used to simulate a non-reachable server.
429+ // Linux sends a `SYN/ACK` back even if the `backlog` queue is full as it has two queues.
430+ // The second queue is not limit by `ChannelOptions.backlog` but by `/proc/sys/net/ipv4/tcp_max_syn_backlog`.
431+
432+ let group = MultiThreadedEventLoopGroup ( numberOfThreads: 1 )
433+ defer {
434+ XCTAssertNoThrow ( try group. syncShutdownGracefully ( ) )
435+ }
436+
437+ let serverChannel = try await ServerBootstrap ( group: self . serverGroup)
438+ . serverChannelOption ( ChannelOptions . backlog, value: 1 )
439+ . serverChannelOption ( ChannelOptions . autoRead, value: false )
440+ . bind ( host: " 127.0.0.1 " , port: 0 )
441+ . get ( )
442+ defer {
443+ XCTAssertNoThrow ( try serverChannel. close ( ) . wait ( ) )
444+ }
445+ let port = serverChannel. localAddress!. port!
446+ let firstClientChannel = try ClientBootstrap ( group: self . serverGroup)
447+ . connect ( host: " 127.0.0.1 " , port: port)
448+ . wait ( )
449+ defer {
450+ XCTAssertNoThrow ( try firstClientChannel. close ( ) . wait ( ) )
451+ }
452+ let url = " http://localhost: \( port) /get "
453+ #endif
454+
455+ let httpClient = HTTPClient ( eventLoopGroupProvider: . shared( self . clientGroup) ,
456+ configuration: . init( timeout: . init( connect: . milliseconds( 100 ) , read: . milliseconds( 150 ) ) ) )
457+
458+ defer {
459+ XCTAssertNoThrow ( try httpClient. syncShutdown ( ) )
460+ }
461+
462+ let request = HTTPClientRequest ( url: url)
463+ let start = NIODeadline . now ( )
464+ await XCTAssertThrowsError ( try await httpClient. execute ( request, deadline: . now( ) + . seconds( 30 ) ) ) {
465+ XCTAssertEqualTypeAndValue ( $0, HTTPClientError . connectTimeout)
466+ let end = NIODeadline . now ( )
467+ let duration = end - start
468+
469+ // We give ourselves 10x slack in order to be confident that even on slow machines this assertion passes.
470+ // It's 30x smaller than our other timeout though.
471+ XCTAssertLessThan ( duration, . seconds( 1 ) )
472+ }
473+ }
474+ #endif
475+ }
476+
397477 func testSelfSignedCertificateIsRejectedWithCorrectErrorIfRequestDeadlineIsExceeded( ) {
398478 #if compiler(>=5.5.2) && canImport(_Concurrency)
399479 guard #available( macOS 10 . 15 , iOS 13 . 0 , watchOS 6 . 0 , tvOS 13 . 0 , * ) else { return }
0 commit comments