File Coverage

blib/lib/App/mirai/Future.pm
Criterion Covered Total %
statement 113 117 96.5
branch 12 16 75.0
condition 4 6 66.6
subroutine 21 22 95.4
pod 2 5 40.0
total 152 166 91.5


line stmt bran cond sub pod time code
1             package App::mirai::Future;
2             $App::mirai::Future::VERSION = '0.003';
3 1     1   14753 use strict;
  1         2  
  1         35  
4 1     1   4 use warnings;
  1         1  
  1         25  
5              
6             =head1 NAME
7              
8             App::mirai::Future - injects debugging code into L
9              
10             =head1 VERSION
11              
12             version 0.003
13              
14             =head1 DESCRIPTION
15              
16             On load, this will monkey-patch L to provide various precarious
17             hooks for Future-related events.
18              
19             =cut
20              
21 1     1   574 use Future;
  1         11373  
  1         41  
22 1     1   8 use Time::HiRes ();
  1         3  
  1         18  
23 1     1   5 use Scalar::Util ();
  1         2  
  1         16  
24 1     1   647 use List::UtilsBy ();
  1         1780  
  1         30  
25              
26 1     1   7 use Carp qw(cluck);
  1         1  
  1         81  
27              
28 1     1   479 use App::mirai::Watcher;
  1         2  
  1         25  
