Skip to content

Conversation

@glbrntt
Copy link
Collaborator

@glbrntt glbrntt commented Jan 16, 2024

Motivation:

The server pipeline needs a channel handler which manages the lifecycle of TCP connections. This includes: policing keepalive pings sent by the client, managing graceful shutdown, shutting down idle connections, and shutting down connections which have lived for too long. Much of this logic can be abstracted into a state machine. This change add that state machine.

Modifications:

  • Add a server connection handler state machine which tracks the graceful shutdown state and polices keepalive pings sent by the client.
  • Replace a few READMEs with empty Swift files to suppress a few build warnings.

Result:

State machine for managing server connections.

Motivation:

The server pipeline needs a channel handler which manages the lifecycle
of TCP connections. This includes: policing keepalive pings sent by the
client, managing graceful shutdown, shutting down idle connections,
and shutting down connections which have lived for too long. Much of
this logic can be abstracted into a state machine. This change add that
state machine.

Modifications:

- Add a server connection handler state machine which tracks the
  graceful shutdown state and polices keepalive pings sent by the
  client.
- Replace a few READMEs with empty Swift files to suppress a few build
  warnings.

Result:

State machine for managing server connections.
@glbrntt glbrntt added the version/v2 Relates to v2 label Jan 16, 2024
Comment on lines +130 to +156
case .active(var state):
let tooManyPings = state.keepAlive.receivedPing(
atTime: time,
hasOpenStreams: !state.openStreams.isEmpty
)

if tooManyPings {
onPing = .enhanceYourCalmThenClose(state.lastStreamID)
self.state = .closed
} else {
onPing = .sendAck
self.state = .active(state)
}

case .closing(var state):
let tooManyPings = state.keepAlive.receivedPing(
atTime: time,
hasOpenStreams: !state.openStreams.isEmpty
)

if tooManyPings {
onPing = .enhanceYourCalmThenClose(state.lastStreamID)
self.state = .closed
} else {
onPing = .sendAck
self.state = .closing(state)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two cases are pretty similar, except for the state that is set in the else branch. Could the duplication be avoided or do we want to see clearly the steps for each case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's somewhat unavoidable here as the associated state types are different. IMO deduplicating here would also result in less readable code.

Comment on lines +294 to +296
if isAcceptablePing {
self.lastValidPingTime = time
tooManyPings = false
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume the bad pings don't have to be consecutive, right? Otherwise, we should reset the ping strikes to 0.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's my understanding from the various gRFCs.

}

/// Reset ping strikes and the time of the last valid ping.
mutating func reset() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're never calling this, which makes me think we haven't yet implemented the ping strike reset logic when receiving DATA/HEADERS frames - correct?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. This will have to be done when the connection handler is done as it requires input from the stream channels as well (which is a bit annoying...). There's effectively nothing to do in the state machine beyond calling reset though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a test that the reset actually works though as that's missing right now.

@glbrntt glbrntt requested a review from gjcairo January 17, 2024 17:15
@glbrntt glbrntt merged commit 917f37d into grpc:main Jan 18, 2024
@glbrntt glbrntt deleted the server-connection-state-machine branch January 18, 2024 08:01
glbrntt added a commit to glbrntt/grpc-swift that referenced this pull request Feb 5, 2024
Motivation:

The server pipeline needs a channel handler which manages the lifecycle
of TCP connections. This includes: policing keepalive pings sent by the
client, managing graceful shutdown, shutting down idle connections,
and shutting down connections which have lived for too long. Much of
this logic can be abstracted into a state machine. This change add that
state machine.

Modifications:

- Add a server connection handler state machine which tracks the
  graceful shutdown state and polices keepalive pings sent by the
  client.
- Replace a few READMEs with empty Swift files to suppress a few build
  warnings.

Result:

State machine for managing server connections.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

version/v2 Relates to v2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants