Skip Menu |

This queue is for tickets about the Future-AsyncAwait CPAN distribution.

Report information
The Basics
Id: 128620
Status: resolved
Priority: 0/
Queue: Future-AsyncAwait

People
Owner: Nobody in particular
Requestors: leonerd-cpan [...] leonerd.org.uk
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: 0.21
Fixed in: 0.29



Subject: Warnings (and memory leak?) when dropping still-pending Futures
Dropping a still-pending future, e.g. my $f = (async sub { await Future->new })->(); undef $f; causes: TODO: free ->awaiting_future TODO: free ->returning_future TODO: free ->frames TODO: free ->padslots -- Paul Evans
A partial fix attached. It doesn't solve every case, but it solves the majority. There's a few awkward cornercases involving certain kinds of save stack cleanup, mostly following things like `try/finally` or foreach loops. It also weakens the stored future references, because without doing that there's a long-lived strong circular reference through them that persists until GD time. -- Paul Evans
Subject: rt128620.patch
=== modified file 'lib/Future/AsyncAwait.xs' --- lib/Future/AsyncAwait.xs 2019-02-02 17:10:46 +0000 +++ lib/Future/AsyncAwait.xs 2019-03-21 22:11:03 +0000 @@ -267,6 +267,28 @@ ret += DMD_ANNOTATE_SV(sv, state->awaiting_future, "the awaiting Future"); ret += DMD_ANNOTATE_SV(sv, state->returning_future, "the returning Future"); + SuspendedFrame *frame; + for(frame = state->frames; frame; frame = frame->next) { + if(frame->stacklen) { + int i; + for(i = 0; i < frame->stacklen; i++) + ret += DMD_ANNOTATE_SV(sv, frame->stack[i], "a suspended stack temporary"); + } + } + + if(state->padlen) { + int i; + for(i = 0; i < state->padlen - 1; i++) + if(state->padslots[i]) + ret += DMD_ANNOTATE_SV(sv, state->padslots[i], "a suspended pad slot"); + } + + if(state->mortallen) { + int i; + for(i = 0; i < state->mortallen; i++) + ret += DMD_ANNOTATE_SV(sv, state->mortals[i], "a suspended mortal"); + } + return ret; } #endif @@ -305,23 +327,84 @@ SuspendedState *state = (SuspendedState *)mg->mg_ptr; if(state->awaiting_future) { - fprintf(stderr, "TODO: free ->awaiting_future\n"); + SvREFCNT_dec(state->awaiting_future); + state->awaiting_future = NULL; } if(state->returning_future) { - fprintf(stderr, "TODO: free ->returning_future\n"); + SvREFCNT_dec(state->returning_future); + state->returning_future = NULL; } if(state->frames) { - fprintf(stderr, "TODO: free ->frames\n"); + SuspendedFrame *frame, *next = state->frames; + while((frame = next)) { + next = frame->next; + + if(frame->stacklen) { + /* The stack isn't refcounted, so we should not SvREFCNT_dec() these + * items + */ + Safefree(frame->stack); + } + + if(frame->marklen) { + Safefree(frame->marks); + } + + if(frame->saved) { + int idx; + for(idx = 0; idx < frame->savedlen; idx++) { + struct Saved *saved = &frame->saved[idx]; + switch(saved->type) { + /* Saved types for which we've no cleanup needed */ + case SAVEt_CLEARPADRANGE: + case SAVEt_CLEARSV: + case SAVEt_COMPPAD: + case SAVEt_INT_SMALL: +#ifdef SAVEt_STRLEN + case SAVEt_STRLEN: +#endif + case SAVEt_SET_SVFLAGS: + break; + + default: + fprintf(stderr, "TODO: free saved slot type %d\n", saved->type); + break; + } + } + + Safefree(frame->saved); + } + + /* TODO: maybe free items of the el.loop */ + +#ifdef HAVE_ITERVAR + if(frame->itervar) + fprintf(stderr, "TODO: free frame->itervar\n"); +#endif + + Safefree(frame); + } } if(state->padslots) { - fprintf(stderr, "TODO: free ->padslots\n"); + int i; + for(i = 0; i < state->padlen - 1; i++) { + if(state->padslots[i]) + SvREFCNT_dec(state->padslots[i]); + } + + Safefree(state->padslots); + state->padslots = NULL; } if(state->mortals) { - fprintf(stderr, "TODO: free ->mortals\n"); + int i; + for(i = 0; i < state->mortallen; i++) + sv_2mortal(state->mortals[i]); + + Safefree(state->mortals); } Safefree(state); @@ -1445,6 +1528,7 @@ future_on_ready(f, curcv); state->awaiting_future = newSVsv(f); + sv_rvweaken(state->awaiting_future); if(!state->returning_future) state->returning_future = future_new_from_proto(f); @@ -1453,9 +1537,12 @@ SvREFCNT_dec((SV *)curcv); PUSHMARK(SP); - PUSHs(state->returning_future); + mPUSHs(newSVsv(state->returning_future)); PUTBACK; + if(!SvWEAKREF(state->returning_future)) + sv_rvweaken(state->returning_future); + TRACEPRINT("LEAVE await curcv=%p [%s:%d]\n", curcv, CopFILE(curcop), CopLINE(curcop)); return PL_ppaddr[OP_RETURN](aTHX); === added file 't/42unresolved.t' --- t/42unresolved.t 1970-01-01 00:00:00 +0000 +++ t/42unresolved.t 2019-03-21 22:11:03 +0000 @@ -0,0 +1,42 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More; + +use Future; + +use Future::AsyncAwait; + +my $orig_cxstack_ix = Future::AsyncAwait::__cxstack_ix; + +async sub identity +{ + return await $_[0]; +} + +async sub func +{ + my ( $f, @vals ) = @_; + + my $pad = "foo" . ref($f); + my $x = 123; + $x + 1 + [ "a", await identity $f ]; +} + +# unresolved +foreach ( 1 .. 1023 ) { + my $f1 = Future->new; + my $fret = func( $f1, 1, 2 ); + + undef $fret; +} + +is( Future::AsyncAwait::__cxstack_ix, $orig_cxstack_ix, + 'cxstack_ix did not grow during the test' ); + +done_testing; + +require Devel::MAT::Dumper; +Devel::MAT::Dumper::dump("42unresolved.pmat");
As of Future::AsyncAwait 0.29 I think most of these cases should now be fixed. I'll keep this open for now but if no more turn up soon I'll close it. -- Paul Evans