Skip Menu |

This queue is for tickets about the future CPAN distribution.

Report information
The Basics
Id: 120468
Status: resolved
Priority: 0/
Queue: future

People
Owner: Nobody in particular
Requestors: TEAM [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in: (no value)
Fixed in: 0.37



Subject: ->wait_any Future going out of scope while processing dependents
Seen a few of these, particularly with code using Net::Async::HTTP where the remote server closes the connection: Can't call method "_mark_ready" on an undefined value at .../site_perl/5.24.0/Future.pm line 1665. This is the $weakself->_mark_ready() call in ->wait_any. I think what's happening is that the ->wait_any Future is going out of scope while the dependents are being processed, so we start out with a valid $weakself, but end up with undef by the end of that sub. The attached patch may not be the right thing to do - maybe the future going out of scope is an invalid situation, and we should be flagging this the same way as cases such as void-context ->then?
Subject: 2017-03-01-wait-any-weakself.patch
diff --git a/lib/Future.pm b/lib/Future.pm index c926bd2..5c7a4f3 100644 --- a/lib/Future.pm +++ b/lib/Future.pm @@ -1643,26 +1643,26 @@ sub wait_any weaken( my $weakself = $self ); my $sub_on_ready = sub { - return unless $weakself; - return if $weakself->{result} or $weakself->{failure}; # don't recurse on child ->cancel + return unless my $self = $weakself; + return if $self->{result} or $self->{failure}; # don't recurse on child ->cancel return if --$pending and $_[0]->{cancelled}; if( $_[0]->{cancelled} ) { - $weakself->{failure} = [ "All component futures were cancelled" ]; + $self->{failure} = [ "All component futures were cancelled" ]; } elsif( $_[0]->{failure} ) { - $weakself->{failure} = [ $_[0]->failure ]; + $self->{failure} = [ $_[0]->failure ]; } else { - $weakself->{result} = [ $_[0]->get ]; + $self->{result} = [ $_[0]->get ]; } foreach my $sub ( @subs ) { $sub->{ready} or $sub->cancel; } - $weakself->_mark_ready( "wait_any" ); + $self->_mark_ready( "wait_any" ); }; foreach my $sub ( @subs ) {
On Wed Mar 01 10:01:27 2017, TEAM wrote: Show quoted text
> Seen a few of these, particularly with code using Net::Async::HTTP > where the remote server closes the connection: > > Can't call method "_mark_ready" on an undefined value at > .../site_perl/5.24.0/Future.pm line 1665. > > This is the $weakself->_mark_ready() call in ->wait_any. > > I think what's happening is that the ->wait_any Future is going out of > scope while the dependents are being processed, so we start out with a > valid $weakself, but end up with undef by the end of that sub. > > The attached patch may not be the right thing to do - maybe the future > going out of scope is an invalid situation, and we should be flagging > this the same way as cases such as void-context ->then?
This is tricky. The bugfix does indeed look like a sensible thing to be doing. Problem is I can't manage to find a way to trigger the original bug, so as to write a unit test for the fix of it. I think it must be a tricky combination of circumstances because I've not managed to stumble on the exact set required. Do you have any more insight into how I can reproduce this in a test? -- Paul Evans
Here's an updated patch with test case, as discussed in IRC.
Subject: 2017-11-28-wait-any-weakself.patch
diff --git a/lib/Future.pm b/lib/Future.pm index c926bd2..5c7a4f3 100644 --- a/lib/Future.pm +++ b/lib/Future.pm @@ -1643,26 +1643,26 @@ sub wait_any weaken( my $weakself = $self ); my $sub_on_ready = sub { - return unless $weakself; - return if $weakself->{result} or $weakself->{failure}; # don't recurse on child ->cancel + return unless my $self = $weakself; + return if $self->{result} or $self->{failure}; # don't recurse on child ->cancel return if --$pending and $_[0]->{cancelled}; if( $_[0]->{cancelled} ) { - $weakself->{failure} = [ "All component futures were cancelled" ]; + $self->{failure} = [ "All component futures were cancelled" ]; } elsif( $_[0]->{failure} ) { - $weakself->{failure} = [ $_[0]->failure ]; + $self->{failure} = [ $_[0]->failure ]; } else { - $weakself->{result} = [ $_[0]->get ]; + $self->{result} = [ $_[0]->get ]; } foreach my $sub ( @subs ) { $sub->{ready} or $sub->cancel; } - $weakself->_mark_ready( "wait_any" ); + $self->_mark_ready( "wait_any" ); }; foreach my $sub ( @subs ) { diff --git a/t/11wait_any.t b/t/11wait_any.t index c72629e..294e8ea 100644 --- a/t/11wait_any.t +++ b/t/11wait_any.t @@ -149,4 +149,19 @@ use Future; '->get on empty wait_any is empty' ); } +# wait_any instance disappearing partway through cancellations +{ + my $f = Future->new; + my $wait; + $wait = Future->wait_any( + $f, + my $cancelled = Future->new->on_cancel(sub { + undef $wait + }) + ); + is(exception { $f->done(1) }, undef, 'no problems cancelling a Future which clears the original ->wait_any ref'); + ok($cancelled->is_cancelled, 'cancellation occurred as expected'); + ok($f->is_done, 'and ->wait_any is marked as done'); +} + done_testing;
On Tue Nov 28 07:13:44 2017, TEAM wrote: Show quoted text
> Here's an updated patch with test case, as discussed in IRC.
Ah yes, that gets it. I'll also apply analogous tests/fixes for the other convergent constructors as well. -- Paul Evans
Fixed in 0.37 -- Paul Evans