Skip to content

Close a dropped TCP session without blocking on its task - #86

Open
IntellyCode wants to merge 1 commit into
narrowlink:mainfrom
IntellyCode:fix-blocking-drop
Open

Close a dropped TCP session without blocking on its task#86
IntellyCode wants to merge 1 commit into
narrowlink:mainfrom
IntellyCode:fix-blocking-drop

Conversation

@IntellyCode

Copy link
Copy Markdown

IpStackTcpStream::drop waits for the session's task through block_in_place and block_on. block_in_place works by handing the worker's core to the blocking pool, but the pool stops accepting work as soon as the runtime starts shutting down. A session dropped from that point on blocks its worker forever — the core is never handed off, so the task being waited for has no thread left to run on, and the runtime's shutdown is itself waiting for that worker. For a TUN proxy this happens on every exit with open connections: a handful of sessions usually gets away with it, a few hundred hangs the process for good. On a current-thread runtime the same line panics instead, which aborts the process from inside Drop.

This keeps what the waiting was for and drops the waiting. send_fin_n_change_state_to_fin_wait1 is synchronous and writes to an unbounded channel, so the farewell packet is sent from the drop itself, under the same state guard. The exit signal becomes try_send, and the task is aborted rather than awaited — which drops its destroy_messenger, and since the watcher does rx.await.ok(), the session still leaves the stack.

`IpStackTcpStream::drop` waited for the session's task to finish, through
`block_in_place` and `block_on`. `block_in_place` works by handing the worker's
core to the blocking pool, and the pool stops accepting work once the runtime
begins shutting down. A session dropped from that point on blocks its worker
forever: the core is never handed off, the task it waits for has no thread left
to run on, and the runtime's own shutdown waits for that worker in turn.

The farewell packet reaches the device through an unbounded channel, so it is
sent from the drop itself. The task is told to exit through the channel it
already watches, and aborted rather than awaited. Aborting drops the task's
`destroy_messenger`, and the watcher that removes the session treats a dropped
sender the same as a sent one, so the session still leaves the stack.
@SajjadPourali SajjadPourali self-assigned this Aug 15, 2026
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.

2 participants