Ensure socket is closed even when transmit raises in Connection#close#17
Merged
Merged
Conversation
When ActionCable::Server::Base#restart iterates its connections and calls connection.close on each, the disconnect message is transmitted and then socket.close is called. If the socket has already been closed (e.g. by a prior restart call that hasn't been fully cleaned up yet), transmit raises ClosedQueueError and socket.close is never reached. Move socket.close into an ensure block so the socket is always closed regardless of whether transmitting the disconnect message succeeds. Errors during transmission are logged and swallowed since the socket is being torn down anyway. Fixes a ClosedQueueError reported when restart is called while a previous restart's connection cleanup is still pending.
Contributor
Author
|
Copilot authored this fix. However, I think it's the correct semantic. The logging line is optional - I actually think we don't really need it as there is no reasonable action that can be taken by the user IMHO. On closing, we are trying to gracefully transmit the shutdown message, but there is no guarantee that transmit will work – ever. Since we don't want close to raise, it needs to ignore transmit failures. Alternative to #16. To be really defensive, |
Member
|
Thanks! Looks good |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
ActionCable::Server::Base#restartiterates its connections and callsconnection.closeon each.Connection#closetransmits the disconnect message and then callssocket.close. If the socket has already been closed (e.g. by a priorrestartcall whose middleware cleanup hasn't run yet),transmitraisesClosedQueueErrorandsocket.closeis never reached.Real-world backtrace:
This happens because the middleware's
ensureblock (which callsremove_connection) runs asynchronously after the WebSocket loop exits. Betweenconnection.close(socket closed) and that cleanup, the connection is still in the server's connections list. A secondrestartcall during this window finds the stale entry and tries to close it again.Fix
Move
socket.closeinto anensureblock so the socket is always closed regardless of whether transmitting the disconnect message succeeds. Errors duringtransmitare logged and swallowed — the socket is being torn down anyway.Tests
Added
test_socket_is_closed_even_when_transmit_raises_during_closetotest/connection/base_test.rbwhich stubssocket.transmitto raiseClosedQueueErrorand asserts thatsocket.closeis still called.