Skip to content

Commit c2638ff

Browse files
glbrnttLukasa
authored andcommitted
Close stream channels when the parent channel becomes inactive (#131)
Motivation: Stream channels don't get closed when the parent channel closes. This can lead to some unexpected behaviour for users. Modifications: Call receiveStreamClosed on stream channels in the multiplexer when reciving channelInactive. Added a test to verify the stream is closed. Result: Stream channels will be closed when the parent channel is closed.
1 parent c7c0ca9 commit c2638ff

File tree

3 files changed

+51
-0
lines changed

3 files changed

+51
-0
lines changed

Sources/NIOHTTP2/HTTP2StreamMultiplexer.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ public final class HTTP2StreamMultiplexer: ChannelInboundHandler, ChannelOutboun
9797
context.fireChannelActive()
9898
}
9999

100+
public func channelInactive(context: ChannelHandlerContext) {
101+
for channel in self.streams.values {
102+
channel.receiveStreamClosed(nil)
103+
}
104+
context.fireChannelInactive()
105+
}
106+
100107
public func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
101108
switch event {
102109
case let evt as StreamClosedEvent:

Tests/NIOHTTP2Tests/ConfiguringPipelineTests+XCTest.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ extension ConfiguringPipelineTests {
2929
("testBasicPipelineCommunicates", testBasicPipelineCommunicates),
3030
("testPipelineRespectsPositionRequest", testPipelineRespectsPositionRequest),
3131
("testPreambleGetsWrittenOnce", testPreambleGetsWrittenOnce),
32+
("testClosingParentChannelClosesStreamChannel", testClosingParentChannelClosesStreamChannel),
3233
]
3334
}
3435
}

Tests/NIOHTTP2Tests/ConfiguringPipelineTests.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,47 @@ class ConfiguringPipelineTests: XCTestCase {
138138
// We don't expect anything else at this point.
139139
XCTAssertNil(try self.serverChannel.readOutbound(as: IOData.self))
140140
}
141+
142+
func testClosingParentChannelClosesStreamChannel() throws {
143+
/// A channel handler that succeeds a promise when the channel becomes inactive.
144+
final class InactiveHandler: ChannelInboundHandler {
145+
typealias InboundIn = Any
146+
147+
let inactivePromise: EventLoopPromise<Void>
148+
149+
init(inactivePromise: EventLoopPromise<Void>) {
150+
self.inactivePromise = inactivePromise
151+
}
152+
153+
func channelInactive(context: ChannelHandlerContext) {
154+
inactivePromise.succeed(())
155+
}
156+
}
157+
158+
let clientMultiplexer = try assertNoThrowWithValue(self.clientChannel.configureHTTP2Pipeline(mode: .client).wait())
159+
XCTAssertNoThrow(try self.serverChannel.configureHTTP2Pipeline(mode: .server).wait())
160+
161+
XCTAssertNoThrow(try self.assertDoHandshake(client: self.clientChannel, server: self.serverChannel))
162+
163+
let inactivePromise: EventLoopPromise<Void> = self.clientChannel.eventLoop.makePromise()
164+
let streamChannelPromise: EventLoopPromise<Channel> = self.clientChannel.eventLoop.makePromise()
165+
166+
clientMultiplexer.createStreamChannel(promise: streamChannelPromise) { channel, _ in
167+
return channel.pipeline.addHandler(InactiveHandler(inactivePromise: inactivePromise))
168+
}
169+
170+
(self.clientChannel.eventLoop as! EmbeddedEventLoop).run()
171+
172+
let streamChannel = try assertNoThrowWithValue(try streamChannelPromise.futureResult.wait())
173+
// Close the parent channel, not the stream channel.
174+
XCTAssertNoThrow(try self.clientChannel.close().wait())
175+
176+
XCTAssertNoThrow(try inactivePromise.futureResult.wait())
177+
XCTAssertFalse(streamChannel.isActive)
178+
179+
XCTAssertThrowsError(try self.clientChannel.finish()) { error in
180+
XCTAssertEqual(error as? ChannelError, .alreadyClosed)
181+
}
182+
XCTAssertNoThrow(try self.serverChannel.finish())
183+
}
141184
}

0 commit comments

Comments
 (0)