Skip Menu |

This queue is for tickets about the future CPAN distribution.

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

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

Bug Information
Severity: Wishlist
Broken in: 0.30
Fixed in: 0.31



Subject: Test::Future::no_pending_futures lifecycle issues
The current implementation of no_pending_futures holds on to references for all created Futures. This prevents things going out of scope - as a contrived example: no_pending_futures { Scalar::Util::weaken(my $f = Future->wrap); is($f, undef, 'Future instance disappears after use'); } '...'; Since the test is only looking for Futures that were still pending after the block returns, how about removing futures from the list once they're no longer pending? Something like ->on_ready(extract_by { ... } @futures) in the local *Future::new wrapper, perhaps. cheers, Tom
On Thu Dec 25 09:10:15 2014, TEAM wrote: Show quoted text
> Since the test is only looking for Futures that were still pending > after the block returns, how about removing futures from the list once > they're no longer pending? Something like ->on_ready(extract_by { ... > } @futures) in the local *Future::new wrapper, perhaps.
Yes. Turns out the implementation is a little more complex due to the shortcut that ->done and ->fail as class constructors take; those need special handling. Patch attached; will be in next release. -- Paul Evans
Subject: rt101128.patch
=== modified file 'lib/Test/Future.pm' --- lib/Test/Future.pm 2014-11-26 14:34:46 +0000 +++ lib/Test/Future.pm 2015-03-08 18:19:00 +0000 @@ -72,11 +72,35 @@ my @futures; + no warnings 'redefine'; + my $new = Future->can( "new" ); - no warnings 'redefine'; local *Future::new = sub { my $f = $new->(@_); push @futures, $f; + $f->on_ready( sub { + my $f = shift; + for ( 0 .. $#futures ) { + refaddr( $futures[$_] ) == refaddr( $f ) or next; + + splice @futures, $_, 1, (); + return; + } + }); + return $f; + }; + + my $done = Future->can( "done" ); + local *Future::done = sub { + my $f = $done->(@_); + pop @futures if !ref $_[0]; # class method + return $f; + }; + + my $fail = Future->can( "fail" ); + local *Future::fail = sub { + my $f = $fail->(@_); + pop @futures if !ref $_[0]; # class method return $f; }; === modified file 't/50test-future.t' --- t/50test-future.t 2014-11-15 00:17:09 +0000 +++ t/50test-future.t 2015-03-08 18:17:12 +0000 @@ -4,6 +4,7 @@ use warnings; use Test::More; +use Test::Refcount; use Test::Builder::Tester; use Future; @@ -41,6 +42,41 @@ $f->cancel; } +# does not retain Futures +{ + test_out( "ok 1 - refcount 2 before drop" ); + test_out( "ok 2 - refcount 1 after drop" ); + test_out( "ok 3 - retain" ); + + no_pending_futures { + my $arr = [1,2,3]; + my $f = Future->new; + $f->done( $arr ); + is_refcount( $arr, 2, 'refcount 2 before drop' ); + undef $f; + is_refcount( $arr, 1, 'refcount 1 after drop' ); + } 'retain'; + + test_test( "no_pending_futures does not retain completed Futures" ); +} + +# does not retain immedate Futures +{ + test_out( "ok 1 - refcount 2 before drop" ); + test_out( "ok 2 - refcount 1 after drop" ); + test_out( "ok 3 - retain" ); + + no_pending_futures { + my $arr = [1,2,3]; + my $f = Future->done( $arr ); + is_refcount( $arr, 2, 'refcount 2 before drop' ); + undef $f; + is_refcount( $arr, 1, 'refcount 1 after drop' ); + } 'retain'; + + test_test( "no_pending_futures does not retain immediate Futures" ); +} + END { # Clean up Devel::MAT dumpfile my $pmat = $0;
Released in 0.31 -- Paul Evans