Refactor outbound session lifecycle with disconnect-notice latch#9
Merged
henrikbjorn merged 4 commits intomasterfrom Mar 20, 2026
Merged
Refactor outbound session lifecycle with disconnect-notice latch#9henrikbjorn merged 4 commits intomasterfrom
henrikbjorn merged 4 commits intomasterfrom
Conversation
Server now owns the connection lifecycle instead of the listener. The read loop uses a latch that waits for both CHANNEL_HANGUP_COMPLETE and disconnect-notice before closing, ensuring all events are delivered when linger is active. - Add disconnect_notice? predicate to Response - Add ConnectionError for clean shutdown on connection drop - Add connection_closed hook (closes queues) to Base and Outbound - Track event hooks via Async::Barrier instead of bare Async - Server/Client: extract handle_session, start_read_loop, read_messages - Queue close in read_task ensure to avoid deadlock with handle_session - Outbound run_session auto-lingers and rescues connection errors
Client#run only rescued IOError and socket errors, not ConnectionError. A mid-handshake connection drop would crash the reconnect loop instead of retrying.
Without barrier.wait in connection_closed, event hooks could be killed mid-execution when the read loop latch triggers. Now connection_closed closes queues (unblocking any hooks stuck on send_message) then waits for all event hook fibers to finish via the Async::Barrier.
c960657
approved these changes
Mar 20, 2026
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.
Why
The previous architecture had the read loop as the main fiber and
run_sessionas a child task. Whensession_initiatedreturned, the session task was dead but the connection was still alive — events would arrive but nobody owned the lifecycle. This made event-driven patterns fragile: who calls disconnect, when, and from which event?FreeSWITCH sends
text/disconnect-noticeafter all events have been delivered (when linger is active). This is the protocol-level "session is done" signal. The library should use it to own the full session lifecycle rather than leaving it to user code.What changed
CHANNEL_HANGUP_COMPLETEandtext/disconnect-noticearrive, ensuring all events are delivered when linger is active.connection_closedhook on Base/Outbound closes queues from the read task's ensure block. This must live on the read fiber — not inhandle_session's ensure — becausehandle_sessionis blocked onrun_session, which is blocked on a queue dequeue. Closing the queue from the same fiber that's waiting on it would deadlock.handle_session,start_read_loop,read_messagesfor clear separation: accept/connect builds objects,handle_sessionorchestrates lifecycle,start_read_looplaunches the reader fiber,read_messageshandles protocol.ConnectionErrorraised on nil dequeue (closed queue) so connection drops cleanly unblocksend_messageandapplication.ConnectionErrorso a mid-handshake connection drop doesn't crash the inbound reconnect loop.