NAME

EV::Future - Minimalist high-performance async control flow for EV

SYNOPSIS

use EV;
use EV::Future;

my @w;
parallel([
    sub { my $done = shift; push @w, EV::timer 0.1, 0, sub { $done->() } },
    sub { my $done = shift; push @w, EV::timer 0.2, 0, sub { $done->() } },
], sub { print "all done\n" });

parallel_limit([
    sub { my $done = shift; push @w, EV::timer 0.1, 0, sub { $done->() } },
    sub { my $done = shift; push @w, EV::timer 0.2, 0, sub { $done->() } },
    sub { my $done = shift; push @w, EV::timer 0.1, 0, sub { $done->() } },
], 2, sub { print "all done (max 2 in-flight)\n" });

series([
    sub { my $done = shift; $done->() },
    sub { my $done = shift; $done->() },
], sub { print "all done in order\n" });

race([
    sub { my $done = shift; push @w, EV::timer 0.1, 0, sub { $done->("a") } },
    sub { my $done = shift; push @w, EV::timer 0.2, 0, sub { $done->("b") } },
], sub { my $winner = shift; print "winner: $winner\n" });

EV::run;

DESCRIPTION

Four control-flow primitives (parallel, parallel_limit, series, race), implemented in XS for minimal overhead. All four are exported by default.

Each task is a coderef that receives a single done callback as its only argument; the task must invoke done exactly once to mark completion.

If \@tasks is empty, final_cb fires immediately. Non-coderef elements in \@tasks are treated as no-op tasks that complete instantly.

Safe vs unsafe mode

Each function takes an optional trailing $unsafe flag. In safe mode (the default), each dispatch is wrapped in G_EVAL, every task gets its own done CV, and double-calls are silently dropped. Unsafe mode skips G_EVAL and reuses a single shared CV, roughly doubling throughput at the cost of:

  • Exceptions from a task bypass cleanup and leak the internal context.

  • Double-calling done corrupts the completion counter, which may invoke final_cb before all tasks have actually finished.

Use unsafe mode only when tasks are well-behaved and performance is critical.

FUNCTIONS

parallel(\@tasks, \&final_cb, [$unsafe])

Dispatch every task immediately; call final_cb once each task has invoked its done callback.

parallel_limit(\@tasks, $limit, \&final_cb, [$unsafe])

Dispatch tasks with at most $limit in flight at any time. $limit is clamped to 1..scalar(@tasks): $limit == 1 degenerates to series, $limit >= @tasks degenerates to parallel.

There is no cancellation mechanism; all dispatched tasks must complete. The truthy-done cancellation supported by series does not apply here.

series(\@tasks, \&final_cb, [$unsafe])

Run tasks sequentially; each task starts only after the previous calls its done. To cancel the series and skip remaining tasks, pass a true value to done:

series([
    sub { my $d = shift; $d->(1) },        # cancel here
    sub { die "never reached" },
], sub { print "finished early\n" });

Cancellation works in both safe and unsafe modes.

race(\@tasks, \&final_cb, [$unsafe])

Dispatch every task; call final_cb with the arguments passed to the first done invocation. Subsequent done calls (whether from the winning task or losers) are silently ignored.

Losing tasks continue to run; EV::Future does not cancel their EV watchers (it didn't create them). To tear losers down, hold their watchers in a shared lvalue and clear it from final_cb:

my @w;
race([
    sub { my $d = shift; push @w, EV::timer 0.1, 0, sub { $d->("a") } },
    sub { my $d = shift; push @w, EV::timer 0.2, 0, sub { $d->("b") } },
], sub { my $winner = shift; @w = () });

Non-coderef elements in \@tasks count as instantly-completed winners (with no arguments) and short-circuit dispatch.

BENCHMARKS

1000 synchronous tasks, 5000 iterations (bench/comparison.pl):

--- PARALLEL (iterations/sec) ---
EV::Future (unsafe)          4,386
EV::Future (safe)            2,262
AnyEvent::cv (begin/end)     1,027
Future::XS::wait_all           982
Promise::XS::all                32

--- PARALLEL LIMIT 10 (iterations/sec) ---
EV::Future (unsafe)          4,673
EV::Future (safe)            2,688
Future::Utils::fmap_void       431

--- SERIES (iterations/sec) ---
EV::Future (unsafe)          5,000
AnyEvent::cv (stack-safe)    3,185
EV::Future (safe)            2,591
Future::XS (chain)             893
Promise::XS (chain)            809

SEE ALSO

EV, Future::XS, Promise::XS

AUTHOR

vividsnow

LICENSE

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.