EV-Future
view release on metacpan or search on metacpan
t/repro_unsafe_abandon.t view on Meta::CPAN
churn(40);
ok(!$err, 'EV::run completed without error') or diag $err;
is($t3_ran, 0, 'series does not continue past the failed task');
is($final, 0, 'final_cb not called');
@w = ();
};
subtest 'parallel unsafe: late completion does not fire final_cb' => sub {
our @w;
my $final = 0;
eval {
parallel([
sub { my $d = shift; push @w, EV::timer 0.01, 0, sub { $d->() } },
sub { my $d = shift; $d->(); die "boom\n" },
], sub { $final++ }, 1);
};
is($@, "boom\n", 'exception propagated to caller');
my $err = run_loop();
churn(40);
ok(!$err, 'EV::run completed without error') or diag $err;
is($final, 0, 'final_cb not called by the late completion');
@w = ();
};
subtest 'race unsafe: late winner does not fire final_cb' => sub {
our @w;
my $final = 0;
eval {
race([
sub { my $d = shift; push @w, EV::timer 0.01, 0, sub { $d->("late") } },
sub { die "boom\n" },
], sub { $final++ }, 1);
};
is($@, "boom\n", 'exception propagated to caller');
my $err = run_loop();
churn(40);
ok(!$err, 'EV::run completed without error') or diag $err;
is($final, 0, 'final_cb not called by the late winner');
@w = ();
};
# An abandoned operation is frozen, not finished: nothing will ever advance it,
# and in unsafe mode its context is deliberately leaked because the unwind
# cannot run cleanup. A handle held over it is the only thing that can still
# reach that context, and cancel() is the documented way to reclaim it.
#
# The assertions below pin the observable half of that contract: stale non-zero
# counts while abandoned, zero after cancel, and final_cb firing only for
# cancel(1). The reclaim of the memory itself is deliberately not asserted here,
# because no leak checker can see it - the leaked context stays reachable
# through the shared done CV's payload pointer, which lives in a Perl SV arena,
# so valgrind reports it as still reachable rather than lost either way. It was
# measured instead by counting live allocations at exit: 20 abandonments leave
# 45 blocks from plimit_start alive without the cancel and 2 with it.
subtest 'cancel reclaims an abandoned operation' => sub {
our @w;
# limit 2: tasks 1 and 2 go out in the opening burst, so the handle is
# assigned before task 3 is dispatched from the event loop and dies there.
my ($final, $h) = (0);
$h = parallel_limit([
sub { my $d = shift; push @w, EV::timer 0.01, 0, sub { $d->() } },
sub { my $d = shift; push @w, EV::timer 0.50, 0, sub { $d->() } },
sub { die "boom\n" },
sub { my $d = shift; $d->() },
], 2, sub { $final++ }, 1);
my $bail = EV::timer 0.1, 0, sub { EV::break };
eval { EV::run };
is($final, 0, 'final_cb did not fire after the abandonment');
cmp_ok($h->pending, '>', 0, 'an abandoned operation still reports pending tasks');
cmp_ok($h->active, '>', 0, 'and still reports tasks in flight');
$h->cancel;
is($h->pending, 0, 'cancel released the abandoned context: pending is zero');
is($h->active, 0, 'cancel released the abandoned context: active is zero');
is($final, 0, 'a plain cancel still did not fire final_cb');
# Same shape again, this time recovered with the firing form.
my ($final2, $h2) = (0);
$h2 = parallel_limit([
sub { my $d = shift; push @w, EV::timer 0.01, 0, sub { $d->() } },
sub { my $d = shift; push @w, EV::timer 0.50, 0, sub { $d->() } },
sub { die "boom\n" },
sub { my $d = shift; $d->() },
], 2, sub { $final2++ }, 1);
my $bail2 = EV::timer 0.1, 0, sub { EV::break };
eval { EV::run };
is($final2, 0, 'second operation abandoned too');
$h2->cancel(1);
is($final2, 1, 'cancel(1) fired final_cb once after the abandonment');
is($h2->pending, 0, 'and released the context');
@w = ();
};
subtest 'safe mode still completes normally' => sub {
our @w;
my $final = 0;
# No exception: the guard must not abandon anything on a normal unwind.
parallel_limit([
sub { my $d = shift; push @w, EV::timer 0.01, 0, sub { $d->() } },
sub { my $d = shift; push @w, EV::timer 0.02, 0, sub { $d->() } },
sub { my $d = shift; push @w, EV::timer 0.01, 0, sub { $d->() } },
], 2, sub { $final++; EV::break });
my $err = run_loop();
ok(!$err, 'EV::run completed without error') or diag $err;
is($final, 1, 'final_cb fired exactly once');
@w = ();
};
( run in 2.845 seconds using v1.01-cache-2.11-cpan-2e0ccfb7a10 )