```{index} single: Peta::FFI::Pool ``` # Peta::FFI::Pool ```{pperl-module-badges} Peta::FFI::Pool ``` Run C functions on worker threads, so that a call which hangs cannot hang the program. Some C calls can block inside the kernel for as long as it likes: `statfs`, `stat` or `open` on an NFS, CIFS or CephFS mount whose server is gone waits uninterruptibly, and no signal or alarm gets the thread back. perl5 programs fork a child with a timeout for every such probe. A pool runs the call on a thread of its own instead, and reports how it went as an event: the event loop stays free, and a hung mount costs one parked thread, not a process per probe. `Peta::FFI::Pool->new(threads => 4)` makes a pool of at most four threads. `$pool->call($function, $deadline, @args)` runs a function prepared with [Peta::FFI](../FFI)'s `function` or `attach` with `@args`, and returns the call's id at once. When the call returns, an event `{ id => $id, state => 'done', result => ..., errno => ... }` is queued, and what the function wrote through its arguments (a `P` buffer, an `*{Class}` record, an `*i`) has been written into the caller's scalars by the time the event is taken. When `$deadline` seconds pass first, the event is `{ id => $id, state => 'timeout' }` instead. The thread stays with the call until it returns, since nothing can cancel it, and what the call then produces is thrown away: the caller's scalars are left alone. A pool never queues a call. When all its threads are taken, by calls running or by calls hung past their deadline, the call's event is `{ id => $id, state => 'busy' }`, at once. The events are taken with `next`, oldest first, and `fd` reads ready while there are some, so the pool is a [Peta::FFI::EventSource](EventSource) for any event loop. ## Synopsis ```perl use Peta::FFI; use Peta::FFI::Pool; use Peta::FFI::EventSource; my $libc = Peta::FFI::dlopen("libc.so.6"); my $statfs = Peta::FFI::function($libc, "statfs", "(pP)i"); my $pool = Peta::FFI::Pool->new(threads => 4); my $source = Peta::FFI::EventSource->new($pool, fd => \&Peta::FFI::Pool::fd, next => \&Peta::FFI::Pool::next); my $st = "\0" x 120; # struct statfs, x86-64 my $probe = $pool->call($statfs, 2, "/mnt/share", \$st); my $guard = $source->watch(sub { for my $event (@_) { next unless $event->{id} == $probe; if ($event->{state} eq 'done') { print $event->{result} == 0 ? "mounted\n" : "error $event->{errno}\n"; } else { print "/mnt/share does not answer\n"; # timeout, or busy } } }); ``` ## What a call takes A call's arguments are read and copied before `call` returns, so the thread never reads a Perl value, and changing them meanwhile changes nothing. A function can take numbers, `b`, `o`, `p`, `P`, `*T` of a number or `o`, handles and records (`{Class}` and `*{Class}`). It can return `v`, a number other than a long double or a complex one, `b`, `o`, `p`, `s`, a handle or a record. Arrays, the out pointers and arrays of `*T[...]`, `*`, `*s` and `*P[...]`, and closures are refused: a call that needs them runs on Perl's own thread, with `$function->(@args)`. A closure passed as an `o` still cannot be called on a pool's thread, which stops the program as any thread of a library's own does. A handle passed to a call is kept alive until the call returns, even past its deadline, so its destructor cannot free what the thread is still using. A handle a late call returns is destroyed, and a string it returns freed; a plain number is lost, so a file descriptor a late `open` returns stays open. ## Threads, signals and fork A pool's threads start when a call needs one, up to `threads`, and block every signal, which Perl's thread handles. They belong to the process that made the pool: after `fork`, the child makes a pool of its own, and `call` croaks on the parent's. ## Functions ### Pools #### `new` `Peta::FFI::Pool->new(threads => $n)`: a pool of at most `$n` threads (4 when not given), none started yet. #### `call` `$pool->call($function, $deadline, @args)`: run `$function`, a code reference from [Peta::FFI](../FFI)'s `function` or `attach`, with `@args` on one of the pool's threads, and return the call's id. Its event is `done` when it returns, `timeout` when `$deadline` seconds (a fraction is fine; `undef` is no deadline) pass first, and `busy` at once when every thread is taken. ### Events #### `fd` `$pool->fd`: a descriptor that reads ready while events are queued, for an event loop. Never close it: it is the pool's. #### `next` `$pool->next`: the oldest event, a hash reference, or undef when there is none. `id` is the call's, `state` is `done`, `timeout` or `busy`, and a `done` event has the function's `result` and the `errno` the call left; what the function wrote through its arguments is in the caller's scalars when it is returned.