Core LibraryunreleasedGuides

Choose documentation 2

latest

Current version

latest
English

Unreleased libtmux has not been released. Its API may change during development. This site's structure and URLs may change too.

Observe output and wait for rendered text

Use native_watch.ml for a complete concurrent literal-send, future-byte match and rendered-screen check. Its application owns the Eio scheduler and switch. Use native_wait.ml to capture exactly one pane and wait until its ordinary screen contains the supplied text. The example imports the installed Eio client, owns its switch, uses an explicit socket, closes its client and prints the matched pane ID. It requires an existing daemon and one pane; absence and ambiguity remain errors.

Client.wait_for_text first subscribes, then captures the screen immediately. Output invalidates that observation. Queued Data events are coalesced before the next bounded capture; a queued Gap stops that coalescing. Membership and overflow gaps cause fresh graph acquisition and subscription in the same connection binding. A disconnected daemon or stream fails the wait.

Success returns a captured screen containing the text. Text that appears and disappears between captures may be missed. Deadlines are checked between acquisitions, whose in-flight I/O keeps the client’s exchange timeout. A timed out wait does not prove that text was absent in the past.

The wait releases its temporary subscription on every exit. An earlier error or cancellation remains the primary result; Client.cleanup retains secondary cleanup failures. If cleanup fails after a match or during gap recovery, the wait fails instead of reporting success or opening another subscription.

Use Client.subscribe and Subscription.next for future output bytes. Data retains every byte, including NUL and invalid UTF-8. Its cursor records a local subscription generation and an observed byte offset. A cursor is neither a snapshot timestamp nor a server-side history position. poll only removes a queued event and performs no I/O.

Subscription.wait_for_bytes consumes those future events and matches across event boundaries. Its nonempty pattern is limited to 64 KiB. It returns the match’s end cursor, possibly inside the last consumed Data event. A deadline leaves the subscription active. A Gap returns Output.Ended reason; later matching requires an explicit fresh capture and new subscription.

The defaults are 1 MiB and 1024 queued events per subscriber, at most 16 active subscribers, and eight total client lanes including command/wait lanes. Reuse one verified lane per covered session. Admission reserves bytes against the shared 32 MiB budget. A full queue produces Gap then End rather than blocking another subscriber or an independent command. Unknown loss has no byte count. These limits apply together; plan shared capacity before combining many observers with captures and reads.

Coverage uses an observed session/window context and revalidates it during admission. Layout or membership changes end continuity conservatively. Detaching a stream leaves the primary client usable; a daemon replacement retires the whole binding. New coverage always has a new subscription generation.

Eio readers belong to the client, use no application callbacks and keep running when a subscriber stalls. The blocking backend advances all stream lanes during Subscription.next. Cancellation closes the affected subscription and propagates through the backend’s cancellation mechanism. Client close releases all owned lanes and retains terminal events; it leaves the daemon alive.

When a subscription releases the last user of a lane, close waits for its owned helper to be reaped. Concurrent Eio subscription closes join that same cleanup. Repeated closes preserve the first terminal reason and any cleanup failure; unconsumed terminal events remain readable through Subscription.poll.

The recovery program shows how to cancel one operation waiter while keeping its retained outcome available. Cancelling a subscription closes that subscription; it does not cancel an already admitted command.

Neither interface establishes an atomic capture/stream barrier or reconstructs terminal history. An immutable snapshot remains a separate captured value.

Plan shared capacity

Every client has its own bounded payload budget and process-slot pool. The blocking and Eio clients use the same accounting. An individually valid queue or command can still return Error.Capacity when another reservation consumes the available headroom:

ResourceCurrent limit or reservation
Aggregate client payload32 MiB shared by queues, commands and operation scratch space
Admitted commands64; encoded payload accounting remains charged until completion or cancellation before writing
Admitted command bytes1 MiB shared across admitted operations; submission also temporarily reserves 2 MiB
Client process slots8, including the primary control client; each auxiliary slot reserves 128 KiB
Subscriptions16 active per client, sharing a verified stream lane per covered session
Subscriber queue1 MiB / 1024 events by default; reserve the full configured byte capacity even when empty
Graph or rendered capture16 MiB operation scratch reservation plus an 8 MiB control exchange
Option, environment or hook read16 MiB scratch reservation plus a temporary process slot

The payload budget tracks library reservations. Caller-retained snapshots, completed operation handles, receipts and exception payloads have their own lifetimes, alongside runtime overhead, child-process memory and tmux’s allocations. See observation retention.

