Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't fail closeFuture when an error occurs on closing #487

Merged
merged 4 commits into from
Dec 16, 2024

Conversation

gjcairo
Copy link
Contributor

@gjcairo gjcairo commented Dec 13, 2024

Strictly speaking, a close operation can't really fail (except when the closing mode isn't supported or the channel is already closed). Even if an error is encountered while closing a channel, or if an error caused the channel to be closed, the end result is the same: the channel will be closed.

In this case in particular, when streams are closed from the client (i.e. by sending a RST_STREAM frame) the HTTP2StreamChannel would fail the channel's closeFuture. This isn't appropriate however, as the channel is successfully closed.

This change stops failing the stream channel's closeFuture. Instead, if the close happens uncleanly, the error will be fired down the pipeline and the close method's promise will be failed.

@gjcairo gjcairo requested a review from Lukasa December 13, 2024 21:30
@@ -694,14 +694,14 @@ final class HTTP2StreamChannel: Channel, ChannelCore, @unchecked Sendable {
self.failPendingWrites(error: error)
if let promise = self.pendingClosePromise {
self.pendingClosePromise = nil
promise.fail(error)
promise.succeed()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is what was agreed: IIRC the promise from close(mode:promise:) (i.e. pendingClosePromise) can fail to provide diagnostic information. It's the closeFuture (i.e. part of the Channel API) which mustn't fail.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with what @glbrntt said here. This means we need to change the implementation of executeThenClose to discard the error from close and instead add a call to closeFuture.get()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And update the docs for Channel.closeFuture

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh okay, I misunderstood this. The one thing I found weird when implementing this (and why I decided to not fail this promise either) is that calling channel.close() will return a failed promise (since it creates one and passes it to channel.close(promise: newPromise) under the hood), which I personally find slightly confusing. But if you're okay with this behaviour I can just be clear in the docs about it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's expected. Roughly speaking:

  • channel.close(promise:) (and variants of, i.e. channel.close()) are "close the channel and tell me how it closed" (cleanly/uncleanly)
  • channel.closeFuture is "tell me when the channel is closed"

let errorHandler = ErrorEncounteredHandler()
let streamChannelPromise = self.clientChannel.eventLoop.makePromise(of: Channel.self)
clientMultiplexer.createStreamChannel(promise: streamChannelPromise) { channel in
try? channel.pipeline.addHandler(errorHandler).wait()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This stream channel initializer will be executed on the event-loop; you can't wait() while on an EL. You need to use an alternative API, like the sync operations.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one got marked as resolved but wasn't actually resolved.

FWIW I also don't think it's necessary for all of these tests to check this behaviour (i.e. the stream closed error gets fired down the pipeline); we end up with a much larger diff than necessary and harder to maintain tests. I'd prefer we had a separate test which asserts the specific behaviour.

Copy link
Contributor Author

@gjcairo gjcairo Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one got marked as resolved but wasn't actually resolved.

Ah, sorry I fixed it in a bunch of places but missed a few others.

FWIW I also don't think it's necessary for all of these tests to check this behaviour [..] I'd prefer we had a separate test which asserts the specific behaviour.

We were already performing assertions on the close promise everywhere - you could use the same argument there. I thought that it was more consistent if we are going to assert something on the close promise that we also check the right behaviour is seen for the closeFuture and the errors being thrown downstream. But I can revert those changes if you disagree and add a single test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, I saw the first one and assumed they'd all been missed.

I'm okay not reverting it, the work's been done now. I get the following the existing pattern argument; I just think we should be mindful when we make changes like this to keep tests maintainable. This package is bad enough as it is as most tests have about three different versions because of the different multiplexing APIs...

@Lukasa Lukasa added the 🔨 semver/patch No public API change. label Dec 16, 2024
@Lukasa Lukasa changed the title Don't fail close promise when an error occurs on closing Don't fail closeFuture when an error occurs on closing Dec 16, 2024
@gjcairo gjcairo requested a review from glbrntt December 16, 2024 12:23
@gjcairo gjcairo enabled auto-merge (squash) December 16, 2024 12:50
@gjcairo gjcairo requested a review from FranzBusch December 16, 2024 12:50
let errorHandler = ErrorEncounteredHandler()
let streamChannelPromise = self.clientChannel.eventLoop.makePromise(of: Channel.self)
clientMultiplexer.createStreamChannel(promise: streamChannelPromise) { channel in
try? channel.pipeline.addHandler(errorHandler).wait()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one got marked as resolved but wasn't actually resolved.

FWIW I also don't think it's necessary for all of these tests to check this behaviour (i.e. the stream closed error gets fired down the pipeline); we end up with a much larger diff than necessary and harder to maintain tests. I'd prefer we had a separate test which asserts the specific behaviour.

@gjcairo gjcairo force-pushed the dont-fail-close-promise branch from f2bbfc5 to fb977b5 Compare December 16, 2024 14:01
@gjcairo gjcairo requested a review from glbrntt December 16, 2024 15:05
@gjcairo gjcairo merged commit 170f4ca into apple:main Dec 16, 2024
38 checks passed
gjcairo added a commit to apple/swift-nio that referenced this pull request Dec 17, 2024
…s `executeThenClose` (#3032)

After the changes introduced in
apple/swift-nio-http2#487, we need to make a
small change in the implementation of `NIOAsyncChannel` to wait on the
`closeFuture` instead of on `close`'s promise in the `executeThenClose`
implementation.

### Motivation:

`executeThenClose` shouldn't fail from errors arising from closing the
channel - at this point, the user of the channel cannot really do
anything, and since the channel has been closed, we should not fail
since resources have been cleaned up anyways.

### Modifications:

This PR changes the implementation of `NIOAsyncChannel` to wait on the
`closeFuture` instead of on `close`'s promise in the `executeThenClose`
implementation.
It also updates the docs for `closeFuture` to better explain when it
will be succeeded and why it won't ever be failed.


### Result:

`executeThenClose` won't throw errors upon closing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🔨 semver/patch No public API change.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants