1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
|
use v5.10;
use strict;
use warnings;
use Test2::V0 0.000148; # is_refcount
use Future;
# cancel
{
my $future = Future->new;
my $cancelled;
ref_is( $future->on_cancel( sub { $cancelled .= "1" } ), $future, '->on_cancel returns $future' );
$future->on_cancel( sub { $cancelled .= "2" } );
my $ready;
$future->on_ready( sub { $ready++ if shift->is_cancelled } );
$future->on_done( sub { die "on_done called for cancelled future" } );
$future->on_fail( sub { die "on_fail called for cancelled future" } );
$future->on_ready( my $ready_f = Future->new );
$future->on_done( my $done_f = Future->new );
$future->on_fail( my $fail_f = Future->new );
$future->cancel;
ok( $future->is_ready, '$future->cancel marks future ready' );
ok( $future->is_cancelled, '$future->cancelled now true' );
is( $cancelled, "21", '$future cancel blocks called in reverse order' );
is( $ready, 1, '$future on_ready still called by cancel' );
ok( $ready_f->is_cancelled, 'on_ready chained future cnacelled after cancel' );
ok( !$done_f->is_ready, 'on_done chained future not ready after cancel' );
ok( !$fail_f->is_ready, 'on_fail chained future not ready after cancel' );
is( $future->state, "cancelled", '$future->state after ->cancel' );
like( dies { $future->result }, qr/cancelled/, '$future->result throws exception by cancel' );
is( dies { $future->cancel }, undef,
'$future->cancel a second time is OK' );
$done_f->cancel;
$fail_f->cancel;
}
# immediately cancelled
{
my $future = Future->new;
$future->cancel;
my $ready_called;
$future->on_ready( sub { $ready_called++ } );
my $done_called;
$future->on_done( sub { $done_called++ } );
my $fail_called;
$future->on_fail( sub { $fail_called++ } );
$future->on_ready( my $ready_f = Future->new );
$future->on_done( my $done_f = Future->new );
$future->on_fail( my $fail_f = Future->new );
is( $ready_called, 1, 'on_ready invoked for already-cancelled future' );
ok( !$done_called, 'on_done not invoked for already-cancelled future' );
ok( !$fail_called, 'on_fail not invoked for already-cancelled future' );
ok( $ready_f->is_cancelled, 'on_ready chained future cnacelled for already-cancelled future' );
ok( !$done_f->is_ready, 'on_done chained future not ready for already-cancelled future' );
ok( !$fail_f->is_ready, 'on_fail chained future not ready for already-cancelled future' );
$done_f->cancel;
$fail_f->cancel;
}
# cancel chaining
{
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++ } );
$f1->cancel;
is( $cancelled, 1, 'Chained cancellation' );
}
# ->done on cancelled
{
my $f = Future->new;
$f->cancel;
ok( eval { $f->done( "ignored" ); 1 }, '->done on cancelled future is ignored' );
ok( eval { $f->fail( "ignored" ); 1 }, '->fail on cancelled future is ignored' );
}
# without_cancel
{
my $f1 = Future->new;
is_oneref( $f1, '$f1 has single reference initially' );
my $f2 = $f1->without_cancel;
is_refcount( $f1, 2, '$f1 has two references after ->without_cancel' );
$f2->cancel;
ok( !$f1->is_cancelled, '$f1 not cancelled just because $f2 is' );
my $f3 = $f1->without_cancel;
$f1->done( "result" );
ok( $f3->is_ready, '$f3 ready when $f1 is' );
is( [ $f3->result ], [ "result" ], 'result of $f3' );
is_oneref( $f1, '$f1 has one reference after done' );
$f1 = Future->new;
$f2 = $f1->without_cancel;
$f1->cancel;
ok( $f2->is_cancelled, '$f1 cancelled still cancels $f2' );
}
# references are not retained in callbacks
{
my $guard = {};
my $future = Future->new;
is_oneref( $guard, '$guard has refcount 1 before ->on_cancel' );
$future->on_cancel( do { my $ref = $guard; sub { $ref = $ref } } );
is_refcount( $guard, 2, '$guard has refcount 2 after ->on_cancel' );
$future->cancel;
is_oneref( $guard, '$guard has refcount 1 after ->cancel' );
}
# test amortized compaction
SKIP: {
my $f = Future->new;
my @subf;
skip "Future is not a Future::PP", 4 unless Future->isa( "Future::PP" );
push @subf, Future->new and $f->on_cancel( $subf[-1] ) for 1 .. 100;
# gutwrench
is( scalar @{ $f->{on_cancel} }, 100, '$f on_cancel list is 100 items initially' );
# We should be able to cancel the first 49 of these without triggering a compaction
$_->done for @subf[0..48];
# gutwrench
is( scalar @{ $f->{on_cancel} }, 100, '$f on_cancel list still 100 items' );
# Cancelling the next one will cause a compaction
$_->done for $subf[49];
# gutwrench
is( scalar @{ $f->{on_cancel} }, 50, '$f on_cancel list now only 50 items' );
# Cancelling most of the rest will compact again
$_->done for @subf[50..90];
# gutwrench
is( scalar @{ $f->{on_cancel} }, 12, '$f on_cancel list now only 12 items' );
$f->cancel;
}
# test that undef'ed futures in either direction do not segfault the XS impl
{
my $f1 = Future->new->set_label("f1");
$f1->on_cancel( my $f2 = Future->new->set_label("f2") );
undef $f2;
$f1->cancel;
}
done_testing;
|