Crash fix: HTTP2 can handle requests are cancelled (#555)

Co-authored-by: George Barnett <gbarnett@apple.com>
This commit is contained in:
Fabian Fett
2022-02-10 10:42:27 +01:00
committed by GitHub
co-authored by George Barnett
parent 5ead318ad2
commit 5844a6b4ee
4 changed files with 52 additions and 9 deletions
@@ -108,16 +108,24 @@ struct HTTPRequestStateMachine {
}
mutating func startRequest(head: HTTPRequestHead, metadata: RequestFramingMetadata) -> Action {
guard case .initialized = self.state else {
preconditionFailure("`start()` must be called first, and exactly once. Invalid state: \(self.state)")
}
switch self.state {
case .initialized:
guard self.isChannelWritable else {
self.state = .waitForChannelToBecomeWritable(head, metadata)
return .wait
}
return self.startSendingRequest(head: head, metadata: metadata)
guard self.isChannelWritable else {
self.state = .waitForChannelToBecomeWritable(head, metadata)
case .failed:
// The request state machine is marked as failed before the request is started, if
// the request was cancelled before hitting the channel handler. Before `startRequest`
// is called on the state machine, `willExecuteRequest` is called on
// `HTTPExecutableRequest`, which might loopback to state machines cancel method.
return .wait
}
return self.startSendingRequest(head: head, metadata: metadata)
case .running, .finished, .waitForChannelToBecomeWritable, .modifying:
preconditionFailure("`startRequest()` must be called first, and exactly once. Invalid state: \(self.state)")
}
}
mutating func writabilityChanged(writable: Bool) -> Action {
@@ -381,6 +389,10 @@ struct HTTPRequestStateMachine {
case .initialized, .waitForChannelToBecomeWritable:
let error = HTTPClientError.cancelled
self.state = .failed(error)
// Okay, this has different semantics for HTTP/1 and HTTP/2. In HTTP/1 we don't want to
// close the connection, if we haven't sent anything yet, to reuse the connection for
// another request. In HTTP/2 we must close the channel to ensure it is released from
// HTTP/2 multiplexer.
return .failRequest(error, .none)
case .running: