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 #20 footgun)

  • "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.