From 5be6a4cab628602175a64b1b70ae56692afc1640 Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Mon, 26 Jan 2026 17:06:04 +0000 Subject: [PATCH 1/4] Update NIO dependency to use PR 3495 branch --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index b77b90b1..1bc851d9 100644 --- a/Package.swift +++ b/Package.swift @@ -323,7 +323,7 @@ let package = Package( .library(name: "NIOCertificateHelpers", targets: ["NIOCertificateHelpers"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-nio.git", from: "2.81.0"), + .package(url: "https://github.com/apple/swift-nio.git", branch: "refs/pull/3495/head"), .package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.27.0"), .package(url: "https://github.com/apple/swift-http-types.git", from: "1.3.0"), .package(url: "https://github.com/apple/swift-http-structured-headers.git", from: "1.2.0"), From 37254a6b48841d723808aec920e2c990af9770ad Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Mon, 26 Jan 2026 17:02:25 +0000 Subject: [PATCH 2/4] Add test that exercises channel creation with closed parent channel --- .../NIOResumableUploadTests.swift | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Tests/NIOResumableUploadTests/NIOResumableUploadTests.swift b/Tests/NIOResumableUploadTests/NIOResumableUploadTests.swift index 8c1df99a..ed766d49 100644 --- a/Tests/NIOResumableUploadTests/NIOResumableUploadTests.swift +++ b/Tests/NIOResumableUploadTests/NIOResumableUploadTests.swift @@ -639,6 +639,35 @@ final class NIOResumableUploadTests: XCTestCase { XCTAssertTrue(try channel3.finish().isClean) XCTAssertTrue(try channel.finish().isClean) } + + func testChannelInitHandlesParentThrowingOnOptionsRead() throws { + let channel = EmbeddedChannel() + let recorder = InboundRecorder() + + // Close the channel before we even get going. + try channel.close().wait() + + // Opt into the throwing behaviour (similar to socket-based channels). + channel.allowOptionsWhenClosed = false + + let context = HTTPResumableUploadContext(origin: "https://example.com") + try channel.pipeline.syncOperations.addHandler( + HTTPResumableUploadHandler(context: context, handlers: [recorder]) + ) + + // Send a request. + let request = HTTPRequest(method: .get, scheme: "https", authority: "example.com", path: "/") + try channel.writeInbound(HTTPRequestPart.head(request)) + try channel.writeInbound(HTTPRequestPart.end(nil)) + + // Check recorder was called, which means the outer channel handler was created and called. + XCTAssertEqual(recorder.receivedFrames.count, 2) + XCTAssertEqual(recorder.receivedFrames[0], HTTPRequestPart.head(request)) + XCTAssertEqual(recorder.receivedFrames[1], HTTPRequestPart.end(nil)) + + // Tolerate the channel being already closed -- we closed it :) + XCTAssertTrue(try channel.finish(acceptAlreadyClosed: true).isClean) + } } extension HTTPField.Name { From f72b45b335d24a408f6fa070148a5b5356d8d2a6 Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Mon, 26 Jan 2026 17:21:24 +0000 Subject: [PATCH 3/4] Gracefully handle failing to read channel option in HTTPResumableUploadChannel --- Sources/NIOResumableUpload/HTTPResumableUploadChannel.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/NIOResumableUpload/HTTPResumableUploadChannel.swift b/Sources/NIOResumableUpload/HTTPResumableUploadChannel.swift index 73a28ed1..434c3418 100644 --- a/Sources/NIOResumableUpload/HTTPResumableUploadChannel.swift +++ b/Sources/NIOResumableUpload/HTTPResumableUploadChannel.swift @@ -77,9 +77,9 @@ final class HTTPResumableUploadChannel: Channel, ChannelCore, @unchecked Sendabl self.allocator = parent.allocator self.closePromise = parent.eventLoop.makePromise() self.eventLoop = parent.eventLoop - // Only support Channels that implement sync options - let autoRead = try! parent.syncOptions!.getOption(ChannelOptions.autoRead) - self.autoRead = NIOLoopBound(autoRead, eventLoop: eventLoop) + // Only support Channels that implement sync options, but catch errors if e.g. parent is closed already. + let autoRead = try? parent.syncOptions!.getOption(ChannelOptions.autoRead) + self.autoRead = NIOLoopBound(autoRead ?? false, eventLoop: eventLoop) self._pipeline = ChannelPipeline(channel: self) channelConfigurator(self) } From 67116837d6cd5ea1fcb685bad7f36f63f6c357e0 Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Wed, 28 Jan 2026 09:07:49 +0000 Subject: [PATCH 4/4] Update NIO dependency to 2.94.0 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 1bc851d9..38b0ade8 100644 --- a/Package.swift +++ b/Package.swift @@ -323,7 +323,7 @@ let package = Package( .library(name: "NIOCertificateHelpers", targets: ["NIOCertificateHelpers"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-nio.git", branch: "refs/pull/3495/head"), + .package(url: "https://github.com/apple/swift-nio.git", from: "2.94.0"), .package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.27.0"), .package(url: "https://github.com/apple/swift-http-types.git", from: "1.3.0"), .package(url: "https://github.com/apple/swift-http-structured-headers.git", from: "1.2.0"),