Hey all,
When consuming a NIOAsyncChannel's inbound AsyncSequence using for await, cancelling the reading task while a read is in flight can leave the channel in a hung state:
- the channel remains active (no close event)
- pending reads are never resumed
- no error is surfaced
- the connection must be force-closed externally
This appears to be a missing cancellation or cleanup path in the async read pipeline.
Minimal reproduction
The example below isolates the behavior by cancelling a task that is iterating the inbound stream:
import NIOCore
import NIOPosix
// import NIOAsyncChannel as appropriate
func reproduce(with asyncChannel: NIOAsyncChannel<ByteBuffer, ByteBuffer>) async throws {
let reader = Task {
do {
for try await _ in asyncChannel.inboundStream {
// simulate partial consumption
break
}
} catch {
print("reader error:", error)
}
}
// allow a read to begin
try await Task.sleep(nanoseconds: 100_000_000)
// cancel while a read may be inflight
reader.cancel()
try await Task.sleep(nanoseconds: 500_000_000)
print("channel isActive =", asyncChannel.channel.isActive)
}
This can be reproduced using a simple client/server pair (for example with ServerBootstrap and ClientBootstrap) and invoking reproduce(with:) on the server-side NIOAsyncChannel.
Michael.