in Sources/NIOHTTP2/ConnectionStateMachine/ConnectionStateMachine.swift [1186:1256]
mutating func receiveGoaway(lastStreamID: HTTP2StreamID) -> StateMachineResultWithEffect {
// GOAWAY frames are some of the most subtle frames in HTTP/2, they cause a number of state transitions all at once.
// In particular, the value of lastStreamID heavily affects the state transitions we perform here.
// In this case, all streams initiated by us that have stream IDs higher than lastStreamID will be closed, effective
// immediately. If this leaves us with zero streams, the connection is fullyQuiesced. Otherwise, we are quiescing.
switch self.state {
case .prefaceReceived(var state):
return self.avoidingStateMachineCoW { newState in
let result = state.receiveGoAwayFrame(lastStreamID: lastStreamID)
let newStateData = QuiescingPrefaceReceivedState(fromPrefaceReceived: state, lastStreamID: lastStreamID)
newState = .quiescingPrefaceReceived(newStateData)
return result
}
case .active(var state):
return self.avoidingStateMachineCoW { newState in
let result = state.receiveGoAwayFrame(lastStreamID: lastStreamID)
let newStateData = RemotelyQuiescedState(fromActive: state, lastLocalStreamID: lastStreamID)
newState = .remotelyQuiesced(newStateData)
newState.closeIfNeeded(newStateData)
return result
}
case .locallyQuiesced(var state):
return self.avoidingStateMachineCoW { newState in
let result = state.receiveGoAwayFrame(lastStreamID: lastStreamID)
let newStateData = BothQuiescingState(fromLocallyQuiesced: state, lastLocalStreamID: lastStreamID)
newState = .bothQuiescing(newStateData)
newState.closeIfNeeded(newStateData)
return result
}
case .remotelyQuiesced(var state):
return self.avoidingStateMachineCoW { newState in
let result = state.receiveGoAwayFrame(lastStreamID: lastStreamID)
newState = .remotelyQuiesced(state)
newState.closeIfNeeded(state)
return result
}
case .bothQuiescing(var state):
return self.avoidingStateMachineCoW { newState in
let result = state.receiveGoAwayFrame(lastStreamID: lastStreamID)
newState = .bothQuiescing(state)
newState.closeIfNeeded(state)
return result
}
case .quiescingPrefaceReceived(var state):
return self.avoidingStateMachineCoW { newState in
let result = state.receiveGoAwayFrame(lastStreamID: lastStreamID)
newState = .quiescingPrefaceReceived(state)
return result
}
case .idle, .prefaceSent, .quiescingPrefaceSent:
// We're waiting for the preface.
return .init(result: .connectionError(underlyingError: NIOHTTP2Errors.missingPreface(), type: .protocolError), effect: nil)
case .fullyQuiesced(var state):
return self.avoidingStateMachineCoW { newState in
// We allow duplicate GOAWAY here, so long as it ratchets correctly.
let result = state.receiveGoAwayFrame(lastStreamID: lastStreamID)
newState = .fullyQuiesced(state)
return result
}
case .modifying:
preconditionFailure("Must not be left in modifying state")
}
}