Skip to content

daemon: never run the detach handshake under the shells lock - #413

Open
dob323 wants to merge 1 commit into
shell-pool:masterfrom
dob323:fix/detach-shells-lock
Open

daemon: never run the detach handshake under the shells lock#413
dob323 wants to merge 1 commit into
shell-pool:masterfrom
dob323:fix/detach-shells-lock

Conversation

@dob323

@dob323 dob323 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

The client_connection/client_connection_ack exchange is a rendezvous, so it only completes while the shell->client thread sits in its select loop. A client whose socket has stopped draining (a stalled ssh window, a suspended laptop) leaves that thread blocked in write(), and handle_detach ran the exchange while still holding the global shells lock — one unresponsive client wedged every list, attach, detach and kill in the daemon.

This resolves the requested names to Arcd ctl handles under the shells lock, drops it, then runs each handshake with the same bounded send/recv timeouts the session-message detach path already uses. A session that cannot complete the handshake in time is reported as not attached instead of being allowed to stall the daemon.

Regression test regression::detach_of_stalled_client_does_not_wedge_daemon: SIGSTOP the attach client, flood the socket buffers, detach. It times out on master and passes with the patch.

Found by stress-testing shpool 0.11.0 under a session manager that drives many concurrent sessions; we have been running this fix in production for a week. AI disclosure, as requested on #406: drafted with an AI assistant, reviewed and tested by a human before submission.

@ethanpailes ethanpailes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks pretty good, can you rebase against HEAD? My comments are mostly just nits.

Thanks for the fix!

// immediately stop the client so nothing drains.
attach_proc.run_cmd("yes | head -c 8000000; echo flood-done")?;
let client_pid = attach_proc.proc.id().to_string();
let stopped = Command::new("kill")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

// Both halves are bounded, matching the session-message detach path.
// A session that cannot complete the handshake in time is reported as
// not attached rather than being allowed to stall the daemon.
let mut detached_sessions = vec![];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: Vec::with_capacity(targets.len())

// publish; we just update the lifecycle state eagerly so a concurrent
// list() reflects the detach immediately.
if !detached_sessions.is_empty() {
let _s = span!(Level::INFO, "timestamp_lock(shells)").entered();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why "timestamp_lock"? I think "hook_lock" would be a better description

client_connection/client_connection_ack are rendezvous channels, so the
exchange only completes while the shell->client thread sits in its select
loop. A client whose socket stopped draining (a stalled ssh window, a
suspended laptop) leaves that thread blocked in write(), and handle_detach
ran the exchange while still holding the global shells lock -- one
unresponsive client wedged every list, attach, detach and kill in the
daemon.

Resolve names to Arc'd ctl handles under the lock, drop it, then run each
handshake with the same bounded send/recv timeouts the session-message
detach path already uses. A session that cannot complete the handshake is
reported as not attached instead of being allowed to stall the daemon.

Regression test: stop the attach client with SIGSTOP, flood the socket
buffers, detach. On unpatched master the daemon wedges and the test times
out; patched, detach answers and a follow-up list completes.
@dob323
dob323 force-pushed the fix/detach-shells-lock branch from 60cc844 to 455150a Compare August 17, 2026 20:15
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