fix: restore terminal mouse tracking state on PTY session disconnect#190
fix: restore terminal mouse tracking state on PTY session disconnect#190
Conversation
…189) After a PTY session disconnects (normal exit, Ctrl+C, network drop, or panic), remote interactive programs (vim, tmux, htop) may have enabled mouse tracking on the local terminal. Without cleanup, the terminal remains in tracking mode and prints raw SGR escape sequences on mouse movement. All cleanup paths now emit the complete set of mouse-tracking-off sequences (modes 1000, 1002, 1003, 1006, 1015) together with cursor-show and alternate-screen-exit: - TerminalStateGuard::restore_terminal_state() (Drop path) - force_terminal_cleanup() (last-resort call sites) - TerminalGuard::restore_terminal() in interactive_signal.rs (panic hook) now delegates to force_terminal_cleanup() instead of carrying its own incomplete cleanup — centralising logic and fixing the gap.
Use try_lock() in force_terminal_cleanup() so the panic hook path (TerminalGuard::restore_terminal -> force_terminal_cleanup) cannot deadlock if the panicking thread already holds TERMINAL_MUTEX, and so a poisoned mutex from a previous panic does not cause a secondary panic via unwrap(). The underlying stdout writes and disable_raw_mode are individually safe; the lock only serializes concurrent teardown.
Implementation Review SummaryIntent
Findings Addressed
Remaining ItemsNone. Verification
Notes on items called out in the review brief
Final VerdictApprove — ready to merge once the orchestrator transitions the status label. |
Add four unit tests to src/pty/terminal.rs covering the safety properties of force_terminal_cleanup(): - idempotency: calling twice does not panic - poisoned-mutex resilience: try_lock().ok() yields None instead of panicking when the mutex is poisoned by a previous panic - held-mutex resilience: try_lock().ok() returns immediately rather than blocking when the lock is already held on this thread Tests use local Mutex instances (never the global TERMINAL_MUTEX) so the global state is not disturbed for other tests in the same process. A comment block explains what cannot be tested in a non-TTY environment and why. Also extend the force_terminal_cleanup() docstring to make explicit that "unsynchronized" means without the lock, not skipped — addressing the LOW-severity documentation finding from the security review.
PR FinalizationTests (4 added)File:
Test count before: 1183. After: 1187. All pass, 0 failed. A comment block in the test module documents the gap: TTY-dependent operations (raw mode toggle, alternate screen) and the escape-sequence output cannot be meaningfully asserted in a non-TTY Docstring update
CHANGELOG
Docs / i18nNo docs/ content covers terminal cleanup internals at this level. No update needed. Lint / format
All checks passing. Ready for merge. |
Summary
0;59;35M32;58;35M...) on any mouse movement.TerminalStateGuard::restore_terminal_state()(the Drop path) andforce_terminal_cleanup()(last-resort call sites) to emit the complete set of mouse-tracking-off sequences (modes 1000, 1002, 1003, 1006, 1015) plus cursor-show (?25h) and alternate-screen-exit (?1049l) on teardown.TerminalGuard::restore_terminal()ininteractive_signal.rs(the panic-hook path) to delegate toforce_terminal_cleanup()instead of maintaining its own incomplete cleanup — this centralises the logic and closes the gap on the panic path (option a from the issue).Cleanup paths covered
TerminalStateGuard::Drop(normal session exit)force_terminal_cleanup()(dispatcher, execution, pty/mod call sites)TerminalGuard::restore_terminal()— panic hookforce_terminal_cleanup()(same as above)Panic-hook path decision
Chose option (a):
TerminalGuard::restore_terminal()now callsforce_terminal_cleanup(). This avoids duplicating the escape-sequence list and ensures the panic path receives any future improvements toforce_terminal_cleanup()automatically.Signal handler observation
The existing Ctrl+C and SIGTERM handlers (via
ctrlcandtokio::signal) set the shutdown flag and rely on the existing call sites andTerminalStateGuard::Dropto perform cleanup. The three existingforce_terminal_cleanup()call sites (dispatcher.rs:429,execution.rs:99,pty/mod.rs:161) are sufficient. No new signal handler was added.Test plan
cargo fmt --checkpassescargo check --all-targetspasses (clean compilation)cargo test --libpasses (1183 tests, 0 failed)cargo clippy --all-targets -- -D warnings: the 9 errors reported are all pre-existing in unrelated files (hostlist/expander.rs,server/sftp.rs,ssh/ssh_config/path.rs,ssh/tokio_client/channel_manager.rs,ui/tui/event.rs); zero errors in modified filesbssh -H user@host, startvimwith:set mouse=a, quit, disconnect — confirm no garbled mouse sequences appear (must be run by maintainer in an interactive terminal)Closes #189