NAME

DBIO::Forked::Storage - Fork-based async storage skeleton — make any sync DBIO driver async

VERSION

version 0.900001

DESCRIPTION

Fork-based async storage backend subclassing core DBIO::Storage::Async. It makes any sync DBIO driver async without an async-capable client and without an event loop, via fork()-per-query (Model A, see DBIO::Forked): the child reconnects the sync driver fresh from the stored DBI-form connect info, runs the driver's ordinary sync CRUD (no SQL is re-implemented here), serializes the result rows back over a pipe with Storable, and exits; the parent returns a DBIO::Forked::Future bound to the pipe read fd.

METHODS

new

my $storage = DBIO::Forked::Storage->new($schema);

Construct the fork-based async backend for $schema. The schema reference is weakened (the schema owns the storage, not the other way round). Connect info is supplied separately via "connect_info".

future_class

Returns 'DBIO::Forked::Future' -- the loop-free, pipe-backed Future this backend hands out.

connect_info

$storage->connect_info([ $dsn, $user, $pass, \%attrs, \%dbio_opts ]);

Store the DBI-form connect info verbatim (the core resolver passes the sync storage's _connect_info straight through). Returns the stored value.

Informational in Model A: the forked child runs the inherited sync storage's own CRUD, and that storage reconnects itself in the child via its inherited fork handling (see docs/adr/0002). This stored connect info is therefore not consumed on the query path -- it is kept as latent diagnostics / raw material for a possible future "fresh storage" variant.

select_async

my $future = $storage->select_async($source, $select, $where, $attrs);

Run a SELECT in a forked child and return a DBIO::Forked::Future of the result rows.

select_single_async

Like "select_async" but resolves to the first row only.

insert_async

my $future = $storage->insert_async($source, \%vals);

update_async

my $future = $storage->update_async($source, \%vals, \%where);

delete_async

my $future = $storage->delete_async($source, \%where);

txn_do_async

my $future = $storage->txn_do_async(sub { ... }, @args);

Run a whole transaction in a single forked child. Mechanically identical to the CRUD path: the child runs the inherited sync storage's ordinary txn_do($body, @args) (core wraps BEGIN / body / COMMIT|ROLLBACK + retry in a BlockRunner), so BEGIN, the body and COMMIT/ROLLBACK all execute on the child's own freshly-reconnected sync connection. The $body closure does not cross the process boundary as data -- it is inherited through fork() from parent memory and runs in the child; it is called as $body->(@args) and receives no storage argument (it closes over $schema/$storage).

Two limits the caller must respect:

  • The body's return value must be Storable-serializable -- scalars or plain array/hash refs of scalars. It is frozen back over the pipe, so a live Row/ResultSet object (which drags its storage and live DB handle along) cannot cross: such a return surfaces as a failed Future with a clear message from "get" (see "_serialization_error"). Return plain data (ids, hashrefs of column values), not live result objects.

  • The body must use sync operations only. Calling a *_async method inside the body would fork again from within the child. Use the ordinary sync CRUD ($schema->resultset(...)->...) inside a txn_do_async body.

ACTIVATION

Loading this module (or DBIO::Forked) registers a generic forked async mode on the core base storage class (ADR 0030):

DBIO::Storage::DBI->register_async_mode( forked => 'DBIO::Forked::Storage' );

so every DBIO driver inherits it. A connection then opts in per-connection, at connect time:

my $schema = MyApp::Schema->connect($dsn, $user, $pass, { async => 'forked' });

Nothing is auto-wired: a connection opened without { async => 'forked' } stays fully synchronous (its *_async methods croak). When the mode is chosen, the core resolver in DBIO::Storage::DBI constructs __PACKAGE__->new($schema) as the embedded async backend and feeds it the sync storage's DBI-form connect info via "connect_info"; that backend then answers the six *_async methods.

STATUS

All six *_async entry points are implemented (with their sync ->get wrappers): each forks per query and roundtrips through "_run_forked" and DBIO::Forked::Future. "txn_do_async" runs the whole transaction in one child; see the two caller-facing limits documented on it.

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.