libtmux for Perl
# LibTmux-Async
LibTmux-Async runs tmux operations as Futures using the public [LibTmux core](https://github.com/libtmux/libtmux-perl/tree/master/packages/core) model. Submit independent operations concurrently, query their captured results locally, and explicitly await executor cleanup. [Run the example](#create-a-session-and-run-concurrent-operations) · [Cancellation](#cancel-work-and-finish-cleanup) · [Guide](https://libtmux.org/en/perl/latest/guides/async/) · [API reference](https://libtmux.org/en/perl/latest/reference/libtmux-async-server/) ## Install from source Install core first. From the async distribution directory: ```console $ cpm install \ --global \ . ``` Requires Perl 5.44, Future 0.54, IO::Async 0.805, IO::Async::Loop::Ppoll 0.10, and IO::Ppoll 0.14 or newer. IO::Ppoll includes an XS extension. Linux x86_64 LP64 and tmux 3.3a–3.8 are supported; CI tests the tmux endpoints. This distribution is not published on CPAN. Read the [source installation guide](https://github.com/libtmux/libtmux-perl/blob/master/docs/installation.md) for interpreter and dependency setup. ## Create a session and run concurrent operations [examples/concurrent.pl](examples/concurrent.pl) uses installed modules. It creates and removes its own session on the explicit socket, preserves other sessions, and leaves the caller's event loop alive. ```console $ perl examples/concurrent.pl "${TMPDIR:-/tmp}/libtmux-perl-async-$$.sock" ``` ```perl use v5.44; use IO::Async::Loop::Ppoll; use Future; use LibTmux::Async::Server; my $socket = shift @ARGV // die "Usage: $0 SOCKET_PATH [TMUX]\n"; my $tmux = shift @ARGV // 'tmux'; # example: begin connect my $loop = IO::Async::Loop::Ppoll->new; my $server = LibTmux::Async::Server->new( loop => $loop, socket_path => $socket, executable => $tmux, config_file => '/dev/null', max_concurrent => 2, max_queue => 8, ); # example: end connect my ($session, $failure); try { my $created = $server->new_session( name => 'perl-async', command => [$^X, '-e', 'while () { print }'], ); $loop->await($created); $session = $created->get; # example: begin concurrent my $capture = $server->snapshot; my $version = $server->cmd('display-message', '-p', '#{version}'); $loop->await(Future->needs_all($capture, $version)); my $snapshot = $capture->get; my $panes = $snapshot->sessions->one({name => 'perl-async'}) ->window_links->one->window->panes->where({active => true}); say 'async active panes: ', $panes->count; say 'tmux version: ', $version->get->stdout; # example: end concurrent die 'async session missing pane' unless $panes->count == 1; # example: begin batch my $batch = $server->batch( [ ['display-message', '-p', 'first'], ['display-message', '-p', 'second'], ] ); $loop->await($batch); my $results = $batch->get; for my $entry ($results->@*) { die $entry->{error} if defined $entry->{error}; print $entry->{result}->stdout; } # example: end batch die 'batch order changed' unless $results->[0]{result}->stdout eq "first\n" && $results->[1]{result}->stdout eq "second\n"; } catch ($error) { $failure = $error; $session //= $error->created if $error isa LibTmux::X::Creation; } if ($session) { try { my $removed = $server->kill_session(session => $session); $loop->await($removed); $removed->get; } catch ($cleanup) { warn "session cleanup failed: $cleanup\n"; $failure //= $cleanup; } } # example: begin close my $closed = $server->close; $loop->await($closed); try { $closed->get } catch ($cleanup) { warn "executor cleanup failed: $cleanup\n"; $failure //= $cleanup; } die $failure if defined $failure; say 'async operations and cleanup: ok'; # example: end close ``` The output includes `async active panes: 1`, the connected tmux version, `first` then `second`, and `async operations and cleanup: ok`. [`Future->needs_all`](https://metacpan.org/pod/Future#needs_all) lets independent acquisition and display requests overlap. `batch` preserves request order and returns a `{result, error}` entry for each independent command. Each ordinary operation yields one core value, including one `undef` for a void method. ## Cancel work and finish cleanup Cancelling an operation Future requests cleanup and suppresses later command continuations. It does not undo accepted effects. Only successful completion of the Future from [`close`](https://libtmux.org/en/perl/latest/reference/libtmux-async-server-close/) proves executor-owned processes and pipes have finished. Keep driving the loop through that Future; cancelling a close observer cannot cancel private cleanup. Closing preserves a borrowed tmux server. The standalone [lifecycle program](https://github.com/libtmux/libtmux-perl/blob/master/examples/async-lifecycle.pl) adds failure and cancellation paths against an existing explicit socket. It preserves the original operation failure and reports cleanup failure separately. See the [async guide](https://libtmux.org/en/perl/latest/guides/async/) for all three tested modes. ## Choose bounds and caller syntax Construct `IO::Async::Loop::Ppoll` explicitly and pass it to [`Server->new`](https://libtmux.org/en/perl/latest/reference/libtmux-async-server-new/). `max_concurrent` bounds active subprocesses; `max_queue` bounds waiting requests. Other loop backends are rejected before dispatch. A command timeout excludes queue delay and the total duration of a multi-command operation. `Future::AsyncAwait` is optional caller syntax. Core and workspace remain usable without Future, IO::Async, or IO::Ppoll. Installed documentation is available with `perldoc LibTmux::Async::Server` and `perldoc LibTmux::Async::Transport`. Persistent control and streaming APIs remain deferred; see the [architecture](https://github.com/libtmux/libtmux-perl/blob/master/docs/architecture.md).