Skip to content
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

Move packet writing code, add graceful shutdown handler, fix close handshake #43

Merged
merged 7 commits into from Mar 22, 2024

Conversation

adam-fowler
Copy link
Member

@adam-fowler adam-fowler commented Mar 22, 2024

  • Move write packet code into WebSocketOutboundWriter
  • Add cancellation and graceful shutdown handlers
  • Implement close process correctly ie send close, wait for responding close

router.ws("/ws") { inbound, outbound, _ in
for try await packet in inbound {
router.ws("/ws") { ws, _ in
for try await packet in ws.inbound {
Copy link
Contributor

Choose a reason for hiding this comment

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

I like that this follows the pattern found in NIO. But I wonder if ws.inbound as a symbol is easy to reason about for newcomers to the ecosystem.

if case .text("disconnect") = packet {
break
}
try await outbound.write(.custom(packet.webSocketFrame))
try await ws.outbound.write(.custom(packet.webSocketFrame))
Copy link
Contributor

Choose a reason for hiding this comment

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

ws.write would also be easier to reason about IMO

try await handler(webSocketHandlerInbound, webSocketHandlerOutbound, context)
try await self.close(code: .normalClosure, outbound: outbound, context: context)
} onGracefulShutdown: {
Task {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a way around an unstructured task here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Unfortunately not. I can't close either the inbound stream or outbound writer as they are needed for the close handshake. It's a recurring issue with graceful shutdown handlers, where something more complex is need to initiate shutdown.

Copy link
Member Author

Choose a reason for hiding this comment

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

I could have a child task that waits on a continuation and then calls close. I then resume the continuation in the graceful shutdown. But I'd have to somehow get the continuation to the shutdown, and also support the situation where the graceful shutdown is called before the continuation is created. That'd be a bit of a mess

guard self.type == .client else { return nil }
let bytes: [UInt8] = (0...3).map { _ in UInt8.random(in: .min ... .max) }
return WebSocketMaskingKey(bytes)
try await outbound.write(frame: .init(fin: true, opcode: .connectionClose, data: buffer))
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should consider making close non-throwing. I get that writing a frame can fail, but that's an acceptable and ignorable failure in my book - at this point in the WebSocket's lifecycle. Possibly something to ask the NIO team's opinion on.

Copy link
Member Author

Choose a reason for hiding this comment

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

I disagree here, this initiates the close handshake and doesn't actually close the websocket. It only gets closed when we receive the close connection message from the client. If we ignore the error then the close connection is never sent and we don't receive a close connection from the client. Remember also this is an internal function. Users will close the connection by exiting the handler.

In the current situation when the error is thrown out of the asyncChannel.executeThenClose closure which will force close the connection.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, good clarification. I misremember that then

Base automatically changed from cleanup-api to main March 22, 2024 10:39
@adam-fowler adam-fowler changed the title Group inbound/outbound into WebSocket type Move packet writing code, add graceful shutdown handler, fix close handshake Mar 22, 2024
@adam-fowler adam-fowler merged commit df44f88 into main Mar 22, 2024
3 of 4 checks passed
@adam-fowler adam-fowler deleted the group-inbound-outbound branch March 22, 2024 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants