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
=== 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");