File: 02cancel.pl

package info (click to toggle)
libfuture-perl 0.52-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 676 kB
  • sloc: perl: 4,636; makefile: 2
file content (195 lines) | stat: -rw-r--r-- 5,531 bytes parent folder | download | duplicates (2)
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;