Skip Menu |

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

Report information
The Basics
Id: 126037
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.15
Fixed in: 0.16



Subject: die after nested await causes immediate program END
The attached test case causes the entire program to enter END time (doesn't die, doesn't immediately abort; all END blocks appear to run correctly): A smaller test case without the 'outer' function does not fail (pass argument 1) A smaller test case without the 'await' in 'inner' does not fail (pass argument 2) Also attached is perl.trace output from running it in case 0. -- Paul Evans
Subject: await-nested.pl
#!/usr/bin/perl use strict; use warnings; use Future; use Future::AsyncAwait; my $N = shift @ARGV // 0; my $f; async sub inner { ( $N == 2 ) or await $f; die "Oopsie\n"; } async sub outer { await inner(); } $f = Future->new; my $fret = ( $N == 1 ) ? inner() : outer(); $f->done( "result" ); print STDERR "Failure was: ", scalar $fret->failure;
Subject: perl.trace
Download perl.trace
application/octet-stream 12.1k

Message body not shown because it is not plain text.

On Fri Aug 10 12:35:33 2018, PEVANS wrote: Show quoted text
> The attached test case causes the entire program to enter END time > (doesn't die, doesn't immediately abort; all END blocks appear to run > correctly):
Large amounts of staring in gdb suggests that the reason for this is that the `return` statement it hits last just before END yields NULL, because the `cx->blk_u.retop` was NULL. I don't yet know if that's because it's looking at the wrong entry on the context stack, or the right entry but it's been overwritten somehow. Investigation continues -- Paul Evans
On Tue Aug 14 10:25:33 2018, PEVANS wrote: Show quoted text
> I don't yet know if that's because it's looking at the wrong entry on > the context stack, or the right entry but it's been overwritten > somehow. > > Investigation continues
... because the perl-level context stack still contains the entry with NULL retop (level 7 here) that call_method() put there, yet the C-level stack has already returned from Perl_call_sv *-[12] CXt_SUB(&main::outer ret=0x555555d7a210) *-[11] CXt_BLOCK *-[10] CXt_LOOP_ARY *-[9] CXt_SUB(&Future::_mark_ready ret=0x555555d8aec8) *-[8] CXt_BLOCK *-[7] CXt_SUB(&Future::fail ret=(nil)) *-[6] CXt_SUB(&main::inner ret=0x555555d7a210) *-[5] CXt_BLOCK *-[4] CXt_LOOP_ARY *-[3] CXt_SUB(&Future::_mark_ready ret=0x555555d5bc90) *-[2] CXt_BLOCK *-[1] CXt_SUB(&Future::done ret=0x555555d81f18) Breakpoint 1, MY_future_fail (my_perl=<optimized out>, failure=0x555555a991e0, f=<optimized out>) at lib/Future/AsyncAwait.xs:1027 1027 fprintf(stderr, "call_method(\"fail\") cxix=%d...\n", cxstack_ix); (gdb) bt #0 MY_future_fail (my_perl=<optimized out>, failure=0x555555a991e0, f=<optimized out>) at lib/Future/AsyncAwait.xs:1027 #1 pp_leaveasync (my_perl=<optimized out>) at lib/Future/AsyncAwait.xs:1149 #2 0x000055555565526a in Perl_runops_debug (my_perl=0x555555a96260) at dump.c:2451 #3 0x00005555555bd4db in S_run_body (oldscope=1, my_perl=0x555555a96260) at perl.c:2527 #4 perl_run (my_perl=0x555555a96260) at perl.c:2455 #5 0x0000555555583f9e in main (argc=<optimized out>, argv=<optimized out>, env=<optimized out>) at perlmain.c:123 -- Paul Evans
On Tue Aug 14 10:58:50 2018, PEVANS wrote: Show quoted text
> ... because the perl-level context stack still contains the entry with > NULL retop (level 7 here) that call_method() put there, yet the C- > level stack has already returned from Perl_call_sv
An additional subtlety of the way pp_entertry works that I missed on my first read-through, is that if CATCH_GET is true then it invokes itself via the (static) docatch() trampoline, for complicated reasons to do with JMPENVs. Attached patch fixes this, and solves the problem. -- Paul Evans
Subject: rt126037.patch
=== added file 'hax/docatch.c.inc' --- hax/docatch.c.inc 1970-01-01 00:00:00 +0000 +++ hax/docatch.c.inc 2019-01-03 21:00:54 +0000 @@ -0,0 +1,42 @@ +/* vi: set ft=c inde=: */ + +#ifndef docatch + +#define docatch(firstpp) S_docatch(aTHX_ firstpp) + +static OP *S_docatch(pTHX_ Perl_ppaddr_t firstpp) +{ + int ret; + OP * const oldop = PL_op; + dJMPENV; + + assert(CATCH_GET == TRUE); + + JMPENV_PUSH(ret); + switch (ret) { + case 0: + PL_op = firstpp(aTHX); + redo_body: + CALLRUNOPS(aTHX); + break; + case 3: + /* die caught by an inner eval - continue inner loop */ + if (PL_restartop && PL_restartjmpenv == PL_top_env) { + PL_restartjmpenv = NULL; + PL_op = PL_restartop; + PL_restartop = 0; + goto redo_body; + } + /* FALLTHROUGH */ + default: + JMPENV_POP; + PL_op = oldop; + JMPENV_JUMP(ret); + NOT_REACHED; /* NOTREACHED */ + } + JMPENV_POP; + PL_op = oldop; + return NULL; +} + +#endif === modified file 'lib/Future/AsyncAwait.xs' --- lib/Future/AsyncAwait.xs 2018-08-10 15:22:14 +0000 +++ lib/Future/AsyncAwait.xs 2019-01-03 21:00:54 +0000 @@ -59,6 +59,11 @@ # define PadnamelistMAX(pnl) AvFILLp(pnl) #endif +/* Currently no version of perl makes this visible, so we always want it. Maybe + * one day in the future we can make it version-dependent + */ +#include "docatch.c.inc" + typedef struct SuspendedFrame SuspendedFrame; struct SuspendedFrame { SuspendedFrame *next; @@ -918,6 +923,9 @@ break; case CXt_EVAL: + if(CATCH_GET) + panic("Too late to docatch()\n"); + cx = cx_pushblock(CXt_EVAL|CXp_TRYBLOCK, frame->gimme, PL_stack_sp, PL_savestack_ix); cx_pusheval(cx, frame->el.eval.retop, NULL); @@ -1153,6 +1161,13 @@ SuspendedState *state = suspendedstate_get(curcv); + if(state && state->awaiting_future && CATCH_GET) { + /* If we don't do this we get all the mess that is + * https://rt.cpan.org/Ticket/Display.html?id=126037 + */ + return docatch(pp_await); + } + if(state && state->awaiting_future) { I32 orig_height; === modified file 't/06await-nested.t' --- t/06await-nested.t 2018-08-10 12:59:20 +0000 +++ t/06await-nested.t 2019-01-03 21:00:54 +0000 @@ -11,20 +11,25 @@ my $orig_cxstack_ix = Future::AsyncAwait::__cxstack_ix; +my $f; +my $failure; + +async sub inner +{ + my $ret = await $f; + die $failure if defined $failure; + return $ret; +} + +async sub outer +{ + await inner(); +} + # await through two nested async sub calls # See also RT123062 { - my $f = Future->new; - - async sub inner - { - await $f; - } - - async sub outer - { - await inner(); - } + $f = Future->new; my $fret = outer(); $f->done( "value" ); @@ -32,6 +37,19 @@ is( scalar $fret->get, "value", '$fret->get through two nested async subs' ); } +# die after double await +# See also RT126037 +{ + $f = Future->new; + + my $fret = outer(); + $failure = "Oopsie\n"; + + $f->done( "result" ); + + is( scalar $fret->failure, "Oopsie\n", '$fret->failure through two nested async subs' ); +} + # await through two nested async method calls { my $f = Future->new;
Released in 0.16 -- Paul Evans