ChannelRead because of closing connection: Remove preconditions (#430)

### Motivation

NIO may send `channelRead` events without a handlers requesting more data with a `context.read()` invocation. This happens if the remote has closed the connection and NIO wants to inform the handlers as soon as possible.

### Changes

- Don't `precondition` on `channelRead` events anymore.
- Close channel if we received an http end without a `context.read()` invocation
This commit is contained in:
Fabian Fett
2021-09-20 18:11:04 +02:00
committed by GitHub
parent 7c9662d31c
commit bef8878ede
4 changed files with 123 additions and 20 deletions
@@ -523,12 +523,22 @@ struct HTTPRequestStateMachine {
where head.status.code < 300:
return self.avoidingStateMachineCoW { state -> Action in
let remainingBuffer = responseStreamState.end()
state = .running(
.streaming(expectedBodyLength: expectedBodyLength, sentBodyBytes: sentBodyBytes, producer: producerState),
.endReceived
)
return .forwardResponseBodyParts(remainingBuffer)
let (remainingBuffer, connectionAction) = responseStreamState.end()
switch connectionAction {
case .none:
state = .running(
.streaming(expectedBodyLength: expectedBodyLength, sentBodyBytes: sentBodyBytes, producer: producerState),
.endReceived
)
return .forwardResponseBodyParts(remainingBuffer)
case .close:
// If we receive a `.close` as a connectionAction from the responseStreamState
// this means, that the response end was signaled by a connection close. Since
// the request is still uploading, we will not be able to finish the upload. For
// this reason we can fail the request here.
state = .failed(HTTPClientError.remoteConnectionClosed)
return .failRequest(HTTPClientError.remoteConnectionClosed, .close)
}
}
case .running(.streaming(_, _, let producerState), .receivingBody(let head, var responseStreamState)):
@@ -536,16 +546,23 @@ struct HTTPRequestStateMachine {
assert(producerState == .paused, "Expected to have paused the request body stream, when the head was received. Invalid state: \(self.state)")
return self.avoidingStateMachineCoW { state -> Action in
let remainingBuffer = responseStreamState.end()
// We can ignore the connectionAction from the responseStreamState, since the
// connection should be closed anyway.
let (remainingBuffer, _) = responseStreamState.end()
state = .finished
return .succeedRequest(.close, remainingBuffer)
}
case .running(.endSent, .receivingBody(_, var responseStreamState)):
return self.avoidingStateMachineCoW { state -> Action in
let remainingBuffer = responseStreamState.end()
let (remainingBuffer, action) = responseStreamState.end()
state = .finished
return .succeedRequest(.none, remainingBuffer)
switch action {
case .none:
return .succeedRequest(.none, remainingBuffer)
case .close:
return .succeedRequest(.close, remainingBuffer)
}
}
case .running(_, .endReceived), .finished: