NAME

DBIO::Forked::Future - Loop-free, pipe-backed Future for DBIO::Forked

VERSION

version 0.900001

DESCRIPTION

A loop-free Future implementation over a pipe read fd, with no Future, no Future::IO and no event loop. It is what DBIO::Forked::Storage hands back from its *_async methods.

It fulfils both the minimal core DBIO::Future contract (then / catch / get / is_ready / is_failed) AND the fuller, DBIO::Future::Immediate-compatible surface (the done / fail / needs_all class constructors, plus and_then), so it works as a live future_class for core's ResultSet async helpers (all_async / first_async / single_async / count_async / create_async), which build futures via $storage->future_class->done(@rows) and ->fail($err).

The model is deliberately simple, matching Model A (one forked child per query):

  • is_ready is an EOF-clean, non-blocking drain. IO::Select->can_read(0) goes true as soon as the child writes anything, not when it is done, so a bare peek would be premature. Instead is_ready reads whatever is available without blocking and accumulates it across calls; it returns true only once the child closes its write end (sysread returns 0 = EOF), at which point the blob is thawed, cached and the child reaped.

  • get blocks until EOF, Storable-thaws the accumulated blob, waitpid-reaps the child, and returns the rows -- or re-throws the child's error. It is idempotent: the result is cached, so a second get does not read or reap again.

  • then / catch / and_then compose lazily. A derived future stores its callbacks and a reference to its source; it resolves (running the callback synchronously, since the source is by then ready) the first time it is forced via get, or via is_ready/is_failed once the source is ready. No callback runs before its source has resolved, and nothing blocks unless get is called.

  • done / fail / needs_all are immediate class constructors (no fork): a settled future is a pre-resolved leaf with no child, so it flows through the same resolution machinery. needs_all blocks on each input via get in turn (the children already run in parallel).

METHODS

new

my $future = DBIO::Forked::Future->new(read_fh => $fh, pid => $pid);

Construct a leaf future bound to the pipe read handle read_fh and the forked child pid. Both are optional so the object can be built before the fork wiring is in place.

done

my $f = DBIO::Forked::Future->done(@values);

Class method: an immediately-resolved successful future carrying @values, with no fork and no pipe. This is the constructor core's ResultSet async helpers call as $storage->future_class->done(@rows).

fail

my $f = DBIO::Forked::Future->fail($error);

Class method: an immediately-resolved failed future; "get" re-throws $error. The ResultSet async helpers call $storage->future_class->fail($err).

needs_all

my $f = DBIO::Forked::Future->needs_all(@futures);

Class method: resolves once ALL @futures have resolved, collecting their values in order; fails as soon as any one fails, with that future's error. Under Model A the forked children already run in parallel; needs_all just blocks on each in turn via "get". Because collection is serial and each child holds its result in the pipe until read, a batch of many large results can stall on the pipe-buffer ceiling (see docs/adr/0003); fine for modest result sets.

then

my $g = $future->then(sub { my @result = @_; ... });

Success continuation. Returns a new DBIO::Forked::Future that, when forced, runs the callback with this future's resolved values and resolves to the callback's return values. On failure the callback is skipped and the failure propagates (unless an optional second $on_fail argument is given).

catch

my $g = $future->catch(sub { my $error = shift; ... });

Failure continuation. Returns a new DBIO::Forked::Future; on failure it runs the callback with the error and resolves to its return values, on success it passes the values through unchanged.

and_then

my $g = $future->and_then(sub { my @r = @_; return DBIO::Forked::Future->done(...) });

Like "then", but the callback is expected to return a future, which is flattened into the chain (no future-wrapping-a-future). Mirrors "and_then" in DBIO::Future::Immediate. (then already flattens a returned future, so and_then is then with future-returning intent made explicit.)

get

my @result = $future->get;

Block until resolved, then return the resolved values -- or croak with the child's error if it failed. Idempotent. Like "get" in DBIO::Future::Immediate, in scalar context it returns the first value (not the count).

is_ready

if ($future->is_ready) { ... }

Non-blocking. True once resolved (the child has finished and its blob been read, or the source of a derived future has resolved). Never blocks.

is_failed

if ($future->is_failed) { ... }

Non-blocking. True once resolved and the result is an error.

STATUS

Full future_class surface implemented: the minimal DBIO::Future contract plus the DBIO::Future::Immediate-compatible done / fail / needs_all / and_then. "then" / "catch" / "and_then" flatten a single returned DBIO::Forked::Future (chaining) but do not otherwise inspect nested structures.

AUTHOR

DBIO Authors

COPYRIGHT AND LICENSE

Copyright (C) 2026 DBIO Authors

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