NAME
DBIO::Storage::PoolBase - Shared connection pool mechanics for async DBIO drivers
VERSION
version 0.900001
SYNOPSIS
package DBIO::PostgreSQL::Async::Pool;
use base 'DBIO::Storage::PoolBase';
sub _create_connection {
my ($self, $conninfo) = @_;
return EV::Pg->new(
conninfo => $conninfo,
on_connect => sub {},
on_error => $self->{on_error},
);
}
sub _shutdown_connection { $_[1]->finish }
sub _transform_conninfo { conninfo_string($_[1]) }
See t/storage/pool_base.t for a runnable example.
DESCRIPTION
Concrete implementation of the DBIO::Storage::Pool contract hosting the pool mechanics shared by all async drivers: idle-pool handling, capacity-bounded connection creation, the waiter queue and shutdown.
Drivers subclass this and supply only the engine seam:
"_create_connection" -- build one driver connection (required)
"_shutdown_connection" -- close one driver connection (optional, defaults to a no-op)
"_transform_conninfo" -- adapt the stored connect info into whatever shape the driver's connection constructor expects (optional, defaults to passing it through unchanged)
"_connection_ready_future" -- gate "acquire" until a connection is actually ready for queries (optional for synchronous pools, where the default no-op is correct; required for every async transport, see the method for why and for the
dbio-mysql-ev karr #20footgun)"future_class" -- the Future implementation used for "acquire" (optional, defaults to Future)
METHODS
new
my $pool = Driver::Pool->new(
conninfo => 'dbname=myapp',
size => 10,
on_error => sub { warn $_[0] },
);
Requires conninfo or conninfo_provider (a coderef returning fresh connect info per connection). size caps the pool (default 5). future_class overrides the Future implementation per instance.
storage (optional) is the owning DBIO::Storage::Async, held weakly. When given, the pool replays that storage's on_connect_do / on_connect_call (and the on_disconnect_* counterparts) against each physical connection at spawn / shutdown (karr #68).
future_class
The Future implementation backing "acquire". Defaults to Future; override in a subclass or pass future_class to "new".
acquire
Returns a connection wrapped in a Future. Hands out an idle connection if one is available, otherwise creates a new connection if the pool has capacity; if all connections are busy and the pool is at max size, queues the request and returns a pending Future that resolves on the next "release".
Whichever of those three paths supplies the connection, acquire ALWAYS chains the result through the "_connection_ready_future" seam before it resolves (karr #75). For synchronous pools that seam is a no-op — the connection is usable the instant it exists — so the returned Future is ready immediately and behaviour is unchanged. For async transports whose connection is not usable until a background connect completes, the seam is the single place where the Future is held pending until the connection is actually ready; see "_connection_ready_future".
_connection_ready_future
sub _connection_ready_future { my ($self, $conn) = @_; ... }
Readiness seam, chained by "acquire" around every connection it hands out — freshly spawned, reused-idle, or handed to a queued waiter. Returns a Future that resolves to $conn once the connection is actually ready to run queries.
The default implementation is a safe no-op for synchronous pools:
sub _connection_ready_future { $_[0]->future_class->done($_[1]) }
A DBI / synchronous connection is usable the instant its constructor returns, so an immediately-done Future is correct and "acquire" stays ready-at-once. Behaviour for every existing synchronous pool is therefore unchanged.
Async transports MUST override this. A backend whose connection is not usable when its constructor returns — every event-loop transport: EV::Pg, EV::MariaDB, future_io, ... — is still connecting in the background when the handle first exists. If such a driver leaves this seam at its default, "acquire" hands out a live-looking but unconnected handle and the very first bound query on a cold pool dies not connected. This is not a theoretical edge: dbio-mysql-ev shipped exactly this bug — its _create_connection wired on_connect => sub {}, a pure no-op, with no readiness tracking, and the symptom was papered over with test-harness pre-warming rather than fixed in the driver (dbio-mysql-ev karr #20). The correct override returns a per-connection Future that resolves from the transport's on_connect callback and fails from its on_error.
The default deliberately does NOT auto-detect the async case — it cannot know whether a given transport's constructor connects synchronously — so forgetting the override stays possible by design. What the base does provide is that the responsibility now lives at ONE documented seam, and the bookkeeping for the common "resolve a per-connection Future from a connect callback" pattern is supplied by "_register_connection_ready", "_connection_ready_lookup" and "_clear_connection_ready", so an override only has to wire on_connect / on_error onto a Future instead of building the side table from scratch. A typical async override:
sub _connection_ready_future {
my ($self, $conn) = @_;
return $self->future_class->done($conn) if $conn->is_connected;
return $self->_connection_ready_lookup($conn)
|| $self->future_class->done($conn);
}
_register_connection_ready
$self->_register_connection_ready($conn, $ready_future);
Bookkeeping primitive for the "_connection_ready_future" pattern. Stores a per-connection readiness Future keyed by Scalar::Util::refaddr($conn), so an async "_create_connection" can hand the Future's done / fail to the transport's on_connect / on_error callbacks and have "_connection_ready_future" find it again by connection. Returns the Future.
_connection_ready_lookup
my $ready = $self->_connection_ready_lookup($conn);
Return the readiness Future registered for $conn via "_register_connection_ready", or undef if none (e.g. a synchronous pool that never registered one). An async "_connection_ready_future" override typically returns $self->_connection_ready_lookup($conn) || $self->future_class->done($conn).
_clear_connection_ready
$self->_clear_connection_ready($conn);
Drop $conn's readiness Future from the side table. Called for you by "shutdown" for every pooled connection, so an async "_shutdown_connection" override never has to clean the table up itself; also exposed for drivers that retire a single connection outside shutdown. A no-op when no readiness table exists.
acquire_txn
Acquire a connection pinned for exclusive transaction use. Same as "acquire" but the connection will not be released back to the idle pool until explicitly released.
release
$pool->release($conn);
Return a connection to the idle pool. If waiters are queued, hands the connection straight to the oldest waiter instead.
size
Total connections (active + idle).
available
Number of idle connections.
max_size
Configured maximum pool size.
shutdown
Close all connections via "_shutdown_connection" and clear the pool.
_create_connection
sub _create_connection { my ($self, $conninfo) = @_; ... }
Required driver hook: build and return one connection from the (already transformed) connect info. The pool tracks the connection; do not push it anywhere yourself.
_shutdown_connection
sub _shutdown_connection { my ($self, $conn) = @_; $conn->finish }
Optional driver hook: close one connection during "shutdown". Defaults to a no-op; exceptions are swallowed by the caller.
_transform_conninfo
sub _transform_conninfo { my ($self, $conninfo) = @_; ... }
Optional driver hook: adapt stored connect info into the shape the driver's connection constructor expects (e.g. a libpq conninfo string). Defaults to returning it unchanged.
AUTHOR
DBIO & DBIx::Class Authors
COPYRIGHT AND LICENSE
Copyright (C) 2026 DBIO Authors Portions Copyright (C) 2005-2025 DBIx::Class Authors Based on DBIx::Class, heavily modified.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.