NAME
DBIO::Async::Storage - Future::IO transport backend for DBIO async storage
VERSION
version 0.900001
DESCRIPTION
The future_io async backend (ADR 0030): one loop-agnostic transport that drives a driver's own non-blocking DBD binding through Future::IO. The event loop is chosen by installing a Future::IO::Impl::* adapter, not by picking a distribution.
The Model-B orchestration -- connect-info normalisation, the CRUD runner ("_run_crud" in DBIO::Storage::Async with its pooled and pinned runners), INSERT returned-columns mapping, "txn_do_async" in DBIO::Storage::Async bracketing through the generic DBIO::Storage::Async::TransactionContext, and the "pipeline" in DBIO::Storage::Async scaffold -- is inherited concretely from DBIO::Storage::Async (ADR 0030 §4). This class supplies only the transport: the Future::IO query execution ("_query_async" / "_query_async_pinned" over "_await_query_result"), the Future implementation ("future_class") and the connection pool ("pool"), plus the DB-specific seam hooks a concrete DBD subclass fills in.
METHODS
future_class
Returns 'Future' -- this backend uses Future from CPAN.
transport_capabilities
my @caps = DBIO::Async::Storage->transport_capabilities; # ('on_connect_replay')
Class method (see "transport_capabilities" in DBIO::Storage::Async). Declares the named transport capabilities this future_io transport provides, so "_async_storage" in DBIO::Storage::DBI lets an async extension layer that declares a matching required_transport_capabilities compose onto it (and croaks on a shortfall rather than silently dropping the feature).
This transport declares on_connect_replay: its pool (DBIO::Async::Pool, a DBIO::Storage::PoolBase) drives core's "_setup_pool_connection" in DBIO::Storage::Async on every freshly-spawned connection, and the { dbh => $dbh } connection shape is handled by the base _run_pool_connect_statement -- so each pooled async connection replays the owning sync storage's on_connect_do/on_connect_call (and the on_disconnect_* actions at shutdown) end to end (karr #68 seam). A concrete DBD subclass inherits this obligation for free.
pool
Returns the DBIO::Async::Pool connection pool, created lazily on first access. Fed the per-spawn conninfo_provider when an AccessBroker is attached (see "_conninfo_provider" in DBIO::Storage::Async), otherwise the static conninfo.
_async_broker_conninfo
my $conninfo = $storage->_async_broker_conninfo($mode);
AccessBroker seam (see "ACCESSBROKER CONSUMPTION" in DBIO::Storage::Async): return one fresh, storage-native conninfo for a single new pool connection, built from the current broker credentials via the inherited normalisation.
_query_async
Transport override: execute a query on a freshly-acquired pooled connection, releasing it once the Future is ready.
Receives SQL in the sql_maker ?-placeholder dialect (the core #70 seam contract) and shapes it into the driver's own dialect internally, up front, through "_transform_sql" before the query reaches the wire -- callers no longer pre-shape. The ?->$N-style rewrite is idempotent on already-shaped SQL (no bare ? left to touch), which is what keeps the transition window safe while core still shapes at its own call site.
_query_async_pinned
Transport override: like "_query_async" but runs on the supplied pinned connection and does not release it -- used for queries inside a pinned transaction. Shapes the incoming ?-dialect SQL internally through "_transform_sql" exactly as "_query_async" does.
_await_readable
my $future = $storage->_await_readable($conn);
Returns a Future that becomes ready once the connection's socket is readable. The single seam through which this transport touches Future::IO: a concrete driver's "_collect_result" that must wait for more of a result also calls this rather than re-implementing the fd wrapping. The filehandle poll needs is supplied by "_conn_poll_fh".
_conn_poll_fh
my $fh = $storage->_conn_poll_fh($conn);
The stable poll filehandle for $conn, created lazily on first use and cached on the connection for its whole life. Future::IO->poll requires a real filehandle, but a driver's "_conn_fileno" hands back the raw integer socket fd (e.g. DBD::Pg's pg_socket). The '+<&' open mode dups that fd (via dup(2)) into an independent filehandle, so closing this handle never touches the driver's own socket.
The dup is cached per connection for a correctness reason, not merely for speed. Future::IO watches the filehandle and, once the poll resolves, unwatches it by its fileno -- but its impls mark the Future done before they unwatch. A fresh-dup-per-poll handle closed the moment its poll resolved would already be gone when the loop unwatches: $fh->fileno returns undef, the loop's watch table is corrupted, and the freed fd -- reused by the next dup -- collides with the stale watch (observed as pg_ready: No asynchronous query is running on the following query). Reusing one stable handle for every poll on a connection keeps the fileno valid across the whole watch/unwatch cycle. The dup fd dies with the connection it mirrors, when $conn is torn down.
Cached on the connection hashref under the reserved key _dbio_async_poll_fh; a driver whose connection is not a hashref must supply its own caching.
_await_conn_ready
my $future = $storage->_await_conn_ready($conn);
Returns a Future that resolves to $conn once the connection is ready for queries. Uses "_conn_ready" to check readiness and "_await_readable" to wait on the fd from "_conn_fileno" when not yet ready.
_await_query_result
my $future = $storage->_await_query_result($conn, $sql, $bind);
Submits the query via "_submit_query", waits for the connection socket to become readable via "_await_readable", and collects the result via "_collect_result". Returns a Future resolving to the result rows.
SEAM HOOKS
Overridden by a concrete DBD subclass; each croaks until provided.
Query execution / connection lifecycle
"_submit_query" -- send query bytes to the wire (non-blocking)
"_collect_result" -- read the result from the wire
"_conn_ready" -- is the connection ready for queries?
"_conn_fileno" -- socket fd for the Future::IO watcher seam
"_create_pool_connection" / "_shutdown_pool_connection" -- pool connection lifecycle
SQL / connect-info / transaction shaping
The SQL-shaping seams (sql_maker_class, _transform_sql, _post_insert_sql) and the pipeline seams (_pipeline_enter/_pipeline_sync/_pipeline_exit) are inherited as croaking seams from DBIO::Storage::Async. This transport additionally requires:
"_normalize_conninfo" -- DB-specific connect-info conversion
"_txn_context_class" / "_txn_conn_accessor" -- the transaction-context class and the pinned-connection accessor key
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.