DBIO-PostgreSQL-EV

 view release on metacpan or  search on metacpan

t/19-copy-in-regression.t  view on Meta::CPAN

use strict;
use warnings;
use Test::More;
use Test::Exception;

# Regression test for karr #13:
#   "copy_in releases pool connection while COPY stream still in flight
#    on the wire"
#
# Storage.pm:copy_in used to call $self->pool->release($pg) synchronously
# immediately after $pg->put_copy_end. EV::Pg only signals "COPY stream
# accepted" at that point — libpq has not yet drained the buffered bytes,
# applied the rows, or sent CommandComplete + ReadyForQuery. Handing the
# connection back to the pool at that moment means the very next
# pool->acquire on the same Storage may hand out the same still-busy
# connection; libpq then refuses the new query with
#   "PQsendQueryParams failed: another command is already in progress"
# and the Storage's CRUD Future fails.
#
# The fix (Storage.pm:copy_in) waits for EV::Pg's SECOND callback firing
# (the one that delivers the final cmd_tuples after libpq has actually
# finished the COPY) before releasing the connection. This test would
# fail loudly against the original implementation and pass against the
# fixed one. It runs the cycle THREE times back-to-back because the bug
# is timing-sensitive: the pool typically has 5 idle slots, so the first
# copy_in is usually followed by an acquire that picks a DIFFERENT idle
# connection; the bug surfaces once the same Storage cycles the same
# physical connection. Three iterations exercise both code paths.
#
# Pre-fix behavior on this test:
#   - iteration 1: first select_async after copy_in picks a different
#     idle connection (or, if pool_size == 1, hits the still-busy one);
#     not always a hard fail, so we run it three times.
#   - iterations 2+3: same connection gets reused, libpq returns
#     "another command is already in progress", select_async Future fails.
#
# Post-fix behavior:
#   - every iteration's follow-up select_async succeeds and returns the
#     rows that were just COPY-loaded.

BEGIN {
  plan skip_all => 'Set DBIO_TEST_PG_DSN to run integration tests'
    unless $ENV{DBIO_TEST_PG_DSN};
}

use EV;
use EV::Pg;
use DBIO::PostgreSQL::EV::Storage;

# Parse DSN into a libpq conninfo hash (same convention as t/10-t/18).
my $dsn  = $ENV{DBIO_TEST_PG_DSN};
my $user = $ENV{DBIO_TEST_PG_USER} || '';
my $pass = $ENV{DBIO_TEST_PG_PASS} || '';

my %ci;
if ($dsn =~ /^dbi:Pg:(.+)/i) {
  for my $kv (split /;/, $1) {
    my ($k, $v) = split /=/, $kv, 2;
    next unless defined $k && length $k;
    $k = 'dbname' if $k eq 'database';
    $ci{$k} = $v;
  }
} else {
  for my $kv (split /\s+/, $dsn) {
    my ($k, $v) = split /=/, $kv, 2;
    $ci{$k} = $v if defined $k && length $k;
  }
}
$ci{user}     = $user if length $user;
$ci{password} = $pass if length $pass;

# Drive a Future to completion on the EV loop under a wall-clock guard.
sub await_guarded {
  my ($f, $what) = @_;
  local $SIG{ALRM} = sub { die "TIMEOUT awaiting $what\n" };
  alarm 20;
  EV::run(EV::RUN_ONCE) until $f->is_ready;
  alarm 0;
  return $f;
}

sub run_raw {
  my ($storage, $sql) = @_;
  my $f = $storage->pool->acquire->then(sub {
    my $pg  = shift;
    my $rf  = Future->new;
    $pg->query($sql, sub {
      my (undef, $err) = @_;
      $storage->pool->release($pg);
      $err ? $rf->fail($err) : $rf->done;
    });
    return $rf;
  });
  return await_guarded($f, "run_raw: $sql")->get;



( run in 0.359 second using v1.01-cache-2.11-cpan-4ab04211f4c )