DBIO-Forked
view release on metacpan or search on metacpan
t/07-transport-paths.t view on Meta::CPAN
# select_single_async resolve to: a cursor is drained, a single row is
# one arrayref, no row is nothing;
# 3. a child that dies before it writes: a failed Future saying so, reaped;
# 4. a Future dropped without get: DESTROY ends and reaps the child, also when the
# child is blocked writing a result nobody will read;
# 5. a result larger than the pipe buffer: complete through get, and
# complete through repeated is_ready (drained over several calls);
# 6. *_async inside a txn_do_async body: there is no guard, it forks again
# from within the child;
# 7. pipeline, listen, deploy_async: what the caller is told.
#
# WHAT RUNS FOR REAL: fork + pipe + Storable, DBIO::Forked::Storage and its
# Future; in section 7 also core's DBIO::Schema and DBIO::Storage::DBI as the
# owner, never connected.
# THE SUBSTITUTIONS: no DBI and no database. Sections 1-6 run a mock sync
# storage whose ops answer fixed data or echo their arguments together with
# the pid they ran in -- so a result also says WHERE it was computed.
use DBIO::Schema;
use DBIO::Storage::DBI;
use DBIO::Storage::Async;
use DBIO::Forked::Storage;
use DBIO::Forked::Future;
alarm 60;
my $BIG_BYTES = 200 * 1024; # three times a 64 KiB pipe buffer, and then some
{
# What a real sync select() returns: a cursor, not rows. Blessed, answers all.
package t::Mock::Cursor;
sub new { my ($class, @rows) = @_; bless { rows => [ @rows ] }, $class }
sub all { @{ $_[0]{rows} } }
package t::Mock::Storage;
sub new { bless {}, shift }
# 200 KiB whose every four bytes differ: a lost, repeated or misplaced chunk
# changes the string, which a run of one character would not show.
sub big { pack 'N*', 0 .. $BIG_BYTES / 4 - 1 }
sub select {
my ($self, $what) = @_;
return t::Mock::Cursor->new([ 1, 'Alice' ], [ 2, 'Bob' ]) if $what eq 'cursor';
return t::Mock::Cursor->new if $what eq 'empty-cursor';
return ([ 1, 'Alice' ]) if $what eq 'one-row';
return ([ 1, $self->big ]) if $what eq 'big';
return ([ $$ ]) if $what eq 'whoami';
if ($what eq 'slow') { Time::HiRes::sleep(0.3); return ([ 'late' ]) }
kill KILL => $$ if $what eq 'die-unanswered';
return ([ 1, 'Alice' ], [ 2, 'Bob' ]);
}
# Sync select_single returns the column values as a flat list.
sub select_single {
my ($self, $what) = @_;
return $what eq 'miss' ? () : (7, 'Grace');
}
sub insert { my $self = shift; return { op => 'insert', pid => $$, args => [ @_ ] } }
sub update { my $self = shift; return { op => 'update', pid => $$, args => [ @_ ] } }
sub delete { my $self = shift; return { op => 'delete', pid => $$, args => [ @_ ] } }
sub txn_do {
my ($self, $code, @args) = @_;
return { op => 'txn_do', pid => $$, body => [ $code->(@args) ] };
}
package t::Mock::Schema;
sub new { my ($class, $storage) = @_; bless { storage => $storage }, $class }
sub storage { $_[0]->{storage} }
}
my $schema = t::Mock::Schema->new(t::Mock::Storage->new);
my $storage = DBIO::Forked::Storage->new($schema);
# Nothing left to wait for: no child of this process, running or dead. The
# stricter reading of "no zombies" -- a 0 here would be a child still alive.
sub no_children_ok {
my ($name) = @_;
my $found = waitpid(-1, WNOHANG);
is $found, -1, $name;
}
# Computed in another process: a pid, and not ours.
sub in_child_ok {
my ($pid, $name) = @_;
ok defined $pid && $pid =~ /^\d+\z/ && $pid != $$, $name;
}
# ---------------------------------------------------------------------------
# 1. The six blocking wrappers.
# ---------------------------------------------------------------------------
{
is_deeply [ $storage->select('rows') ], [ [ 1, 'Alice' ], [ 2, 'Bob' ] ],
'select: blocks and returns the rows';
# The wrappers answer in the shapes of the *_async methods they block on, not
# in those of the sync storage: rows where that returns a cursor, one row
# arrayref where that returns the column values as a list.
is_deeply [ $storage->select_single('hit') ], [ [ 7, 'Grace' ] ],
'select_single: blocks and returns the one row';
for my $op (qw( insert update delete )) {
my $got = $storage->$op('artist', { name => 'Carol' }, { id => 3 });
is $got->{op}, $op, "$op: blocks and returns what the sync $op answered";
is_deeply $got->{args}, [ 'artist', { name => 'Carol' }, { id => 3 } ],
"$op: with the arguments as given";
in_child_ok($got->{pid}, "$op: computed in a child, not in this process");
}
my $got = $storage->txn_do(sub { return [ reverse @_ ] }, 'x', 'y');
is $got->{op}, 'txn_do', 'txn_do: blocks and returns what the sync txn_do answered';
is_deeply $got->{body}, [ [ 'y', 'x' ] ], 'txn_do: the body ran with its arguments';
in_child_ok($got->{pid}, 'txn_do: computed in a child, not in this process');
no_children_ok('the wrappers reaped every child they forked');
}
# ---------------------------------------------------------------------------
# 2. delete_async, select_single_async, and the two reshaped reads.
( run in 0.914 second using v1.01-cache-2.11-cpan-145b269f2f3 )