Skip to content

Commit

Permalink
Throw an appropriate error from the writer when the channel closed (#…
Browse files Browse the repository at this point in the history
…2744)

# Motivation
Currently, when the channel closes and a user tries to write something the writer throws an `AlreadyFinished` error. This error can also be thrown when calling `finish` on the writer and then trying to call `write` again. This makes it hard to distinguish if the thrown error was due to the channel being closed or due to a business logic error in handling the writer.

# Modification
This PR finishes the writer with a `ChannelError.ioOnClosedChannel` if the writer gets finished to due a channel inactive or handler removed.

# Result
Users can now distinguish if they did something wrong with the writer or if the channel closed.
  • Loading branch information
FranzBusch authored Jun 24, 2024
1 parent ea6a8db commit 5d7a999
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ internal final class NIOAsyncChannelOutboundWriterHandler<OutboundOut: Sendable>
@inlinable
func handlerRemoved(context: ChannelHandlerContext) {
self.context = nil
self.sink?.finish()
self.sink?.finish(error: ChannelError.ioOnClosedChannel)
self.writer = nil
}

Expand All @@ -150,7 +150,7 @@ internal final class NIOAsyncChannelOutboundWriterHandler<OutboundOut: Sendable>

@inlinable
func channelInactive(context: ChannelHandlerContext) {
self.sink?.finish()
self.sink?.finish(error: ChannelError.ioOnClosedChannel)
context.fireChannelInactive()
}

Expand Down
18 changes: 18 additions & 0 deletions Tests/NIOCoreTests/AsyncChannel/AsyncChannelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,24 @@ final class AsyncChannelTests: XCTestCase {
}
}

func testAsyncChannelThrowsWhenChannelClosed() async throws {
let channel = NIOAsyncTestingChannel()
let wrapped = try await channel.testingEventLoop.executeInContext {
return try NIOAsyncChannel<String, String>(wrappingChannelSynchronously: channel)
}

try await channel.close(mode: .all)

do {
try await wrapped.executeThenClose { _, outbound in
try await outbound.write("Test")
}
XCTFail("Expected an error to be thrown")
} catch {
XCTAssertEqual(error as? ChannelError, ChannelError.ioOnClosedChannel)
}
}

func testFinishingTheWriterClosesTheWriteSideOfTheChannel() async throws {
let channel = NIOAsyncTestingChannel()
let closeRecorder = CloseRecorder()
Expand Down

0 comments on commit 5d7a999

Please sign in to comment.