On Sun Nov 10 13:01:39 2019, TEAM wrote:
Show quoted text> Looks like the issue is that `$x->on_cancel($y)` is keeping a strong
> ref on `$y` - even when `$y` is marked as ready, it won't be a
> candidate for destruction until `$x` is marked as ready.
That sounds a good description of what's going on.
Show quoted text> Updating `on_cancel` in Future.pm to clear out referenced futures when
> they are marked as ready causes the test case to pass:
I found a somewhat better(?) way to basically do that. Each Future now stores a (weakened) list of SCALAR refs to other places where it is referenced; scalars it should undef when it is marked as ready. The `on_cancel` method then stores a ref in the target $code future's list. This ensures that two pending futures form a strong ref in the cancellation chain, but once the latter one becomes resolved the former one can let go of it.
--
Paul Evans
=== modified file 'lib/Future.pm'
--- lib/Future.pm 2019-06-13 13:41:38 +0000
+++ lib/Future.pm 2019-11-11 14:27:22 +0000
@@ -377,6 +377,8 @@
}
delete $self->{on_cancel};
+ $_ and undef $$_ for @{ $self->{undef_on_ready} };
+
my $callbacks = delete $self->{callbacks} or return;
my $cancelled = $self->{cancelled};
@@ -716,6 +718,10 @@
$self->{ready} and return $self;
push @{ $self->{on_cancel} }, $code;
+ if( $is_future ) {
+ push @{ $code->{undef_on_ready} }, \$self->{on_cancel}[-1];
+ weaken $code->{undef_on_ready}[-1];
+ }
return $self;
}
@@ -1037,6 +1043,7 @@
$self->{cancelled}++;
foreach my $code ( reverse @{ $self->{on_cancel} || [] } ) {
+ defined $code or next;
my $is_future = blessed( $code ) && $code->isa( "Future" );
$is_future ? $code->cancel
: $code->( $self );
=== modified file 't/02cancel.t'
--- t/02cancel.t 2017-11-28 17:32:24 +0000
+++ t/02cancel.t 2019-11-11 14:27:22 +0000
@@ -84,8 +84,18 @@
{
my $f1 = Future->new;
my $f2 = Future->new;
+ my $f3 = Future->new;
$f1->on_cancel( $f2 );
+ $f1->on_cancel( $f3 );
+
+ is_oneref( $f1, '$f1 has refcount 1 after on_cancel chaining' );
+ is_refcount( $f2, 2, '$f2 has refcount 2 after on_cancel chaining' );
+ is_refcount( $f3, 2, '$f3 has refcount 2 after on_cancel chaining' );
+
+ $f3->done;
+ is_oneref( $f3, '$f3 has refcount 1 after done in cancel chain' );
+
my $cancelled;
$f2->on_cancel( sub { $cancelled++ } );