Skip to content

fix(server): close undisclosed sessions when an open fails - #136

Merged
svnscha merged 3 commits into
developfrom
fix/session-ownership
Oct 8, 2026
Merged

svnscha merged 3 commits into
developfrom
fix/session-ownership

Conversation

@svnscha

@svnscha svnscha commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Closes #125.

All four open_* tools register their session before running init commands and triage, so a failure afterwards returns an MCP error with no session_id while the session stays in _sessions with its debugger alive. The caller never learns the id, so it can never close it, and a retry adds another one.

Reproduced against the real registered tool handler on develop, with a debugger that constructs fine and fails its first triage command the way a !peb timeout does:

attempt 1: MCPError: Command timed out after 10 seconds: .lastevent
attempt 2: MCPError: Command timed out after 10 seconds: .lastevent

sessions left registered : 2
shutdown() calls         : 0

After this change the same script reports 0 sessions left and 2 shutdowns.

What it does

An open now owns its debugger until initialization, triage and output filtering have all succeeded. An ExitStack registers the rollback alongside the session, pop_all() disarms it on success, and the rollback is shielded so cancellation cannot skip it. It only ever closes the session created by that call, so unrelated busy sessions are untouched.

Two deliberate choices worth calling out:

  • A rollback never resumes a live target. Closing a session normally lets the target run again, which is what close_kd_session's resume parameter is for. An open that failed never handed out an id, so resuming on its behalf would be a side effect the caller cannot opt out of. CTRL+B detaches and resumes, so a live close with resume disabled now writes nothing: dropping the client leaves the server's target exactly as it is.
  • The rollback runs on failure only, and on a worker rather than the event loop, which has to stay free for break-in on other sessions.

The tree kill is now bounded, since a rolled-back open runs teardown on the event loop's behalf and an unresponsive taskkill must not keep the server from exiting.

Credit

The diagnosis, the reproduction and the ExitStack design are @xiaozhu1337's, from #125 and #126; the first two commits here are theirs. This takes those two fix commits and leaves out the 433-line server_support.py extraction that #126 also carried, so the fix can be reviewed on its own. The third commit is the follow-up above.

Tests

uv run pytest src/mcp_windbg/tests/ -q     ->  189 passed, 2 skipped

Baseline on develop is 156 passed, 2 skipped, so this adds 33 cases: 30 covering ownership across all four open tools (init, mandatory and optional triage, output-filter and cancellation failures, repeated retries, unrelated-session preservation, shutdown errors) and 3 covering the release-target policy. Live tests included, driving real cdb and kd.

xiaozhu1337 and others added 3 commits October 8, 2026 23:01
Three corrections to the rollback, found while reviewing it:

- Roll back on failure only. The rollback ran from a `finally`, so a
  successful open also went through the worker pool a second time, costing
  another thread hop and an uncancellable wait for a limiter token.
- Never resume a live target. Closing a session normally lets the target run
  again, which is what `close_kd_session`'s `resume` parameter is for. An open
  that failed never handed out a session id, so resuming on its behalf is a
  side effect the caller cannot opt out of. CTRL+B detaches *and* resumes, so
  a live close with resume disabled now writes nothing: dropping the client
  leaves the server's target exactly as it is.
- Bound the tree kill. A rolled-back open runs teardown on the event loop's
  behalf, so an unresponsive `taskkill` must not keep the server from exiting.

Also makes `cleanup` keyword-only, since as a trailing positional it could be
shifted silently by a change to the settings tuple.
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.

Failed open tools leak undisclosed debugger sessions after triage or output-filter errors

2 participants