Avoid precondition failure in write timeout (#803)

### Motivation:

In some cases we can crash because of a precondition failure when the
write timeout fires and we aren't in the running state. This can happen
for example if the connection is closed whilst the write timer is
active.

### Modifications:

* Remove the precondition and instead take no action if the timeout
fires outside of the running state. Instead we take a new `Action`,
`.noAction` when the timer fires.
* Clear write timeouts upon request completion. When a request completes
we have no use for the idle write timer, we clear the read timer and we
should clear the write one too.

### Result:

Fewer crashes.

The supplied tests fails without these changes and passes with either of them.
This commit is contained in:
Rick Newton-Rogers
2025-01-28 17:50:38 +00:00
committed by GitHub
parent e69318d4cb
commit f38c2fea86
5 changed files with 80 additions and 1 deletions
@@ -240,6 +240,7 @@ final class HTTP2ClientRequestHandler: ChannelDuplexHandler {
self.request!.fail(error)
self.request = nil
self.runTimeoutAction(.clearIdleReadTimeoutTimer, context: context)
self.runTimeoutAction(.clearIdleWriteTimeoutTimer, context: context)
// No matter the error reason, we must always make sure the h2 stream is closed. Only
// once the h2 stream is closed, it is released from the h2 multiplexer. The
// HTTPRequestStateMachine may signal finalAction: .none in the error case (as this is
@@ -252,6 +253,7 @@ final class HTTP2ClientRequestHandler: ChannelDuplexHandler {
self.request!.succeedRequest(finalParts)
self.request = nil
self.runTimeoutAction(.clearIdleReadTimeoutTimer, context: context)
self.runTimeoutAction(.clearIdleWriteTimeoutTimer, context: context)
self.runSuccessfulFinalAction(finalAction, context: context)
case .failSendBodyPart(let error, let writePromise), .failSendStreamFinished(let error, let writePromise):