Conversation
why: `tmux_cmd` waited on `Popen.communicate()` with no deadline, so a tmux server that accepts a connection and never replies held its caller forever. Cancelling the coroutine that awaits such a call does not interrupt it, so hung calls only accumulate; downstream, forty of them exhausted anyio's default thread limiter and the host process stopped serving every socket, healthy ones included. `TmuxTimeout` is deliberately NOT a `LibTmuxException`. The listing accessors absorb one of those as "nothing to list", which is right for a daemon that has not started and wrong for a server that stopped answering: a caller told there are no sessions goes on to create one on a server that already has them. A sibling type gets that for free at every such site. what: - Add `exc.TmuxTimeout`, carrying the argv and the bound it passed - Add `tmux_cmd(..., timeout=)`; on expiry kill the child and reap it before raising, so repeated timeouts do not leave tmux processes nothing is waiting on - Add a `hanging_tmux` fixture: a stand-in that answers `-V` and hangs on everything else, which is the shape of a wedged server - Cover the raise, and that the process is gone afterwards. Shown failing on the kill: without it the pid is still alive
why: `Server.cmd` is not the only funnel. `neo.fetch_objs` builds a `tmux_cmd` directly and is the engine behind `Server.sessions`, `Session.windows` and `Window.panes`, so a consumer cannot bound its calls with a `Server` subclass -- the busiest path is not reachable that way. what: - Add `Server(timeout=)`, used by `Server.cmd` unless a call overrides it - Pass the server's timeout through `fetch_objs` - Assert every listing accessor raises rather than answering empty on a wedged server: `sessions`, `windows`, `panes`, `clients`. That is what the sibling exception type buys, and the parametrization is what shows it holds at all four
why: New capability with a default that changes nothing, plus one exception type whose place in the hierarchy is a deliberate choice worth stating where users read it. what: - Add a `### What's new` deliverable for the bound and its exception
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #757 +/- ##
==========================================
+ Coverage 52.37% 52.53% +0.16%
==========================================
Files 26 26
Lines 3729 3746 +17
Branches 747 747
==========================================
+ Hits 1953 1968 +15
- Misses 1472 1474 +2
Partials 304 304 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
tony
force-pushed
the
tmux-cmd-timeout
branch
from
September 13, 2026 10:48
14d1564 to
5ed8ae9
Compare
7 tasks
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.
Overlaps #735 and reverses one of its decisions. Opened as a draft so the two approaches can be compared side by side.
What this does
tmux_cmdwaited onPopen.communicate()with no deadline, so a tmux server that accepts a connection and never replies held its caller forever. Cancelling the coroutine that awaits such a call does not interrupt it, so hung calls only accumulate; downstream, forty of them exhausted anyio's default thread limiter and the host process stopped serving every socket, healthy ones included.tmux_cmd(..., timeout=)bounds one command. On expiry the tmux process is killed and reaped beforeexc.TmuxTimeoutis raised, carrying the argv and the bound, so a timed-out call leaves nothing running behind it.Server(timeout=)sets the bound once.Server.cmd()uses it unless a call passes its owntimeout, andneo.fetch_objsuses it too.The second funnel is the point of the server-wide default.
fetch_objsbuilds atmux_cmddirectly and is the engine behindServer.sessions,Session.windows,Window.panesandServer.clients. Those listings never go throughServer.cmd(), so a per-calltimeoutcannot reach them, and neither can aServersubclass.Without a timeout nothing changes: every default is
None, which waits as before.TmuxTimeoutis not aLibTmuxExceptionThe listing accessors are lenient: they read a
LibTmuxExceptionas "nothing to list". That is right for a daemon that has not started and wrong for a server that has stopped answering, because a caller told there are no sessions goes on to create one on a server that already has them. A sibling exception type gets the loud failure at every such site without touching the accessors. The listing test is parametrized oversessions,windows,panesandclientsto show it holds at all four.Relationship to #735
#735 puts
timeouton each call instead: oncmd()forServer,Session,WindowandPane, and onServer.wait_for(), raisingTmuxCommandTimeout(WaitTimeout).Where the two differ:
Server(timeout=)after measuringserver.cmd("attach-session", ...)killed at ~1.06s underServer(timeout=1.0). That risk applies here unchanged:Server.attach_session()goes throughServer.cmd()with no exemption. A per-calltimeout=Nonealso falls back to the server's default, so there is no way to ask for an unbounded call on a bounded server.fetch_objs, sosessions,windows,panesandclientsstay unbounded there. Its exception is also aLibTmuxException(viaWaitTimeout), which the lenient accessors would swallow if it were raised from them.timeouttoSession.cmd(),Window.cmd(),Pane.cmd()andServer.wait_for(); this branch does not.TmuxTimeoutandTmuxCommandTimeout, so one of them should give way.Tests
tmux_cmdraises on expiry, and the process is gone afterwards. Shown failing on the kill: without it the pid is still alive.hanging_tmuxfixture stands in for a wedged server: it answers-Vand hangs on everything else.Verification
ruff check,ruff format,mypyandpy.test --reruns 0(1462 passed, 1 skipped), plusjust build-docs, all clean on the branch tip.