29              
30             # Elapsed time is important to us, even though we could leave this off and
31             # track it ourselves
32 1     1   168 BEGIN { $Future::TIMES = 1 }
33              
34             our %FUTURE_MAP;
35             our @WATCHERS;
36              
37             =head1 create_watcher
38              
39             Returns a new L.
40              
41             my $watcher = App::mirai::Future->create_watcher;
42             $watcher->subscribe_to_event(
43             create => sub { my ($ev, $f) = @_; warn "Created new future: $f\n" },
44             );
45              
46             =cut
47              
48             sub create_watcher {
49 5     5 0 507 my $class = shift;
50 5         17 push @WATCHERS, my $w = App::mirai::Watcher->new;
51 5 50       12 $w->subscribe_to_event(@_) if @_;
52 5         8 $w
53             }
54              
55             =head1 delete_watcher
56              
57             Deletes the given watcher.
58              
59             my $watcher = App::mirai::Future->create_watcher;
60             App::mirai::Future->delete_watcher($watcher);
61              
62             =cut
63              
64             sub delete_watcher {
65 4     4 0 5 my ($class, $w) = @_;
66 4         9 $w = Scalar::Util::refaddr $w;
67 4     7   22 List::UtilsBy::extract_by { Scalar::Util::refaddr($_) eq $w } @WATCHERS;
  7         44  
68             ()
69 4         25 }
70              
71             =head2 future
72              
73             Returns information about the given L instance.
74              
75             =cut
76              
77 4     4 1 1729 sub future { $FUTURE_MAP{$_[1]} }
78              
79             =head1 futures
80              
81             Returns all the Futures we know about.
82              
83             =cut
84              
85             sub futures {
86 7     7 0 2949 grep defined, map $_->{future}, sort values %FUTURE_MAP
87             }
88              
89             =head1 MONKEY PATCHES
90              
91             These reach deep into L and are likely to break any time a new version
92             is released.
93              
94             =cut
95              
96 1     1   5 { no warnings 'redefine';
  1         1  
  1         769  
97              
98             =head2 Future::DESTROY
99              
100             Hook destruction so we know when a L is going away.
101              
102             =cut
103              
104             sub Future::DESTROY {
105 10     10   1704 my $f = shift;
106             # my $f = $destructor->(@_);
107 10         35 $_->invoke_event(destroy => $f) for grep defined, @WATCHERS;
108 10         403 my $entry = delete $FUTURE_MAP{$f};
109 10         86 $f
110             }
111              
112             =head2 Future::set_label
113              
114             Pick up any label changes, since Ls are created without them.
115              
116             =cut
117              
118             sub Future::set_label {
119 0     0 1   my $f = shift;
120 0           ( $f->{label} ) = @_;
121 0           $_->invoke_event(label => $f) for grep defined, @WATCHERS;
122 0           return $f;
123             }
124             }
125              
126             BEGIN {
127             my $prep = sub {
128 11         11 my $f = shift;
129              
130             # Grab the stacktrace first, so we know who started this
131 11         50 my (undef, $file, $line) = caller(1);
132 11         14 my $stack = do {
133 11         10 my @stack;
134 11         9 my $idx = 1;
135 11         55 while(my @x = caller($idx++)) {
136 23         121 unshift @stack, [ @x[0, 1, 2] ];
137             }
138             \@stack
139 11         14 };
140              
141             # I don't know why this is here.
142 11 100       31 if(exists $FUTURE_MAP{$f}) {
143 1 50       5 $FUTURE_MAP{$f}{type} = (exists $f->{subs} ? 'dependent' : 'leaf');
144 1         2 return $f;
145             }
146              
147             # We don't use this either
148 10         7 $f->{constructed_at} = do {
149 10         989 my $at = Carp::shortmess( "constructed" );
150 10         16 chomp $at; $at =~ s/\.$//;
  10         46  
151 10         18 $at
152             };
153              
154             # This is our record, we'll update it when we're marked as ready
155 10 50       55 my $entry = {
156             future => $f,
157             deps => [ ],
158             type => (exists $f->{subs} ? 'dependent' : 'leaf'),
159             created_at => "$file:$line",
160             creator_stack => $stack,
161             status => 'pending',
162             };
163              
164             # ... but we don't want to hold on to the real Future and cause cycles,
165             # memory isn't free
166 10         23 Scalar::Util::weaken($entry->{future});
167              
168 10         13 my $name = "$f";
169 10         18 $FUTURE_MAP{$name} = $entry;
170              
171             # Yes, this means we're modifying the callback list: if we later
172             # add support for debugging the callbacks as well, we'd need to
173             # take this into account.
174             $f->on_ready(sub {
175 7         2079 my $f = shift;
176 7         32 my (undef, $file, $line) = caller(2);
177 7 100       28 $FUTURE_MAP{$f}->{status} =
    100          
178             $f->{failure}
179             ? "failed"
180             : $f->{cancelled}
181             ? "cancelled"
182             : "done";
183 7         20 $FUTURE_MAP{$f}->{ready_at} = "$file:$line";
184 7         6 $FUTURE_MAP{$f}->{ready_stack} = do {
185 7         6 my @stack;
186 7         8 my $idx = 1;
187 7         31 while(my @x = caller($idx++)) {
188 22         110 unshift @stack, [ @x[0,1,2] ];
189             }
190             \@stack
191 7         18 };
192              
193             # who's in charge of picking names around here? do we not have
194             # any interest in consistency?
195 7         30 $_->invoke_event(on_ready => $f) for grep defined, @WATCHERS;
196 10         52 });
197 1     1   5 };
198              
199             my %map = (
200             # Creating a leaf Future, or called via _new_dependent
201             new => sub {
202 1         2 my $constructor = shift;
203             sub {
204 10     10   992 my $f = $constructor->(@_);
205 10         88 $prep->($f);
206             # hahaha
207 10         147 my ($sub) = (caller 1)[3];
208             # no, seriously?
209 10 100 66     51 unless($sub && ($sub eq 'Future::_new_dependent' or $sub eq 'Future::_new_convergent')) {
      66        
210 9         38 $_->invoke_event(create => $f) for grep defined, @WATCHERS;
211             }
212             $f
213 1         3 };
  10         546  
214             },
215              
216             # ->needs_all, ->want_any, etc.
217             _new_dependent => sub {
218 2         3 my $constructor = shift;
219             sub {
220 1     1   465 my @subs = @{$_[1]};
  1         3  
221 1         4 my $f = $constructor->(@_);
222 1         19 $prep->($f);
223 1         2 my $entry = $FUTURE_MAP{$f};
224 1         2 $entry->{subs} = \@subs;
225             # Inform subs that they have a new parent
226 1         2 for(@subs) {
227 3 50       7 die "missing future map entry for $_?" unless exists $FUTURE_MAP{$_};
228 3         3 push @{$FUTURE_MAP{$_}{deps}}, $f;
  3         6  
229 3         8 Scalar::Util::weaken($FUTURE_MAP{$_}{deps}[-1]);
230             }
231 1         6 $_->invoke_event(create => $f) for grep defined, @WATCHERS;
232 1         38 $f
233 2         6 };
234             },
235 1         5 );
236             # Changed in Future 0.30, I believe
237 1         2 $map{_new_convergent} = $map{_new_dependent};
238              
239 1         3 for my $k (keys %map) {
240 3         17 my $orig = Future->can($k);
241 3         4 my $code = $map{$k}->($orig);
242             {
243 1     1   5 no strict 'refs';
  1         1  
  1         57  
  3         4  
244 1     1   5 no warnings 'redefine';
  1         1  
  1         44  
245 3         2 *{'Future::' . $k} = $code;
  3         29  
246             }
247             }
248             }
249              
250             1;
251              
252             __END__