EV-Gearman
view release on metacpan or search on metacpan
t/25_job_lifetime.t view on Meta::CPAN
# Job -> client back-pointer lifetime. A stashed async job must keep
# the connection's control block allocated (as an inert tombstone) so
# that job methods after client destruction croak "client destroyed"
# instead of reading freed memory; and a forged job hashref must croak
# "stale job" instead of dereferencing a forged pointer.
#
# T-D1-1's pre-fix failure is a use-after-free that usually croaks
# "correctly" by luck (the stale bytes are not the magic word) â the
# deterministic pre-fix proof is the ASan demonstration referenced in
# the commit; this test locks the user-visible semantics.
use strict;
use warnings;
use Test::More;
use IO::Socket::INET;
use EV;
use EV::Gearman;
my $host = $ENV{TEST_GEARMAN_HOST} || '127.0.0.1';
my $port = $ENV{TEST_GEARMAN_PORT} || 4730;
my $probe = IO::Socket::INET->new(
PeerAddr => $host, PeerPort => $port, Proto => 'tcp', Timeout => 1,
);
plan skip_all => "no gearmand at $host:$port" unless $probe;
close $probe;
my $func = "lifetime_$$";
# T-D1-1: async worker stashes the job, the client object is destroyed
# from inside the callback, and a later job method must croak
# "client destroyed" â with the memory kept alive by the job's
# tombstone reference, not read from freed heap.
{
my $w = EV::Gearman->new(host => $host, port => $port);
my $c = EV::Gearman->new(host => $host, port => $port);
my $job;
$w->register_function($func => { async => 1 }, sub {
$job = $_[0];
undef $w; # DESTROY with a job outstanding
EV::break;
});
$w->work;
$c->on_connect(sub { $c->submit_job_bg($func, "x") });
my $g = EV::timer 5, 0, sub { EV::break };
EV::run;
ok $job, 'T-D1-1: job was dispatched and stashed';
eval { $job->complete("late") };
like $@, qr/client destroyed/, 'T-D1-1: job method croaks after client destroy';
undef $job; # releases the tombstone reference
pass 'T-D1-1: tombstone released without crash';
undef $c;
}
# T-D1-2: forged job hashrefs. Fork-guarded: pre-fix the _client_ptr
# variant segfaults the child (proven); post-fix all variants croak
# cleanly. No gearmand involved.
{
my @cases = (
['forged with _client_ptr', sub {
my $j = bless { handle => 'H:1', _client_ptr => 12345 },
'EV::Gearman::Job';
$j->complete('x');
}],
['bare forged hash', sub {
my $j = bless { handle => 'H:1' }, 'EV::Gearman::Job';
$j->complete('x');
}],
['blessed arrayref', sub {
my $j = bless [], 'EV::Gearman::Job';
$j->complete('x');
}],
);
for my $case (@cases) {
my ($name, $code) = @$case;
pipe(my $rd, my $wr) or die "pipe: $!";
my $pid = fork();
die "fork: $!" unless defined $pid;
if ($pid == 0) { # child
close $rd;
my $err = do {
local $@;
eval { $code->(); 1 } ? 'NOERROR' : $@;
};
print $wr ($err =~ /stale job|invalid job/ ? "OK" : "BAD:$err");
close $wr;
exit 0;
}
close $wr;
local $/;
my $out = <$rd> // '';
( run in 2.666 seconds using v1.01-cache-2.11-cpan-d80b1682f3f )