-
Notifications
You must be signed in to change notification settings - Fork 122
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
Avoid precondition failure in write timeout #803
Conversation
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. Result: Fewer crashes.
4d896b0
to
90e8b58
Compare
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.
@@ -281,7 +281,7 @@ final class HTTP1ClientChannelHandler: ChannelDuplexHandler { | |||
case .close: | |||
context.close(promise: nil) | |||
|
|||
case .wait: | |||
case .wait, .noAction: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this new state or can we just re-use .wait
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reuse .wait
. .wait
is a do nothing. can be renamed in a follow up pr.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nits.
@@ -281,7 +281,7 @@ final class HTTP1ClientChannelHandler: ChannelDuplexHandler { | |||
case .close: | |||
context.close(promise: nil) | |||
|
|||
case .wait: | |||
case .wait, .noAction: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reuse .wait
. .wait
is a do nothing. can be renamed in a follow up pr.
@@ -359,7 +361,7 @@ struct HTTP1ConnectionStateMachine { | |||
|
|||
mutating func idleWriteTimeoutTriggered() -> Action { | |||
guard case .inRequest(var requestStateMachine, let close) = self.state else { | |||
preconditionFailure("Invalid state: \(self.state)") | |||
return .noAction |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mind adding another test to HTTP1ConnectionStateMachineTests that just tests this edge?
@@ -83,6 +83,8 @@ struct HTTP1ConnectionStateMachine { | |||
case fireChannelActive | |||
case fireChannelInactive | |||
case fireChannelError(Error, closeConnection: Bool) | |||
|
|||
case noAction |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove.
@@ -840,6 +840,81 @@ class HTTP1ClientChannelHandlerTests: XCTestCase { | |||
channel.writeAndFlush(request, promise: nil) | |||
XCTAssertEqual(request.events.map(\.kind), [.willExecuteRequest, .requestHeadSent]) | |||
} | |||
|
|||
class SlowHandler: ChannelOutboundHandler { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this isn't used at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, a relic from a previous test approach.
request.body = .init(contentLength: nil, stream: streamCallback) | ||
|
||
let delegate = NullResponseDelegate() | ||
var maybeRequestBag: RequestBag<NullResponseDelegate>? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use a ResponseAccumulator
here instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hadn't spotted that, thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
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:
Action
,.noAction
when the timer fires.Result:
Fewer crashes.
The supplied tests fails without these changes and passes with either of them.