diff --git a/IntegrationTests/tests_01_allocation_counters/Thresholds/5.10.json b/IntegrationTests/tests_01_allocation_counters/Thresholds/5.10.json index 7ab02866..c2c69a41 100644 --- a/IntegrationTests/tests_01_allocation_counters/Thresholds/5.10.json +++ b/IntegrationTests/tests_01_allocation_counters/Thresholds/5.10.json @@ -18,6 +18,6 @@ "get_100000_headers_canonical_form_trimming_whitespace_from_long_string": 300050, "get_100000_headers_canonical_form_trimming_whitespace_from_short_string": 200050, "hpack_decoding": 5050, - "stream_teardown_100_concurrent": 262550, - "stream_teardown_100_concurrent_inline": 261650 + "stream_teardown_100_concurrent": 252400, + "stream_teardown_100_concurrent_inline": 252400 } diff --git a/IntegrationTests/tests_01_allocation_counters/Thresholds/5.9.json b/IntegrationTests/tests_01_allocation_counters/Thresholds/5.9.json index 7ab02866..c2c69a41 100644 --- a/IntegrationTests/tests_01_allocation_counters/Thresholds/5.9.json +++ b/IntegrationTests/tests_01_allocation_counters/Thresholds/5.9.json @@ -18,6 +18,6 @@ "get_100000_headers_canonical_form_trimming_whitespace_from_long_string": 300050, "get_100000_headers_canonical_form_trimming_whitespace_from_short_string": 200050, "hpack_decoding": 5050, - "stream_teardown_100_concurrent": 262550, - "stream_teardown_100_concurrent_inline": 261650 + "stream_teardown_100_concurrent": 252400, + "stream_teardown_100_concurrent_inline": 252400 } diff --git a/IntegrationTests/tests_01_allocation_counters/Thresholds/6.0.json b/IntegrationTests/tests_01_allocation_counters/Thresholds/6.0.json index 7f478562..392bde13 100644 --- a/IntegrationTests/tests_01_allocation_counters/Thresholds/6.0.json +++ b/IntegrationTests/tests_01_allocation_counters/Thresholds/6.0.json @@ -18,6 +18,6 @@ "get_100000_headers_canonical_form_trimming_whitespace_from_long_string": 300050, "get_100000_headers_canonical_form_trimming_whitespace_from_short_string": 200050, "hpack_decoding": 5050, - "stream_teardown_100_concurrent": 262450, - "stream_teardown_100_concurrent_inline": 261750 + "stream_teardown_100_concurrent": 252450, + "stream_teardown_100_concurrent_inline": 251550 } diff --git a/IntegrationTests/tests_01_allocation_counters/Thresholds/nightly-6.0.json b/IntegrationTests/tests_01_allocation_counters/Thresholds/nightly-6.0.json index 7f478562..392bde13 100644 --- a/IntegrationTests/tests_01_allocation_counters/Thresholds/nightly-6.0.json +++ b/IntegrationTests/tests_01_allocation_counters/Thresholds/nightly-6.0.json @@ -18,6 +18,6 @@ "get_100000_headers_canonical_form_trimming_whitespace_from_long_string": 300050, "get_100000_headers_canonical_form_trimming_whitespace_from_short_string": 200050, "hpack_decoding": 5050, - "stream_teardown_100_concurrent": 262450, - "stream_teardown_100_concurrent_inline": 261750 + "stream_teardown_100_concurrent": 252450, + "stream_teardown_100_concurrent_inline": 251550 } diff --git a/IntegrationTests/tests_01_allocation_counters/Thresholds/nightly-main.json b/IntegrationTests/tests_01_allocation_counters/Thresholds/nightly-main.json index 7f478562..392bde13 100644 --- a/IntegrationTests/tests_01_allocation_counters/Thresholds/nightly-main.json +++ b/IntegrationTests/tests_01_allocation_counters/Thresholds/nightly-main.json @@ -18,6 +18,6 @@ "get_100000_headers_canonical_form_trimming_whitespace_from_long_string": 300050, "get_100000_headers_canonical_form_trimming_whitespace_from_short_string": 200050, "hpack_decoding": 5050, - "stream_teardown_100_concurrent": 262450, - "stream_teardown_100_concurrent_inline": 261750 + "stream_teardown_100_concurrent": 252450, + "stream_teardown_100_concurrent_inline": 251550 } diff --git a/Sources/NIOHTTP2/HTTP2StreamChannel.swift b/Sources/NIOHTTP2/HTTP2StreamChannel.swift index 9f22073f..15e07ca3 100644 --- a/Sources/NIOHTTP2/HTTP2StreamChannel.swift +++ b/Sources/NIOHTTP2/HTTP2StreamChannel.swift @@ -676,7 +676,7 @@ final class HTTP2StreamChannel: Channel, ChannelCore, @unchecked Sendable { self.eventLoop.execute { self.removeHandlers(pipeline: self.pipeline) - self.closePromise.succeed(()) + self.closePromise.succeed() if let streamID = self.streamID { self.multiplexer.streamClosed(id: streamID) } else { @@ -701,7 +701,7 @@ final class HTTP2StreamChannel: Channel, ChannelCore, @unchecked Sendable { self.eventLoop.execute { self.removeHandlers(pipeline: self.pipeline) - self.closePromise.fail(error) + self.closePromise.succeed() if let streamID = self.streamID { self.multiplexer.streamClosed(id: streamID) } else { diff --git a/Tests/NIOHTTP2Tests/ConfiguringPipelineInlineMultiplexerTests.swift b/Tests/NIOHTTP2Tests/ConfiguringPipelineInlineMultiplexerTests.swift index e95949a0..e7772ad0 100644 --- a/Tests/NIOHTTP2Tests/ConfiguringPipelineInlineMultiplexerTests.swift +++ b/Tests/NIOHTTP2Tests/ConfiguringPipelineInlineMultiplexerTests.swift @@ -71,7 +71,10 @@ class ConfiguringPipelineInlineMultiplexerTests: XCTestCase { ) ) - clientMultiplexer.createStreamChannel(promise: nil) { channel in + let errorHandler = ErrorEncounteredHandler() + let streamChannelPromise = self.clientChannel.eventLoop.makePromise(of: Channel.self) + clientMultiplexer.createStreamChannel(promise: streamChannelPromise) { channel in + try? channel.pipeline.syncOperations.addHandler(errorHandler) channel.writeAndFlush(reqFrame.payload).whenComplete { _ in channel.close(promise: requestPromise) } return channel.eventLoop.makeSucceededFuture(()) } @@ -82,9 +85,12 @@ class ConfiguringPipelineInlineMultiplexerTests: XCTestCase { self.interactInMemory(self.clientChannel, self.serverChannel) (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() + let streamChannel = try XCTUnwrap(streamChannelPromise.futureResult.wait()) + XCTAssertNoThrow(try streamChannel.closeFuture.wait()) XCTAssertThrowsError(try requestPromise.futureResult.wait()) { error in XCTAssertTrue(error is NIOHTTP2Errors.StreamClosed) } + XCTAssertTrue(errorHandler.encounteredError is NIOHTTP2Errors.StreamClosed) // We should have received a HEADERS and a RST_STREAM frame. // The RST_STREAM frame is from closing an incomplete stream on the client side. @@ -133,7 +139,10 @@ class ConfiguringPipelineInlineMultiplexerTests: XCTestCase { ) ) - clientMultiplexer.createStreamChannel(promise: nil) { channel in + let errorHandler = ErrorEncounteredHandler() + let streamChannelPromise = self.clientChannel.eventLoop.makePromise(of: Channel.self) + clientMultiplexer.createStreamChannel(promise: streamChannelPromise) { channel in + try? channel.pipeline.syncOperations.addHandler(errorHandler) channel.writeAndFlush(reqFrame.payload).whenComplete { _ in channel.close(promise: requestPromise) } return channel.eventLoop.makeSucceededFuture(()) } @@ -144,9 +153,12 @@ class ConfiguringPipelineInlineMultiplexerTests: XCTestCase { self.interactInMemory(self.clientChannel, self.serverChannel) (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() + let streamChannel = try XCTUnwrap(streamChannelPromise.futureResult.wait()) + XCTAssertNoThrow(try streamChannel.closeFuture.wait()) XCTAssertThrowsError(try requestPromise.futureResult.wait()) { error in XCTAssertTrue(error is NIOHTTP2Errors.StreamClosed) } + XCTAssertTrue(errorHandler.encounteredError is NIOHTTP2Errors.StreamClosed) // We should have received a HEADERS and a RST_STREAM frame. // The RST_STREAM frame is from closing an incomplete stream on the client side. @@ -466,7 +478,10 @@ class ConfiguringPipelineInlineMultiplexerTests: XCTestCase { ) ) - clientMultiplexer.createStreamChannel(promise: nil) { channel in + let errorHandler = ErrorEncounteredHandler() + let streamChannelPromise = self.clientChannel.eventLoop.makePromise(of: Channel.self) + clientMultiplexer.createStreamChannel(promise: streamChannelPromise) { channel in + try? channel.pipeline.syncOperations.addHandler(errorHandler) channel.writeAndFlush(reqFrame.payload).whenComplete { _ in channel.close(promise: requestPromise) } return channel.eventLoop.makeSucceededFuture(()) } @@ -476,9 +491,13 @@ class ConfiguringPipelineInlineMultiplexerTests: XCTestCase { (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() self.interactInMemory(self.clientChannel, self.serverChannel) (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() + + let streamChannel = try XCTUnwrap(streamChannelPromise.futureResult.wait()) + XCTAssertNoThrow(try streamChannel.closeFuture.wait()) XCTAssertThrowsError(try requestPromise.futureResult.wait()) { error in XCTAssertTrue(error is NIOHTTP2Errors.StreamClosed) } + XCTAssertTrue(errorHandler.encounteredError is NIOHTTP2Errors.StreamClosed) // Assert that the user-provided handler received the // HTTP1 parts corresponding to the H2 message sent @@ -556,7 +575,10 @@ class ConfiguringPipelineInlineMultiplexerTests: XCTestCase { ) ) - clientMultiplexer.createStreamChannel(promise: nil) { channel in + let errorHandler = ErrorEncounteredHandler() + let streamChannelPromise = self.clientChannel.eventLoop.makePromise(of: Channel.self) + clientMultiplexer.createStreamChannel(promise: streamChannelPromise) { channel in + try? channel.pipeline.syncOperations.addHandler(errorHandler) channel.writeAndFlush(reqFrame.payload).whenComplete { _ in channel.close(promise: requestPromise) } return channel.eventLoop.makeSucceededFuture(()) } @@ -566,9 +588,13 @@ class ConfiguringPipelineInlineMultiplexerTests: XCTestCase { (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() self.interactInMemory(self.clientChannel, self.serverChannel) (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() + + let streamChannel = try XCTUnwrap(streamChannelPromise.futureResult.wait()) + XCTAssertNoThrow(try streamChannel.closeFuture.wait()) XCTAssertThrowsError(try requestPromise.futureResult.wait()) { error in XCTAssertTrue(error is NIOHTTP2Errors.StreamClosed) } + XCTAssertTrue(errorHandler.encounteredError is NIOHTTP2Errors.StreamClosed) // Assert that the user-provided handler received the // HTTP1 parts corresponding to the H2 message sent diff --git a/Tests/NIOHTTP2Tests/ConfiguringPipelineTests.swift b/Tests/NIOHTTP2Tests/ConfiguringPipelineTests.swift index e35cb33a..531d17b9 100644 --- a/Tests/NIOHTTP2Tests/ConfiguringPipelineTests.swift +++ b/Tests/NIOHTTP2Tests/ConfiguringPipelineTests.swift @@ -61,7 +61,10 @@ class ConfiguringPipelineTests: XCTestCase { ) ) - clientHandler.createStreamChannel(promise: nil) { channel, streamID in + let errorHandler = ErrorEncounteredHandler() + let streamChannelPromise = self.clientChannel.eventLoop.makePromise(of: Channel.self) + clientHandler.createStreamChannel(promise: streamChannelPromise) { channel, streamID in + try? channel.pipeline.syncOperations.addHandler(errorHandler) XCTAssertEqual(streamID, HTTP2StreamID(1)) channel.writeAndFlush(reqFrame).whenComplete { _ in channel.close(promise: requestPromise) } return channel.eventLoop.makeSucceededFuture(()) @@ -73,9 +76,12 @@ class ConfiguringPipelineTests: XCTestCase { self.interactInMemory(self.clientChannel, self.serverChannel) (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() + let streamChannel = try XCTUnwrap(streamChannelPromise.futureResult.wait()) + XCTAssertNoThrow(try streamChannel.closeFuture.wait()) XCTAssertThrowsError(try requestPromise.futureResult.wait()) { error in XCTAssertTrue(error is NIOHTTP2Errors.StreamClosed) } + XCTAssertTrue(errorHandler.encounteredError is NIOHTTP2Errors.StreamClosed) // We should have received a HEADERS and a RST_STREAM frame. // The RST_STREAM frame is from closing an incomplete stream on the client side. @@ -116,7 +122,10 @@ class ConfiguringPipelineTests: XCTestCase { ) ) - clientHandler.createStreamChannel(promise: nil) { channel, streamID in + let errorHandler = ErrorEncounteredHandler() + let streamChannelPromise = self.clientChannel.eventLoop.makePromise(of: Channel.self) + clientHandler.createStreamChannel(promise: streamChannelPromise) { channel, streamID in + try? channel.pipeline.syncOperations.addHandler(errorHandler) XCTAssertEqual(streamID, HTTP2StreamID(1)) channel.writeAndFlush(reqFrame).whenComplete { _ in channel.close(promise: requestPromise) } return channel.eventLoop.makeSucceededFuture(()) @@ -128,9 +137,12 @@ class ConfiguringPipelineTests: XCTestCase { self.interactInMemory(self.clientChannel, self.serverChannel) (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() + let streamChannel = try XCTUnwrap(streamChannelPromise.futureResult.wait()) + XCTAssertNoThrow(try streamChannel.closeFuture.wait()) XCTAssertThrowsError(try requestPromise.futureResult.wait()) { error in XCTAssertTrue(error is NIOHTTP2Errors.StreamClosed) } + XCTAssertTrue(errorHandler.encounteredError is NIOHTTP2Errors.StreamClosed) // We should have received a HEADERS and a RST_STREAM frame. // The RST_STREAM frame is from closing an incomplete stream on the client side. @@ -403,7 +415,10 @@ class ConfiguringPipelineTests: XCTestCase { ) ) - clientHandler.createStreamChannel(promise: nil) { channel, streamID in + let errorHandler = ErrorEncounteredHandler() + let streamChannelPromise = self.clientChannel.eventLoop.makePromise(of: Channel.self) + clientHandler.createStreamChannel(promise: streamChannelPromise) { channel, streamID in + try? channel.pipeline.syncOperations.addHandler(errorHandler) XCTAssertEqual(streamID, HTTP2StreamID(1)) channel.writeAndFlush(reqFrame).whenComplete { _ in channel.close(promise: requestPromise) } return channel.eventLoop.makeSucceededFuture(()) @@ -414,9 +429,13 @@ class ConfiguringPipelineTests: XCTestCase { (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() self.interactInMemory(self.clientChannel, self.serverChannel) (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() + + let streamChannel = try XCTUnwrap(streamChannelPromise.futureResult.wait()) + XCTAssertNoThrow(try streamChannel.closeFuture.wait()) XCTAssertThrowsError(try requestPromise.futureResult.wait()) { error in XCTAssertTrue(error is NIOHTTP2Errors.StreamClosed) } + XCTAssertTrue(errorHandler.encounteredError is NIOHTTP2Errors.StreamClosed) let serverChildChannel = try serverChildChannelPromise.futureResult.wait() try serverChildChannel.pipeline.handler(type: HTTP1ServerRequestRecorderHandler.self).map { serverRecorder in @@ -492,7 +511,10 @@ class ConfiguringPipelineTests: XCTestCase { ) ) - clientHandler.createStreamChannel(promise: nil) { channel, streamID in + let errorHandler = ErrorEncounteredHandler() + let streamChannelPromise = self.clientChannel.eventLoop.makePromise(of: Channel.self) + clientHandler.createStreamChannel(promise: streamChannelPromise) { channel, streamID in + try? channel.pipeline.syncOperations.addHandler(errorHandler) XCTAssertEqual(streamID, HTTP2StreamID(1)) channel.writeAndFlush(reqFrame).whenComplete { _ in channel.close(promise: requestPromise) } return channel.eventLoop.makeSucceededFuture(()) @@ -503,9 +525,13 @@ class ConfiguringPipelineTests: XCTestCase { (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() self.interactInMemory(self.clientChannel, self.serverChannel) (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() + + let streamChannel = try XCTUnwrap(streamChannelPromise.futureResult.wait()) + XCTAssertNoThrow(try streamChannel.closeFuture.wait()) XCTAssertThrowsError(try requestPromise.futureResult.wait()) { error in XCTAssertTrue(error is NIOHTTP2Errors.StreamClosed) } + XCTAssertTrue(errorHandler.encounteredError is NIOHTTP2Errors.StreamClosed) let serverChildChannel = try serverChildChannelPromise.futureResult.wait() try serverChildChannel.pipeline.handler(type: HTTP1ServerRequestRecorderHandler.self).map { serverRecorder in diff --git a/Tests/NIOHTTP2Tests/ConfiguringPipelineWithFramePayloadStreamsTests.swift b/Tests/NIOHTTP2Tests/ConfiguringPipelineWithFramePayloadStreamsTests.swift index 49670075..5537a365 100644 --- a/Tests/NIOHTTP2Tests/ConfiguringPipelineWithFramePayloadStreamsTests.swift +++ b/Tests/NIOHTTP2Tests/ConfiguringPipelineWithFramePayloadStreamsTests.swift @@ -75,7 +75,10 @@ class ConfiguringPipelineWithFramePayloadStreamsTests: XCTestCase { ) ) - clientHandler.createStreamChannel(promise: nil) { channel in + let errorHandler = ErrorEncounteredHandler() + let streamChannelPromise = self.clientChannel.eventLoop.makePromise(of: Channel.self) + clientHandler.createStreamChannel(promise: streamChannelPromise) { channel in + try? channel.pipeline.syncOperations.addHandler(errorHandler) channel.writeAndFlush(reqFrame.payload).whenComplete { _ in channel.close(promise: requestPromise) } return channel.eventLoop.makeSucceededFuture(()) } @@ -86,9 +89,12 @@ class ConfiguringPipelineWithFramePayloadStreamsTests: XCTestCase { self.interactInMemory(self.clientChannel, self.serverChannel) (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() + let streamChannel = try XCTUnwrap(streamChannelPromise.futureResult.wait()) + XCTAssertNoThrow(try streamChannel.closeFuture.wait()) XCTAssertThrowsError(try requestPromise.futureResult.wait()) { error in XCTAssertTrue(error is NIOHTTP2Errors.StreamClosed) } + XCTAssertTrue(errorHandler.encounteredError is NIOHTTP2Errors.StreamClosed) // We should have received a HEADERS and a RST_STREAM frame. // The RST_STREAM frame is from closing an incomplete stream on the client side. @@ -132,7 +138,10 @@ class ConfiguringPipelineWithFramePayloadStreamsTests: XCTestCase { ) ) - clientHandler.createStreamChannel(promise: nil) { channel in + let errorHandler = ErrorEncounteredHandler() + let streamChannelPromise = self.clientChannel.eventLoop.makePromise(of: Channel.self) + clientHandler.createStreamChannel(promise: streamChannelPromise) { channel in + try? channel.pipeline.syncOperations.addHandler(errorHandler) channel.writeAndFlush(reqFrame.payload).whenComplete { _ in channel.close(promise: requestPromise) } return channel.eventLoop.makeSucceededFuture(()) } @@ -143,9 +152,12 @@ class ConfiguringPipelineWithFramePayloadStreamsTests: XCTestCase { self.interactInMemory(self.clientChannel, self.serverChannel) (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() + let streamChannel = try XCTUnwrap(streamChannelPromise.futureResult.wait()) + XCTAssertNoThrow(try streamChannel.closeFuture.wait()) XCTAssertThrowsError(try requestPromise.futureResult.wait()) { error in XCTAssertTrue(error is NIOHTTP2Errors.StreamClosed) } + XCTAssertTrue(errorHandler.encounteredError is NIOHTTP2Errors.StreamClosed) // We should have received a HEADERS and a RST_STREAM frame. // The RST_STREAM frame is from closing an incomplete stream on the client side. @@ -442,7 +454,10 @@ class ConfiguringPipelineWithFramePayloadStreamsTests: XCTestCase { ) ) - clientHandler.createStreamChannel(promise: nil) { channel in + let errorHandler = ErrorEncounteredHandler() + let streamChannelPromise = self.clientChannel.eventLoop.makePromise(of: Channel.self) + clientHandler.createStreamChannel(promise: streamChannelPromise) { channel in + try? channel.pipeline.syncOperations.addHandler(errorHandler) channel.writeAndFlush(reqFrame.payload).whenComplete { _ in channel.close(promise: requestPromise) } return channel.eventLoop.makeSucceededFuture(()) } @@ -452,9 +467,13 @@ class ConfiguringPipelineWithFramePayloadStreamsTests: XCTestCase { (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() self.interactInMemory(self.clientChannel, self.serverChannel) (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() + + let streamChannel = try XCTUnwrap(streamChannelPromise.futureResult.wait()) + XCTAssertNoThrow(try streamChannel.closeFuture.wait()) XCTAssertThrowsError(try requestPromise.futureResult.wait()) { error in XCTAssertTrue(error is NIOHTTP2Errors.StreamClosed) } + XCTAssertTrue(errorHandler.encounteredError is NIOHTTP2Errors.StreamClosed) // Assert that the user-provided handler received the // HTTP1 parts corresponding to the H2 message sent @@ -528,7 +547,10 @@ class ConfiguringPipelineWithFramePayloadStreamsTests: XCTestCase { ) ) - clientHandler.createStreamChannel(promise: nil) { channel in + let errorHandler = ErrorEncounteredHandler() + let streamChannelPromise = self.clientChannel.eventLoop.makePromise(of: Channel.self) + clientHandler.createStreamChannel(promise: streamChannelPromise) { channel in + try? channel.pipeline.syncOperations.addHandler(errorHandler) channel.writeAndFlush(reqFrame.payload).whenComplete { _ in channel.close(promise: requestPromise) } return channel.eventLoop.makeSucceededFuture(()) } @@ -538,9 +560,13 @@ class ConfiguringPipelineWithFramePayloadStreamsTests: XCTestCase { (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() self.interactInMemory(self.clientChannel, self.serverChannel) (self.clientChannel.eventLoop as! EmbeddedEventLoop).run() + + let streamChannel = try XCTUnwrap(streamChannelPromise.futureResult.wait()) + XCTAssertNoThrow(try streamChannel.closeFuture.wait()) XCTAssertThrowsError(try requestPromise.futureResult.wait()) { error in XCTAssertTrue(error is NIOHTTP2Errors.StreamClosed) } + XCTAssertTrue(errorHandler.encounteredError is NIOHTTP2Errors.StreamClosed) // Assert that the user-provided handler received the // HTTP1 parts corresponding to the H2 message sent diff --git a/Tests/NIOHTTP2Tests/HTTP2FramePayloadStreamMultiplexerTests.swift b/Tests/NIOHTTP2Tests/HTTP2FramePayloadStreamMultiplexerTests.swift index 672b583d..5370cbf3 100644 --- a/Tests/NIOHTTP2Tests/HTTP2FramePayloadStreamMultiplexerTests.swift +++ b/Tests/NIOHTTP2Tests/HTTP2FramePayloadStreamMultiplexerTests.swift @@ -154,7 +154,8 @@ final class HTTP2FramePayloadStreamMultiplexerTests: XCTestCase { } func testChannelsCloseAfterResetStreamFrameFirstThenEvent() throws { - let closeError = NIOLockedValueBox(nil) + let errorEncounteredHandler = ErrorEncounteredHandler() + let streamChannelClosed = NIOLockedValueBox(false) XCTAssertNoThrow(try self.channel.connect(to: SocketAddress(unixDomainSocketPath: "/whatever"), promise: nil)) @@ -164,13 +165,10 @@ final class HTTP2FramePayloadStreamMultiplexerTests: XCTestCase { let rstStreamFrame = HTTP2Frame(streamID: streamID, payload: .rstStream(.cancel)) let multiplexer = HTTP2StreamMultiplexer(mode: .server, channel: self.channel) { channel in - closeError.withLockedValue { closeError in - XCTAssertNil(closeError) - } - channel.closeFuture.whenFailure { error in - closeError.withLockedValue { closeError in - closeError = error - } + try? channel.pipeline.syncOperations.addHandler(errorEncounteredHandler) + XCTAssertNil(errorEncounteredHandler.encounteredError) + channel.closeFuture.whenSuccess { + streamChannelClosed.withLockedValue { $0 = true } } return channel.pipeline.addHandler( FramePayloadExpecter(expectedPayload: [frame.payload, rstStreamFrame.payload]) @@ -181,9 +179,7 @@ final class HTTP2FramePayloadStreamMultiplexerTests: XCTestCase { // Let's open the stream up. XCTAssertNoThrow(try self.channel.writeInbound(frame)) self.activateStream(streamID) - closeError.withLockedValue { closeError in - XCTAssertNil(closeError) - } + XCTAssertNil(errorEncounteredHandler.encounteredError) // Now we can send a RST_STREAM frame. XCTAssertNoThrow(try self.channel.writeInbound(rstStreamFrame)) @@ -193,18 +189,19 @@ final class HTTP2FramePayloadStreamMultiplexerTests: XCTestCase { self.channel.pipeline.fireUserInboundEventTriggered(userEvent) (self.channel.eventLoop as! EmbeddedEventLoop).run() - // At this stage the stream should be closed with the appropriate error code. - closeError.withLockedValue { closeError in - XCTAssertEqual( - closeError as? NIOHTTP2Errors.StreamClosed, - NIOHTTP2Errors.streamClosed(streamID: streamID, errorCode: .cancel) - ) - } + // At this stage the stream should be closed, the appropriate error code should have been + // fired down the pipeline. + streamChannelClosed.withLockedValue { XCTAssertTrue($0) } + XCTAssertEqual( + errorEncounteredHandler.encounteredError as? NIOHTTP2Errors.StreamClosed, + NIOHTTP2Errors.streamClosed(streamID: streamID, errorCode: .cancel) + ) XCTAssertNoThrow(try self.channel.finish()) } func testChannelsCloseAfterGoawayFrameFirstThenEvent() throws { - let closeError = NIOLockedValueBox(nil) + let errorEncounteredHandler = ErrorEncounteredHandler() + let streamChannelClosed = NIOLockedValueBox(false) XCTAssertNoThrow(try self.channel.connect(to: SocketAddress(unixDomainSocketPath: "/whatever"), promise: nil)) @@ -217,13 +214,10 @@ final class HTTP2FramePayloadStreamMultiplexerTests: XCTestCase { ) let multiplexer = HTTP2StreamMultiplexer(mode: .server, channel: self.channel) { channel in - closeError.withLockedValue { closeError in - XCTAssertNil(closeError) - } - channel.closeFuture.whenFailure { error in - closeError.withLockedValue { closeError in - closeError = error - } + try? channel.pipeline.syncOperations.addHandler(errorEncounteredHandler) + XCTAssertNil(errorEncounteredHandler.encounteredError) + channel.closeFuture.whenSuccess { + streamChannelClosed.withLockedValue { $0 = true } } // The channel won't see the goaway frame. return channel.pipeline.addHandler(FramePayloadExpecter(expectedPayload: [frame.payload])) @@ -233,9 +227,7 @@ final class HTTP2FramePayloadStreamMultiplexerTests: XCTestCase { // Let's open the stream up. XCTAssertNoThrow(try self.channel.writeInbound(frame)) self.activateStream(streamID) - closeError.withLockedValue { closeError in - XCTAssertNil(closeError) - } + XCTAssertNil(errorEncounteredHandler.encounteredError) // Now we can send a GOAWAY frame. This will close the stream. XCTAssertNoThrow(try self.channel.writeInbound(goAwayFrame)) @@ -245,13 +237,13 @@ final class HTTP2FramePayloadStreamMultiplexerTests: XCTestCase { self.channel.pipeline.fireUserInboundEventTriggered(userEvent) (self.channel.eventLoop as! EmbeddedEventLoop).run() - // At this stage the stream should be closed with the appropriate manufactured error code. - closeError.withLockedValue { closeError in - XCTAssertEqual( - closeError as? NIOHTTP2Errors.StreamClosed, - NIOHTTP2Errors.streamClosed(streamID: streamID, errorCode: .refusedStream) - ) - } + // At this stage the stream should be closed, the appropriate error code should have been + // fired down the pipeline. + streamChannelClosed.withLockedValue { XCTAssertTrue($0) } + XCTAssertEqual( + errorEncounteredHandler.encounteredError as? NIOHTTP2Errors.StreamClosed, + NIOHTTP2Errors.streamClosed(streamID: streamID, errorCode: .refusedStream) + ) XCTAssertNoThrow(try self.channel.finish()) } @@ -447,11 +439,13 @@ final class HTTP2FramePayloadStreamMultiplexerTests: XCTestCase { XCTAssertNoThrow(try self.channel.finish()) } - func testClosePromiseFailsWithError() throws { + func testClosePromiseSucceedsAndErrorIsFiredDownstream() throws { let frameReceiver = FrameWriteRecorder() + let errorEncounteredHandler = ErrorEncounteredHandler() let channelPromise: EventLoopPromise = self.channel.eventLoop.makePromise() let multiplexer = HTTP2StreamMultiplexer(mode: .server, channel: self.channel) { channel in channelPromise.succeed(channel) + try? channel.pipeline.syncOperations.addHandler(errorEncounteredHandler) return channel.eventLoop.makeSucceededFuture(()) } XCTAssertNoThrow(try self.channel.pipeline.addHandler(frameReceiver).wait()) @@ -469,28 +463,31 @@ final class HTTP2FramePayloadStreamMultiplexerTests: XCTestCase { let childChannel = try channelPromise.futureResult.wait() XCTAssertTrue(childChannel.isActive) - // Now we close it. This triggers a RST_STREAM frame. The channel will not be closed at this time. - let closeError = NIOLockedValueBox(nil) - childChannel.close().whenFailure { error in - closeError.withLockedValue { closeError in - closeError = error + // Now we close it. This triggers a RST_STREAM frame. + // Make sure the closeFuture is not failed (closing still succeeds). + // The promise from calling close() should fail to provide the caller with diagnostics. + childChannel.closeFuture.whenFailure { _ in + XCTFail("The close promise should not be failed.") + } + childChannel.close().whenComplete { result in + switch result { + case .success: + XCTFail("The close promise should have been failed.") + case .failure(let error): + XCTAssertTrue(error is NIOHTTP2Errors.StreamClosed) } } XCTAssertEqual(frameReceiver.flushedWrites.count, 1) frameReceiver.flushedWrites[0].assertRstStreamFrame(streamID: streamID, errorCode: .cancel) - closeError.withLockedValue { closeError in - XCTAssertNil(closeError) - } + XCTAssertNil(errorEncounteredHandler.encounteredError) - // Now send the stream closed event. This will fail the close promise. + // Now send the stream closed event. This will fire the error down the pipeline. let userEvent = StreamClosedEvent(streamID: streamID, reason: .cancel) self.channel.pipeline.fireUserInboundEventTriggered(userEvent) - closeError.withLockedValue { closeError in - XCTAssertEqual( - closeError as? NIOHTTP2Errors.StreamClosed, - NIOHTTP2Errors.streamClosed(streamID: streamID, errorCode: .cancel) - ) - } + XCTAssertEqual( + errorEncounteredHandler.encounteredError as? NIOHTTP2Errors.StreamClosed, + NIOHTTP2Errors.streamClosed(streamID: streamID, errorCode: .cancel) + ) XCTAssertNoThrow(try self.channel.finish()) } diff --git a/Tests/NIOHTTP2Tests/HTTP2InlineStreamMultiplexerTests.swift b/Tests/NIOHTTP2Tests/HTTP2InlineStreamMultiplexerTests.swift index 77eb5edf..ba9f2d6e 100644 --- a/Tests/NIOHTTP2Tests/HTTP2InlineStreamMultiplexerTests.swift +++ b/Tests/NIOHTTP2Tests/HTTP2InlineStreamMultiplexerTests.swift @@ -223,7 +223,8 @@ final class HTTP2InlineStreamMultiplexerTests: XCTestCase { } func testChannelsCloseAfterResetStreamFrameFirstThenEvent() throws { - let closeError = NIOLockedValueBox(nil) + let errorEncounteredHandler = ErrorEncounteredHandler() + let streamChannelClosed = NIOLockedValueBox(false) XCTAssertNoThrow(try self.channel.connect(to: SocketAddress(unixDomainSocketPath: "/whatever"), promise: nil)) @@ -239,13 +240,10 @@ final class HTTP2InlineStreamMultiplexerTests: XCTestCase { mode: .server, eventLoop: self.channel.eventLoop, inboundStreamInitializer: { channel in - closeError.withLockedValue { closeError in - XCTAssertNil(closeError) - } - channel.closeFuture.whenFailure { failureError in - closeError.withLockedValue { closeError in - closeError = failureError - } + try? channel.pipeline.syncOperations.addHandler(errorEncounteredHandler) + XCTAssertNil(errorEncounteredHandler.encounteredError) + channel.closeFuture.whenSuccess { + streamChannelClosed.withLockedValue { $0 = true } } return channel.pipeline.addHandler( FramePayloadExpecter(expectedPayload: [frame.payload, rstStreamFrame.payload]) @@ -257,26 +255,27 @@ final class HTTP2InlineStreamMultiplexerTests: XCTestCase { // Let's open the stream up. XCTAssertNoThrow(try self.channel.writeInbound(frame.encode())) - closeError.withLockedValue { closeError in - XCTAssertNil(closeError) - } + XCTAssertNil(errorEncounteredHandler.encounteredError) // Now we can send a RST_STREAM frame. XCTAssertNoThrow(try self.channel.writeInbound(rstStreamFrame.encode())) (self.channel.eventLoop as! EmbeddedEventLoop).run() - // At this stage the stream should be closed with the appropriate error code. - closeError.withLockedValue { error in - XCTAssertEqual( - error as? NIOHTTP2Errors.StreamClosed, - NIOHTTP2Errors.streamClosed(streamID: streamID, errorCode: .cancel) - ) - } + // At this stage the stream should be closed, the appropriate error code should have been + // fired down the pipeline. + streamChannelClosed.withLockedValue { XCTAssertTrue($0) } + XCTAssertEqual( + errorEncounteredHandler.encounteredError as? NIOHTTP2Errors.StreamClosed, + NIOHTTP2Errors.streamClosed(streamID: streamID, errorCode: .cancel) + ) XCTAssertNoThrow(try self.channel.finish()) } func testChannelsCloseAfterGoawayFrameFirstThenEvent() throws { + let errorEncounteredHandler = ErrorEncounteredHandler() + let streamChannelClosed = NIOLockedValueBox(false) + // First, set up the frames we want to send/receive. let streamID = HTTP2StreamID(1) let frame = HTTP2Frame( @@ -302,7 +301,12 @@ final class HTTP2InlineStreamMultiplexerTests: XCTestCase { // Let's open the stream up. let multiplexer = try http2Handler.multiplexer.wait() let streamFuture = multiplexer.createStreamChannel { channel in - channel.eventLoop.makeSucceededVoidFuture() + try? channel.pipeline.syncOperations.addHandler(errorEncounteredHandler) + XCTAssertNil(errorEncounteredHandler.encounteredError) + channel.closeFuture.whenSuccess { + streamChannelClosed.withLockedValue { $0 = true } + } + return channel.eventLoop.makeSucceededVoidFuture() } (self.channel.eventLoop as! EmbeddedEventLoop).run() @@ -310,19 +314,20 @@ final class HTTP2InlineStreamMultiplexerTests: XCTestCase { let stream = try streamFuture.wait() stream.writeAndFlush(frame.payload, promise: nil) + XCTAssertNil(errorEncounteredHandler.encounteredError) // Now we can send a GOAWAY frame. This will close the stream. XCTAssertNoThrow(try self.channel.writeInbound(goAwayFrame.encode())) (self.channel.eventLoop as! EmbeddedEventLoop).run() - XCTAssertThrowsError(try stream.closeFuture.wait()) { closeError in - XCTAssertEqual( - closeError as? NIOHTTP2Errors.StreamClosed, - NIOHTTP2Errors.streamClosed(streamID: streamID, errorCode: .cancel) - ) - } - // At this stage the stream should be closed with the appropriate manufactured error code. + // At this stage the stream should be closed, the appropriate error code should have been + // fired down the pipeline. + streamChannelClosed.withLockedValue { XCTAssertTrue($0) } + XCTAssertEqual( + errorEncounteredHandler.encounteredError as? NIOHTTP2Errors.StreamClosed, + NIOHTTP2Errors.streamClosed(streamID: streamID, errorCode: .cancel) + ) XCTAssertNoThrow(try self.channel.finish()) } @@ -516,14 +521,16 @@ final class HTTP2InlineStreamMultiplexerTests: XCTestCase { XCTAssertNoThrow(try self.channel.finish()) } - func testClosePromiseFailsWithError() throws { + func testClosePromiseSucceedsAndErrorIsFiredDownstream() throws { let frameReceiver = IODataWriteRecorder() + let errorEncounteredHandler = ErrorEncounteredHandler() let channelPromise: EventLoopPromise = self.channel.eventLoop.makePromise() let http2Handler = NIOHTTP2Handler( mode: .server, eventLoop: self.channel.eventLoop, inboundStreamInitializer: { channel in channelPromise.succeed(channel) + try? channel.pipeline.syncOperations.addHandler(errorEncounteredHandler) return channel.eventLoop.makeSucceededVoidFuture() } ) @@ -541,14 +548,22 @@ final class HTTP2InlineStreamMultiplexerTests: XCTestCase { let childChannel = try channelPromise.futureResult.wait() XCTAssertTrue(childChannel.isActive) - // Now we close it. This triggers a RST_STREAM frame. The channel will not be closed at this time. - let closeError = NIOLockedValueBox(nil) - childChannel.close().whenFailure { error in - closeError.withLockedValue { closeError in - closeError = error + // Now we close it. This triggers a RST_STREAM frame. + // Make sure the closeFuture is not failed (closing still succeeds). + // The promise from calling close() should fail to provide the caller with diagnostics. + childChannel.closeFuture.whenFailure { _ in + XCTFail("The close promise should not be failed.") + } + childChannel.close().whenComplete { result in + switch result { + case .success: + XCTFail("The close promise should have been failed.") + case .failure(let error): + XCTAssertTrue(error is NIOHTTP2Errors.StreamClosed) } } XCTAssertEqual(frameReceiver.flushedWrites.count, 1) + var frameDecoder = HTTP2FrameDecoder( allocator: channel.allocator, expectClientMagic: false, @@ -559,12 +574,10 @@ final class HTTP2InlineStreamMultiplexerTests: XCTestCase { } let (flushedFrame, _) = try frameDecoder.nextFrame()! flushedFrame.assertRstStreamFrame(streamID: streamID, errorCode: .cancel) - closeError.withLockedValue { closeError in - XCTAssertEqual( - closeError as? NIOHTTP2Errors.StreamClosed, - NIOHTTP2Errors.streamClosed(streamID: streamID, errorCode: .cancel) - ) - } + XCTAssertEqual( + errorEncounteredHandler.encounteredError as? NIOHTTP2Errors.StreamClosed, + NIOHTTP2Errors.streamClosed(streamID: streamID, errorCode: .cancel) + ) XCTAssertNoThrow(try self.channel.finish()) } diff --git a/Tests/NIOHTTP2Tests/HTTP2StreamMultiplexerTests.swift b/Tests/NIOHTTP2Tests/HTTP2StreamMultiplexerTests.swift index 929e4e6a..3fecd6fa 100644 --- a/Tests/NIOHTTP2Tests/HTTP2StreamMultiplexerTests.swift +++ b/Tests/NIOHTTP2Tests/HTTP2StreamMultiplexerTests.swift @@ -331,7 +331,8 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { @available(*, deprecated, message: "Deprecated so deprecated functionality can be tested without warnings") func testChannelsCloseAfterResetStreamFrameFirstThenEvent() throws { - let closeError = NIOLockedValueBox(nil) + let errorEncounteredHandler = ErrorEncounteredHandler() + let streamChannelClosed = NIOLockedValueBox(false) XCTAssertNoThrow(try self.channel.connect(to: SocketAddress(unixDomainSocketPath: "/whatever"), promise: nil)) @@ -341,13 +342,10 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { let rstStreamFrame = HTTP2Frame(streamID: streamID, payload: .rstStream(.cancel)) let multiplexer = HTTP2StreamMultiplexer(mode: .server, channel: self.channel) { (channel, _) in - closeError.withLockedValue { closeError in - XCTAssertNil(closeError) - } - channel.closeFuture.whenFailure { error in - closeError.withLockedValue { closeError in - closeError = error - } + try? channel.pipeline.syncOperations.addHandler(errorEncounteredHandler) + XCTAssertNil(errorEncounteredHandler.encounteredError) + channel.closeFuture.whenSuccess { + streamChannelClosed.withLockedValue { $0 = true } } return channel.pipeline.addHandler(FrameExpecter(expectedFrames: [frame, rstStreamFrame])) } @@ -356,9 +354,7 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { // Let's open the stream up. XCTAssertNoThrow(try self.channel.writeInbound(frame)) self.activateStream(streamID) - closeError.withLockedValue { closeError in - XCTAssertNil(closeError) - } + XCTAssertNil(errorEncounteredHandler.encounteredError) // Now we can send a RST_STREAM frame. XCTAssertNoThrow(try self.channel.writeInbound(rstStreamFrame)) @@ -368,19 +364,20 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { self.channel.pipeline.fireUserInboundEventTriggered(userEvent) (self.channel.eventLoop as! EmbeddedEventLoop).run() - // At this stage the stream should be closed with the appropriate error code. - closeError.withLockedValue { closeError in - XCTAssertEqual( - closeError as? NIOHTTP2Errors.StreamClosed, - NIOHTTP2Errors.StreamClosed(streamID: streamID, errorCode: .cancel) - ) - } + // At this stage the stream should be closed, the appropriate error code should have been + // fired down the pipeline. + streamChannelClosed.withLockedValue { XCTAssertTrue($0) } + XCTAssertEqual( + errorEncounteredHandler.encounteredError as? NIOHTTP2Errors.StreamClosed, + NIOHTTP2Errors.streamClosed(streamID: streamID, errorCode: .cancel) + ) XCTAssertNoThrow(try self.channel.finish()) } @available(*, deprecated, message: "Deprecated so deprecated functionality can be tested without warnings") func testChannelsCloseAfterGoawayFrameFirstThenEvent() throws { - let closeError = NIOLockedValueBox(nil) + let errorEncounteredHandler = ErrorEncounteredHandler() + let streamChannelClosed = NIOLockedValueBox(false) XCTAssertNoThrow(try self.channel.connect(to: SocketAddress(unixDomainSocketPath: "/whatever"), promise: nil)) @@ -393,13 +390,10 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { ) let multiplexer = HTTP2StreamMultiplexer(mode: .server, channel: self.channel) { (channel, _) in - closeError.withLockedValue { closeError in - XCTAssertNil(closeError) - } - channel.closeFuture.whenFailure { error in - closeError.withLockedValue { closeError in - closeError = error - } + try? channel.pipeline.syncOperations.addHandler(errorEncounteredHandler) + XCTAssertNil(errorEncounteredHandler.encounteredError) + channel.closeFuture.whenSuccess { + streamChannelClosed.withLockedValue { $0 = true } } // The channel won't see the goaway frame. return channel.pipeline.addHandler(FrameExpecter(expectedFrames: [frame])) @@ -409,9 +403,7 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { // Let's open the stream up. XCTAssertNoThrow(try self.channel.writeInbound(frame)) self.activateStream(streamID) - closeError.withLockedValue { closeError in - XCTAssertNil(closeError) - } + XCTAssertNil(errorEncounteredHandler.encounteredError) // Now we can send a GOAWAY frame. This will close the stream. XCTAssertNoThrow(try self.channel.writeInbound(goAwayFrame)) @@ -421,13 +413,13 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { self.channel.pipeline.fireUserInboundEventTriggered(userEvent) (self.channel.eventLoop as! EmbeddedEventLoop).run() - // At this stage the stream should be closed with the appropriate manufactured error code. - closeError.withLockedValue { closeError in - XCTAssertEqual( - closeError as? NIOHTTP2Errors.StreamClosed, - NIOHTTP2Errors.StreamClosed(streamID: streamID, errorCode: .refusedStream) - ) - } + // At this stage the stream should be closed, the appropriate error code should have been + // fired down the pipeline. + streamChannelClosed.withLockedValue { XCTAssertTrue($0) } + XCTAssertEqual( + errorEncounteredHandler.encounteredError as? NIOHTTP2Errors.StreamClosed, + NIOHTTP2Errors.streamClosed(streamID: streamID, errorCode: .refusedStream) + ) XCTAssertNoThrow(try self.channel.finish()) } @@ -630,11 +622,13 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { } @available(*, deprecated, message: "Deprecated so deprecated functionality can be tested without warnings") - func testClosePromiseFailsWithError() throws { + func testClosePromiseSucceedsAndErrorIsFiredDownstream() throws { let frameReceiver = FrameWriteRecorder() + let errorEncounteredHandler = ErrorEncounteredHandler() let channelPromise: EventLoopPromise = self.channel.eventLoop.makePromise() let multiplexer = HTTP2StreamMultiplexer(mode: .server, channel: self.channel) { (channel, _) in channelPromise.succeed(channel) + try? channel.pipeline.syncOperations.addHandler(errorEncounteredHandler) return channel.eventLoop.makeSucceededFuture(()) } XCTAssertNoThrow(try self.channel.pipeline.addHandler(frameReceiver).wait()) @@ -652,28 +646,31 @@ final class HTTP2StreamMultiplexerTests: XCTestCase { let childChannel = try channelPromise.futureResult.wait() XCTAssertTrue(childChannel.isActive) - // Now we close it. This triggers a RST_STREAM frame. The channel will not be closed at this time. - let closeError = NIOLockedValueBox(nil) - childChannel.close().whenFailure { error in - closeError.withLockedValue { closeError in - closeError = error + // Now we close it. This triggers a RST_STREAM frame. + // Make sure the closeFuture is not failed (closing still succeeds). + // The promise from calling close() should fail to provide the caller with diagnostics. + childChannel.closeFuture.whenFailure { _ in + XCTFail("The close promise should not be failed.") + } + childChannel.close().whenComplete { result in + switch result { + case .success: + XCTFail("The close promise should have been failed.") + case .failure(let error): + XCTAssertTrue(error is NIOHTTP2Errors.StreamClosed) } } XCTAssertEqual(frameReceiver.flushedWrites.count, 1) frameReceiver.flushedWrites[0].assertRstStreamFrame(streamID: streamID, errorCode: .cancel) - closeError.withLockedValue { closeError in - XCTAssertNil(closeError) - } + XCTAssertNil(errorEncounteredHandler.encounteredError) - // Now send the stream closed event. This will fail the close promise. + // Now send the stream closed event. This will fire the error down the pipeline. let userEvent = StreamClosedEvent(streamID: streamID, reason: .cancel) self.channel.pipeline.fireUserInboundEventTriggered(userEvent) - closeError.withLockedValue { closeError in - XCTAssertEqual( - closeError as? NIOHTTP2Errors.StreamClosed, - NIOHTTP2Errors.StreamClosed(streamID: streamID, errorCode: .cancel) - ) - } + XCTAssertEqual( + errorEncounteredHandler.encounteredError as? NIOHTTP2Errors.StreamClosed, + NIOHTTP2Errors.streamClosed(streamID: streamID, errorCode: .cancel) + ) XCTAssertNoThrow(try self.channel.finish()) } diff --git a/Tests/NIOHTTP2Tests/SimpleClientServerInlineStreamMultiplexerTests.swift b/Tests/NIOHTTP2Tests/SimpleClientServerInlineStreamMultiplexerTests.swift index e83baa2f..7800669f 100644 --- a/Tests/NIOHTTP2Tests/SimpleClientServerInlineStreamMultiplexerTests.swift +++ b/Tests/NIOHTTP2Tests/SimpleClientServerInlineStreamMultiplexerTests.swift @@ -377,10 +377,12 @@ class SimpleClientServerInlineStreamMultiplexerTests: XCTestCase { } let clientHandler = InboundFramePayloadRecorder() + let errorEncounteredHandler = ErrorEncounteredHandler() let childChannelPromise = self.clientChannel.eventLoop.makePromise(of: Channel.self) let multiplexer = try self.clientChannel.pipeline.handler(type: NIOHTTP2Handler.self).wait().multiplexer.wait() multiplexer.createStreamChannel(promise: childChannelPromise) { channel in - channel.pipeline.addHandler(clientHandler) + try? channel.pipeline.syncOperations.addHandler(errorEncounteredHandler) + return channel.pipeline.addHandler(clientHandler) } self.clientChannel.embeddedEventLoop.run() let childChannel = try childChannelPromise.futureResult.wait() @@ -419,9 +421,10 @@ class SimpleClientServerInlineStreamMultiplexerTests: XCTestCase { self.clientChannel.assertNoFramesReceived() self.serverChannel.assertNoFramesReceived() - // The stream closes with an error. + // The stream closes successfully and an error is fired down the pipeline. self.clientChannel.embeddedEventLoop.run() - XCTAssertThrowsError(try childChannel.closeFuture.wait()) + XCTAssertNoThrow(try childChannel.closeFuture.wait()) + XCTAssertTrue(errorEncounteredHandler.encounteredError is NIOHTTP2Errors.StreamClosed) XCTAssertNoThrow(try self.clientChannel.finish()) XCTAssertNoThrow(try self.serverChannel.finish()) diff --git a/Tests/NIOHTTP2Tests/TestUtilities.swift b/Tests/NIOHTTP2Tests/TestUtilities.swift index cce10a8f..09563aac 100644 --- a/Tests/NIOHTTP2Tests/TestUtilities.swift +++ b/Tests/NIOHTTP2Tests/TestUtilities.swift @@ -1503,3 +1503,14 @@ internal func assertThrowsError( verify(error) } } + +final class ErrorEncounteredHandler: ChannelInboundHandler { + typealias InboundIn = Never + + var encounteredError: Error? + + func errorCaught(context: ChannelHandlerContext, error: Error) { + self.encounteredError = error + context.fireErrorCaught(error) + } +}