Skip to content

Commit 57ad5da

Browse files
committed
Docs(topics[automation]): Wait on a channel instead of polling
why: The page taught polling with a bound, which costs a tmux round-trip per read and a sleep paid whether the command finished or not. tmux offers a rendezvous instead, and it is worth teaching next to the poll so a reader can see the trade. what: - Add "Waiting for a signal instead of polling": a pane signals with `tmux wait-for -S` and the script waits with `Server.wait_for()`, bounded so a signal that never arrives is catchable. - Record two properties of the rendezvous a reader will otherwise meet the hard way. A wait that times out is abandoned rather than withdrawn, and tmux only remembers a signal when nothing is waiting, so the next signal on that channel is spent waking the abandoned wait -- one signal, after which the channel behaves normally. A fresh channel name per rendezvous avoids it. - Note that a returning wait is not proof the work succeeded: tmux releases every waiter on server shutdown exactly as if the channel had been signalled, so a pane that died and a command that finished look the same from the wait alone.
1 parent e46466a commit 57ad5da

1 file changed

Lines changed: 59 additions & 0 deletions

File tree

docs/topics/automation_patterns.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,65 @@ True
437437
>>> timeout_window.kill()
438438
```
439439

440+
### Waiting for a signal instead of polling
441+
442+
Polling costs you a tmux round-trip per read and a `sleep` you pay whether the
443+
command finished or not. tmux offers the other half of the trade: a *channel*,
444+
where one process waits and another signals, and nobody polls in between. The
445+
pane signals with `tmux wait-for -S`, and your script waits with
446+
{meth}`~libtmux.Server.wait_for`.
447+
448+
What you give up is the guarantee that the signal arrives at all — kill the pane,
449+
close its window, or have the command exit before it reaches the `wait-for`, and
450+
the waiter waits forever. Pass a `timeout` to cap it, and a missing signal becomes
451+
a {exc}`~libtmux.exc.TmuxCommandTimeout` you can catch. It is a
452+
{exc}`~libtmux.exc.WaitTimeout`, so an existing handler still catches it, and it
453+
carries the command that was killed and the bound it blew.
454+
455+
```python
456+
>>> signal_window = session.new_window(window_name='signal-demo', attach=False)
457+
>>> signal_pane = signal_window.active_pane
458+
459+
>>> channel = 'demo-work-done'
460+
>>> signal_pane.send_keys(f'echo "working"; tmux wait-for -S {channel}')
461+
>>> session.server.wait_for(channel, timeout=60)
462+
463+
>>> signal_window.kill()
464+
```
465+
466+
Nothing signals `never-arrives`, so the wait ends on the clock rather than on the
467+
work:
468+
469+
```python
470+
>>> from libtmux import exc
471+
472+
>>> try:
473+
... session.server.wait_for('never-arrives', timeout=0.25)
474+
... except exc.TmuxCommandTimeout as e:
475+
... print(f'gave up after {e.timeout}s')
476+
gave up after 0.25s
477+
```
478+
479+
The bound belongs to the call, not to the server object, so the same server can
480+
carry a patient wait for a build and an impatient one for a health check.
481+
482+
Two properties of tmux's rendezvous are worth knowing before you rely on it.
483+
484+
A wait that times out is abandoned rather than withdrawn, and tmux only
485+
*remembers* a signal when nothing is waiting for the channel. The abandoned wait
486+
still counts, so the next signal on that channel is spent waking it instead of
487+
being remembered — one signal, after which the channel behaves normally again.
488+
Waits already in flight are woken as usual and other channels are untouched.
489+
Naming a fresh channel per rendezvous, from a build id or a UUID, sidesteps it
490+
entirely.
491+
492+
A returning `wait_for` also means less than it appears to. tmux releases every
493+
waiter when the server shuts down, and it does so exactly as if the channel had
494+
been signalled, so a pane that died and a command that finished are
495+
indistinguishable from the wait alone. When it matters whether the work actually
496+
succeeded, have the command report its own result — write the exit status to a
497+
pane option and read it back — rather than treating the wake-up as proof.
498+
440499
### Retry pattern
441500

442501
For flaky work that succeeds on a later attempt, wait for each attempt to finish and

0 commit comments

Comments
 (0)