# 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).