NAME
Future::Selector - manage a collection of pending futures
SYNOPSIS
use Future::AsyncAwait;
use Future::IO;
use Future::Selector;
use IO::Socket::IP;
my $selector = Future::Selector->new;
my $listensock = IO::Socket::IP->new(
LocalHost => "::1",
LocalPort => "8191",
Listen => 1,
);
$selector->add(
data => "listener",
gen => sub { Future::IO->accept( $listensock ) },
);
while(1) {
my @ready = await $selector->select;
...
}
DESCRIPTION
Objects in this class maintain a collection of pending Future
instances, and manage the lifecycle of waiting for their eventual
completion. This provides a central structure for writing asynchronous
event-driven programs using Future and Future::IO-based logic.
When writing an asynchronous Future-based client, often the program can
be structured similar to a straight-line synchronous program, where at
any point the client is just waiting on sending or receiving one
particular message or data-flow. It therefore suffices to use a simple
call/response structure, perhaps written using the async and await
keywords provided by Future::AsyncAwait.
In contrast, a server program often has many things happening at once.
It will be handling multiple clients simultaneously, as well as waiting
for new client connections and any other internal logic it requires to
provide data to those clients. There is not just one obvious pending
future at any one time; there could be several that all need to be
monitored for success or failure.
A Future::Selector instance helps this situation, by storing an entire
set of pending futures that represent individual sub-divisions of the
work of the program (or a part of it). As each completes, the selector
instance informs the containing code so it can continue to perform the
work required to handle that part, perhaps resulting in more future
instances for the selector to manage.
Program Structure
As per the SYNOPSIS example, a typical server-style program would
probably be structured around a while(1){} loop that repeatedly awaits
on the next select future from the selector instance, looking for the
next thing to do. The data values stored with each future and returned
by the select method can be used to help direct the program into
working out what is going on. For example, string names or object
instances could help identify different kinds of next step.
use v5.36;
...
$selector->add(
data => "listener",
gen => sub { Future::IO->accept( $listensock ) },
);
while(1) {
foreach my ( $data, $f ) ( await $selector->select ) {
if( $data eq "listener" ) {
# a new client has been accept()ed. should now set up handling
# for it in some manner.
my $sock = await $f;
my $clientconn = ClientConnection->new( fh => $sock );
$selector->add( data => $clientconn, f => $clientconn->run );
}
elsif( $data isa ClientConnection ) {
# an existing connection's runloop has terminated. should now
# handle that in whatever way is appropriate
...
}
...
}
}
Alternatively, if each stored future instance already performed all of
the work required to handle it before it yields success, there may be
nothing for the toplevel application loop to do other than repeatedly
wait for things to happen.
$selector->add(
data => undef, # ignored
gen => async sub {
my $sock = await Future::IO->accept( $listensock );
my $clientconn = ClientConnection->new( fh => $sock );
$selector->add( data => undef, f => $clientconn->run );
}
);
await $selector->select while 1;
Failure propagation by the select method here ensures any errors
encountered by individual component futures are still passed upwards
through the program structure, ultimately ending at the toplevel if
nothing else catches it first.
Comparison With select(2), epoll, etc..
In some ways, the operation of this class is similar to system calls
like select(2) and poll(2). However, there are several key differences:
* Future::Selector stores high-level futures, rather than operating
directly on system-level filehandles. As such, it can wait for
application-level events and workflow when those things are
represented by futures.
* The main waiting call, "select", is a method that returns a future.
This could be returned from some module or component of a program, to
be awaited on by another outer Future::Selector instance. The
application is not limited to exactly one as would be the case for
blocking system calls, but can instead create a hierarchical
structure out of as many instances as are required.
* Once added, a given future remains a member of a Future::Selector
instance until it eventually completes; which may require many calls
to the select method (or indeed, it may never complete during the
lifetime of the program, for tasks that should keep pending
throughout). In this way, the object is more comparable to persistent
system-level schedulers like Linux's epoll or BSD's kqueue
mechanisms, than the one-shot nature of select(2) or poll(2)
themselves.
METHODS
add
$selector->add( data => $data, f => $f );
Adds a new future to the collection.
After the future becomes ready, the currently-pending select future (or
the next one to be created) will complete. It will yield the given data
and future instance if this future succeeded, or fail with the same
failure if this future failed. At that point it will be removed from
the stored collection. If the item future was cancelled, it is removed
from the collection but otherwise ignored; the select future will
continue waiting for another result.
$selector->add( data => $data, gen => $gen );
$f = $gen->();
Adds a new generator of futures to the collection.
The generator is a code reference which is used to generate a future,
which is then added to the collection similar to the above case. Each
time the future becomes ready, the generator is called again to create
another future to continue watching. This continues until the generator
returns undef.
select
( $data1, $f1, $data2, $f2, ... ) = await $selector->select();
Returns a future that will become ready when at least one of the stored
futures is ready. It will yield an even-sized list of pairs, giving the
associated data and original (now-completed) futures that were stored.
If you are intending to run the loop indefinitely, be careful not to
write code such as
1 while await $selector->select;
because in scalar context, the awaited future will yield its first
value, which will be the data associated with the first completed
future. If that data value was false (such as undef) then the loop will
stop running at that point. Generally in these sorts of situations you
want to use "run" or "run_until_ready".
run
await $selector->run();
Since version 0.02.
Returns a future that represents repeatedly calling the "select" method
indefinitely. This will not return, except that if any of the contained
futures fails then this will fail the same way.
This is most typically used at the toplevel of a server-type program,
one where there is no normal exit condition and the program is expected
to remain running unless some fatal error happens.
run_until_ready
@result = await $selector->run_until_ready( $f );
Since version 0.02.
Returns a future that represents repeatedly calling the "select" method
until the given future instance is ready. When it becomes ready (either
by success or failure) the returned future will yield the same result.
If the returned future is cancelled, then $f itself will be cancelled
too. This will not cancel a concurrently-pending select or run call,
however.
The given future will be added to the selector by calling this method;
you should not call "add" on it yourself first.
This is typically used in client or hybrid code, or as a nested
component of a server program, which needs to wait on a result while
also performing other background tasks.
Remember that since this method itself returns a future, it could
easily serve as the input to another outer-level selector instance.
TODO
* Convenience ->add_f / ->add_gen
* Configurable behaviour on component future failure
AUTHOR
Paul Evans <leonerd@leonerd.org.uk>