From c70e0856797ef826ff13627790241011f831975f Mon Sep 17 00:00:00 2001 From: Peter Adams <63288215+PeterAdams-A@users.noreply.github.com> Date: Fri, 3 Nov 2023 13:58:47 +0000 Subject: [PATCH] testPlatformConnectErrorIsForwardedOnTimeout port reuse (#716) Motivation: The above test has been seen to fail with port already in use. The test assumes that a port can be bound to twice in a row. It's possible that another process takes the port between the two binds - this can be stopped by binding a second time before stopping the first. Modifications: Allow port reuse and reorder the test to bind a second time before releasing the first. Result: Test should no longer be flaky. --- Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift | 10 ++++++---- Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift | 7 +++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift b/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift index 71eb0c44b..97f0385ea 100644 --- a/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift @@ -376,7 +376,7 @@ class HTTP2ClientTests: XCTestCase { } func testPlatformConnectErrorIsForwardedOnTimeout() { - let bin = HTTPBin(.http2(compress: false)) + let bin = HTTPBin(.http2(compress: false), reusePort: true) let clientGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2) let el1 = clientGroup.next() let el2 = clientGroup.next() @@ -404,20 +404,22 @@ class HTTP2ClientTests: XCTestCase { XCTAssertEqual(.ok, response1?.status) XCTAssertEqual(response1?.version, .http2) let serverPort = bin.port - XCTAssertNoThrow(try bin.shutdown()) - // client is now in HTTP/2 state and the HTTPBin is closed - // start a new server on the old port which closes all connections immediately + let serverGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { XCTAssertNoThrow(try serverGroup.syncShutdownGracefully()) } var maybeServer: Channel? XCTAssertNoThrow(maybeServer = try ServerBootstrap(group: serverGroup) .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) + .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEPORT), value: 1) .childChannelInitializer { channel in channel.close() } .childChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) .bind(host: "127.0.0.1", port: serverPort) .wait()) + // shutting down the old server closes all connections immediately + XCTAssertNoThrow(try bin.shutdown()) + // client is now in HTTP/2 state and the HTTPBin is closed guard let server = maybeServer else { return } defer { XCTAssertNoThrow(try server.close().wait()) } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift index fc0879de3..2ebccdafe 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift @@ -438,6 +438,7 @@ internal final class HTTPBin where _ mode: Mode = .http1_1(ssl: false, compress: false), proxy: Proxy = .none, bindTarget: BindTarget = .localhostIPv4RandomPort, + reusePort: Bool = false, handlerFactory: @escaping (Int) -> (RequestHandler) ) { self.mode = mode @@ -460,6 +461,7 @@ internal final class HTTPBin where self.serverChannel = try! ServerBootstrap(group: self.group) .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) + .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEPORT), value: reusePort ? 1 : 0) .serverChannelInitializer { channel in channel.pipeline.addHandler(self.activeConnCounterHandler) }.childChannelInitializer { channel in @@ -642,9 +644,10 @@ extension HTTPBin where RequestHandler == HTTPBinHandler { convenience init( _ mode: Mode = .http1_1(ssl: false, compress: false), proxy: Proxy = .none, - bindTarget: BindTarget = .localhostIPv4RandomPort + bindTarget: BindTarget = .localhostIPv4RandomPort, + reusePort: Bool = false ) { - self.init(mode, proxy: proxy, bindTarget: bindTarget) { HTTPBinHandler(connectionID: $0) } + self.init(mode, proxy: proxy, bindTarget: bindTarget, reusePort: reusePort) { HTTPBinHandler(connectionID: $0) } } }