DBIx-Loop
view release on metacpan or search on metacpan
t/15-fork.t view on Meta::CPAN
}
use DBIx::Loop;
my $dir = File::Temp::tempdir(CLEANUP => 1);
my $db_file = "$dir/fork.db";
diag("fork safety over $ADAPTER");
my $ad = $ADAPTER->new;
my $db = DBIx::Loop->connect("dbi:SQLite:dbname=$db_file", '', '',
{ RaiseError => 1 }, loop => $ad, workers => 2);
sub await1 {
my ($f) = @_;
$ad->await($f);
my ($r) = $f->get;
return $r;
}
await1($db->do("CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)"));
await1($db->do("INSERT INTO t (id, v) VALUES (1, 'parent')"));
# ---- the pool is lazy: no workers until the first statement ------------------
# (already true by now - the do() above forced it)
my @parent_pids = $db->_worker_pids;
is(scalar @parent_pids, 2, 'the parent forked its two workers');
ok((grep { $_ > 0 } @parent_pids) == 2, 'and both have real pids');
# ---- a child gets its own pool, not the parent's -----------------------------
{
my $pid = open my $rd, '-|';
die "fork: $!" unless defined $pid;
if (!$pid) {
# First statement in the child must build a fresh pool: the inherited
# one is disowned, not reused.
my $r = await1($db->query("SELECT v FROM t WHERE id = 1"));
print join(',', $db->_worker_pids), "\n";
print $r->{rows}[0][0], "\n";
exit 0;
}
my @out = <$rd>;
close $rd;
chomp @out;
my @child_pids = split /,/, ($out[0] // '');
is(scalar @child_pids, 2, 'the child forked its own two workers');
my %parent = map { $_ => 1 } @parent_pids;
is(scalar(grep { $parent{$_} } @child_pids), 0,
'and none of them is one of the parent\'s');
is($out[1], 'parent', 'the child read its own correct row');
}
# ---- the parent survives the child's exit -----------------------------------
# This is the SIGTERM bug: the child's DESTROY used to reap the parent's
# workers, so the next parent query hung or failed.
{
my @still = $db->_worker_pids;
is_deeply([sort { $a <=> $b } @still], [sort { $a <=> $b } @parent_pids],
'the parent still has the same workers after the child exited');
my $alive = grep { kill(0, $_) } @still;
is($alive, scalar @still, 'and every one of them is still running');
my $r = await1($db->query("SELECT v FROM t WHERE id = 1"));
is($r->{rows}[0][0], 'parent', 'the parent can still query');
}
# ---- no response theft under concurrent children ----------------------------
# Each child writes a row only it knows, then reads it back. If two processes
# were sharing a socketpair, a child would sometimes receive a sibling's row.
{
await1($db->do("INSERT INTO t (id, v) VALUES (?, ?)", $_ + 10, "child$_"))
for 1 .. 4;
my @kids;
for my $i (1 .. 4) {
my $pid = open my $rd, '-|';
die "fork: $!" unless defined $pid;
if (!$pid) {
my $got = '';
for (1 .. 5) {
my $r = await1($db->query("SELECT v FROM t WHERE id = ?", $i + 10));
$got = $r->{rows}[0][0] // '(undef)';
last if $got ne "child$i";
}
print $got, "\n";
exit 0;
}
push @kids, [ $i, $rd ];
}
my @wrong;
for my $k (@kids) {
my ($i, $rd) = @$k;
chomp(my $got = <$rd> // '');
close $rd;
push @wrong, "child$i got '$got'" if $got ne "child$i";
}
is_deeply(\@wrong, [], 'four concurrent children each saw only their own row')
or diag explain \@wrong;
}
$db->disconnect;
done_testing;
( run in 2.127 seconds using v1.01-cache-2.11-cpan-14f38c9f855 )