DBIO-PostgreSQL-Async
view release on metacpan or search on metacpan
docs/adr/0003-dedicated-listen-connection-pooled-notify-via-pg-notify.md view on Meta::CPAN
handle throws "not connected" (`Storage.pm:498-500`). And on the NOTIFY side, the
SQL `NOTIFY` statement takes no bind placeholders, so inlining a payload as a
string literal invites quoting bugs (`Storage.pm:576-577`).
## Decision
Split the two directions across two different connection strategies.
- **LISTEN on a dedicated, buffered, out-of-pool connection.** `listen` does not
touch the pool. It lazily builds a single dedicated `EV::Pg` handle
(`Storage.pm:501-523`) with `keep_alive => 1` and an `on_notify` callback that
dispatches to the registered per-channel handler (`Storage.pm:515-520`).
Because the socket is not connected yet, `LISTEN`/`UNLISTEN` statements are
*buffered*: an `on_connect` callback flushes a pending queue once the socket is
up (`Storage.pm:508-513`); until then statements are pushed onto
`_listen_pending` (`Storage.pm:527-531`), and `unlisten` mirrors the same
buffer-or-send logic (`Storage.pm:542-554`). The dedicated handle is torn down
in `disconnect` (`Storage.pm:692-695`).
- **NOTIFY on a pooled connection via `pg_notify()`.** `notify` acquires a normal
pooled connection (`Storage.pm:573`), runs
`SELECT pg_notify($1, $2)` with the channel and payload as bind params, and
releases the connection in the callback (`Storage.pm:578-586`). The method's POD
states the contrast explicitly: "Unlike listen, this does not require a
dedicated connection â it uses a pooled connection from the normal pool"
(`Storage.pm:563-564`). Using the `pg_notify()` *function* rather than the bare
`NOTIFY` statement is what makes binding the channel and payload possible.
## Rationale
The asymmetry of the feature dictates the asymmetry of the implementation. A
`LISTEN` must outlive any single checkout, so it cannot share the pool's
release-and-reuse lifecycle; a dedicated keep-alive connection with its own
`on_notify` pump is the only correct host. The connect-race buffering is not
optional polish â without it the first `LISTEN` issued right after
`EV::Pg->new` would reliably throw "not connected"; queuing until `on_connect`
and flushing is the documented fix (`Storage.pm:498-500`). On the NOTIFY side,
the statement is stateless, so paying for a dedicated connection would be waste â
the pool is correct â and routing through `pg_notify($1, $2)` instead of literal
`NOTIFY channel, 'payload'` lets libpq bind the values, sidestepping the
payload-quoting bugs that string interpolation into a `NOTIFY` statement invites
(`Storage.pm:576-577`).
lib/DBIO/PostgreSQL/Async/Storage.pm view on Meta::CPAN
# Use a dedicated connection for LISTEN (not from the pool).
# EV::Pg->new returns before the socket is actually connected; query()
# dispatched on a not-yet-connected handle throws "not connected".
# We buffer LISTEN/UNLISTEN until on_connect fires and then flush.
$self->{_listen_pg} ||= do {
require EV::Pg;
$self->{_listen_pending} = [];
$self->{_listen_connected} = 0;
my $pg = EV::Pg->new(
conninfo => $self->_conninfo_string,
keep_alive => 1,
on_connect => sub {
$self->{_listen_connected} = 1;
my $q = delete $self->{_listen_pending} || [];
$self->{_listen_pending} = [];
$self->{_listen_pg}->query($_, sub {}) for @$q;
},
on_error => sub { warn "LISTEN connection error: $_[0]\n" },
on_notify => sub {
my ($ch, $payload, $pid) = @_;
if (my $handler = $self->{_listeners}{$ch}) {
( run in 0.786 second using v1.01-cache-2.11-cpan-14f38c9f855 )