For example, sixteen default queues on one covered session reserve 16 MiB plus that session’s auxiliary slot. A subsequent capture or read cannot obtain its scratch reservation. Even eight default queues leave insufficient room for the capture’s nested control exchange. Closing subscriptions releases their reservations; the client remains usable after a capacity error.

Empty queues, one covered sessionCaptureOption read
16 × 1 MiBCapacityCapacity
8 × 1 MiBCapacityFits the reservation budget
7 × 1 MiBFits the reservation budgetFits the reservation budget
16 × 256 KiBFits the reservation budgetFits the reservation budget

These examples describe reservation accounting on a fixed one-pane graph. Additional lanes, admitted commands and concurrent work consume more headroom. A budget that fits still needs valid targets, available data and a responsive daemon.

The complete watcher chooses its queue explicitly:

let subscription =
get
(Client.subscribe ~capacity_bytes:(256 * 1024) ~capacity_events:256 client
pane)

Its full program includes the client, pane acquisition, result handling, Eio switch and cleanup. Choose a capacity for the burst your application can consume; a smaller queue reaches Gap Overflow sooner when its reader stalls.

Slots need their own headroom. The primary client and seven distinct covered sessions fill all eight slots, leaving none for a read process or wait_for. If an application needs those operations, it can cap distinct covered sessions at six while keeping one slot for service, accounting for any waits already using slots. The library currently reserves no slot exclusively for service. Several panes in the same session can share its stream lane.

Reducing queue capacities does not free process slots. Closing the last active subscriber for a covered session releases its lane after owned-helper cleanup. A separate client has its own budget and slots, but also its own source generation: acquire observations on that client before using its targets.

Recover after a gap

Close the ended subscription, capture the graph again, and establish fresh coverage for the observed pane. This recovers future observation. Bytes lost before the new subscription are still unavailable; a screen capture cannot reconstruct their history.

The complete recovery program creates a window running cat, observes a reply, then splits the window to trigger a membership gap. It handles that gap, removes the extra pane and closes the old subscription. It captures the original pane again, subscribes, and observes new input with a different cursor generation:

let recover_coverage client id (previous : Output.cursor) =
let fresh = capture_pane client id in
let after =
with_subscription client fresh (fun subscription ->
echo client fresh subscription "after-gap")
in
if Generation.equal previous.generation after.generation then
failwith "new subscription reused the old cursor generation";
print_endline "coverage=fresh; cursor-generation=new; future=after-gap"

The source contains the imports, scheduler, cleanup and matching helpers. capture_pane fails if the pane disappeared. with_subscription admits coverage before echo sends input and awaits its reply concurrently. The example never reconnects, reuses an old cursor or retries an uncertain mutation. It removes only panes whose creation returned successfully, leaving any foreign panes untouched. Primary exceptions survive secondary cleanup failures.

Creation here uses convenience calls. Cancellation before one returns can leave a creation whose ID the caller never received. For creation outcomes that must survive waiter cancellation, retain Client.submit’s handle before Client.await, as shown in operation recovery. An uncertain outcome does not authorize a retry or deletion of an unproven resource.

After installing from source, run this walkthrough from the clone. Create a private seed session:

Terminal window
$ export LIBTMUX_STREAM_RECOVERY_DIR="$(mktemp -d -t libtmux-ocaml-stream-recovery.XXXXXX)"
Terminal window
$ env -u TMUX -u TMUX_PANE tmux \
-S "$LIBTMUX_STREAM_RECOVERY_DIR/server" \
-f /dev/null \
new-session \
-d \
-s stream-demo \
-- "exec cat"

Run the program against the seed session’s literal $0 ID:

Terminal window
$ opam exec -- dune exec examples/native_stream_recovery.exe -- \
tmux "$LIBTMUX_STREAM_RECOVERY_DIR/server" '$0'

Expected output is gap=membership-changed; history=unavailable, followed by coverage=fresh; cursor-generation=new; future=after-gap. The seed session remains, with no additional panes or clients. Remove that session and directory:

Terminal window
$ env -u TMUX -u TMUX_PANE tmux \
-S "$LIBTMUX_STREAM_RECOVERY_DIR/server" \
kill-session \
-t '=stream-demo'
Terminal window
$ rm -f -- "$LIBTMUX_STREAM_RECOVERY_DIR/server"
Terminal window
$ rmdir "$LIBTMUX_STREAM_RECOVERY_DIR"

This exercise uses a stable private daemon and a deliberate layout change. Transport loss, repeated membership changes or a missing pane can prevent fresh coverage. Preserve those failures and choose the next action explicitly.

First use · Control and recovery · API · Guide

Esc

Type to search.

Reference previewOpen reference