Peta::FFI::EventSource#

events from a C library’s own threads, drained on Perl’s

SYNOPSIS#

use Peta::FFI;
use Peta::FFI::EventSource;

my $lib = Peta::FFI::dlopen('/opt/example/lib/libexample.so');
Peta::FFI::handle_type('Example::Monitor', $lib, 'example_monitor_free');
my $open = Peta::FFI::function($lib, 'example_monitor_open', '(p)<Example::Monitor>');
my $fd   = Peta::FFI::function($lib, 'example_monitor_fd',   '(<Example::Monitor>)i32');
my $next = Peta::FFI::function($lib, 'example_monitor_next', '(<Example::Monitor>)s',
                               free => 'example_string_free');

my $source = Peta::FFI::EventSource->new($open->('block'), fd => $fd, next => $next);

# With AnyEvent (and so with EV underneath):
my $guard = $source->watch(sub { handle_event($_) for @_ });
AnyEvent->condvar->recv;

# With Mojo::IOLoop, which takes filehandles:
Mojo::IOLoop->singleton->reactor->io($source->fh, sub { handle_event($_) for $source->drain })
    ->watch($source->fh, 1, 0);

# With a loop of your own:
vec(my $rin = '', $source->fd, 1) = 1;
while (select(my $rout = $rin, undef, undef, undef)) {
    handle_event($_) for $source->drain;
}

DESCRIPTION#

A library that produces events on threads of its own - a device monitor, a netlink listener - must not call into Perl from those threads: Perl runs on the interpreter’s thread only, and a Peta::FFI closure called from anywhere else stops the program. The library queues the events instead, and signals that there are some through a file descriptor. This module watches the descriptor and, when it reads ready, takes the events one by one until the queue is empty.

One source is one handle of the library’s: a library with several independent event streams opens a handle for each, and each becomes a source of its own.

A Peta::FFI::Pool, which runs C functions on threads of its own, is such a source too, with its own fd and next, and so is a Peta::FFI::Queue, where the calls of queued closures wait; their events are hash references:

my $source = Peta::FFI::EventSource->new($pool,
    fd => \&Peta::FFI::Pool::fd, next => \&Peta::FFI::Pool::next);

The library’s half#

The library provides, for its source handle type:

  • a file descriptor function, int fd(source *)

    returning a descriptor that reads ready while events are queued - an eventfd or the read end of a pipe. Peta::FFI calls it once, in new.

  • a next-event function, char *next(source *)

    returning the oldest queued event as a string the caller frees, or NULL when the queue is empty. Declared with the s result type and the library’s free function, so Perl receives a copy and the library’s string is released at once.

  • a destructor

    declared with handle_type, which stops the library’s thread and closes the descriptor. It runs once, when the last reference to the handle goes; the source holds one.

Clearing the descriptor is the library’s job: when next returns NULL, the library resets its eventfd or empties its pipe. Peta::FFI only waits for readability, level-triggered, and drains until NULL, so it never needs to know which kind of descriptor it is. A full queue is the library’s to report, typically as an event of its own saying how many were lost; such an event passes through like any other.

METHODS#

  • new($handle, fd => $fd_function, next => $next_function)

    The source for $handle. Both functions are code references from Peta::FFI::function taking the handle; $fd_function is called once, here. Croaks when the library returns no descriptor.

  • fd

    The library’s descriptor, for an event loop that takes descriptor numbers (EV, select). Never close it: it is the library’s.

  • fh

    A read filehandle on a duplicate of the descriptor, for an event loop that takes filehandles (Mojo::IOLoop, IO::Select). It becomes readable exactly when the descriptor does, and closing it - or letting it go - closes only the duplicate, so it may outlive the source. The same filehandle is returned on every call.

  • drain

    The queued events, oldest first, each as next returned it - the string a library returned, a pool’s hash reference; the empty list when there are none.

  • watch($callback)

    Watches the descriptor with AnyEvent (loaded on first use) and calls $callback with the drained events whenever there are some. Returns the watcher: the watch lasts as long as it is referenced.

  • handle

    The library handle the source was made for.

SEE ALSO#

Peta::FFI - function, handle_type and the s result type.

Peta::FFI::Pool - C functions run on worker threads, their calls reported as events.

Peta::FFI::Queue - closures a library calls on its own threads, their calls queued for the event loop.