EV-Future

 view release on metacpan or  search on metacpan

Future.xs  view on Meta::CPAN

    ctx->num_cvs = 0;
    ctx->shared_cv = NULL;
    ctx->abandoned = 0;

    ifp_guard *guard = ifp_guard_push(aTHX_ &ctx->is_freed_ptr, NULL,
                                      NULL, &ctx->abandoned);
    int *is_freed = &guard->is_freed;

    /* Counted before anything is dispatched: a task that completes the whole
       operation synchronously runs cleanup, which drops the context's
       reference, and only this one keeps the cell alive until we return it. */
    evf_handle *h = NULL;
    if (want_handle) {
        h = evf_handle_new(aTHX_ NULL, EVF_KIND_PARALLEL);
        evf_handle_attach(aTHX_ h, ctx);
        ctx->h = h;
        guard->pending_h = h;
    }

    SV *done_rv = NULL;
    if (unsafe) {

t/repro_reentrant_cancel.t  view on Meta::CPAN

use strict;
use warnings;
use Test::More;
use EV;
use EV::Future;

# A cancel issued from inside a DESTROY that fires *during* cleanup itself is
# a different hazard from an ordinary user cancel. Cleanup drops
# ctx->tasks/final_cb/worker, and any of those can be the last reference
# keeping a blessed object alive. If that object's DESTROY calls $h->cancel
# while the handle had not been detached yet, the handle still looked live
# and cancel would re-enter cleanup on a context that is already half torn
# down: it double-decs ctx->tasks and ctx->final_cb, walks whatever CV field
# the first pass freed without NULLing, then double-frees the handle cell and
# the context itself. Detaching the handle first, before any SvREFCNT_dec that
# can run Perl, makes the reentrant call see h->ctx == NULL and bail out
# immediately.
#
# All four primitives have their own cleanup with its own copy of that
# ordering, so all four are exercised here: covering only parallel let the

t/repro_reentrant_cancel.t  view on Meta::CPAN

    {
        name  => 'race',
        start => sub { race($_[0], $_[1]) },
        # race treats a non-coderef element as an instant winner, which would
        # settle it inside the XSUB before the handle reaches Perl - too early
        # for the DESTROY to have a handle to cancel. So race's guard travels
        # inside a task CV that never completes; the tasks array is still the
        # only owner, and race_cleanup still frees it.
        guard_element => sub {
            my $guard = shift;
            return sub { my $keep_alive = $guard; return };
        },
    },
);

for my $kind (@kinds) {
    my $name = $kind->{name};

    subtest "reentrant cancel from a guard freed via the final_cb closure ($name)" => sub {
        our @w;
        my ($final, $reentries, $err) = (0, 0, '');

t/repro_reentrant_cancel.t  view on Meta::CPAN

                $err = $@ if $@;
            });
            # $guard's only remaining owner once this block ends is the
            # closure's own captured copy. The closure itself is passed
            # straight through as an argument and never bound to a variable
            # that outlives this statement, so ctx->final_cb ends up as its
            # sole reference; freeing it during cleanup is what frees $guard
            # in turn.
            $h = $kind->{start}->(
                [ sub { my $d = shift; push @w, EV::timer 0.01, 0, sub { $d->() } } ],
                sub { my $keep_alive = $guard; $final++ },
            );
        }

        $h->cancel;

        is($reentries, 1, 'guard DESTROY ran its re-entrant cancel exactly once');
        is($final, 0, 'final_cb was freed, not called (plain cancel does not fire it)');
        ok(!$err, 'the re-entrant cancel call did not die') or diag $err;
        is_deeply(\@warnings, [], 'no "Attempt to free unreferenced scalar" or similar')
            or diag explain \@warnings;

t/repro_unsafe_abandon.t  view on Meta::CPAN

# 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" },



( run in 1.770 second using v1.01-cache-2.11-cpan-14f38c9f855 )