File Coverage

blib/lib/App/mirai/Future.pm
Criterion Covered Total %
statement 112 116 96.5
branch 12 16 75.0
condition 3 3 100.0
subroutine 21 22 95.4
pod 2 5 40.0
total 150 162 92.5


line stmt bran cond sub pod time code
1             package App::mirai::Future;
2             $App::mirai::Future::VERSION = '0.002';
3 1     1   27426 use strict;
  1         3  
  1         46  
4 1     1   7 use warnings;
  1         2  
  1         37  
5              
6             =head1 NAME
7              
8             App::mirai::Future - injects debugging code into L
9              
10             =head1 VERSION
11              
12             Version 0.002
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   1148 use Future;
  1         16222  
  1         37  
22 1     1   10 use Time::HiRes ();
  1         2  
  1         15  
23 1     1   5 use Scalar::Util ();
  1         2  
  1         13  
24 1     1   829 use List::UtilsBy ();
  1         4637  
  1         31  
25              
26 1     1   11 use Carp qw(cluck);
  1         2  
  1         87  
27              
28 1     1   777 use App::mirai::Watcher;
  1         3  
  1         30  
29              
30             # Elapsed time is important to us, even though we could leave this off and
31             # track it ourselves
32 1     1   184 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 407 my $class = shift;
50 5         21 push @WATCHERS, my $w = App::mirai::Watcher->new;
51 5 50       13 $w->subscribe_to_event(@_) if @_;
52 5         10 $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 9 my ($class, $w) = @_;
66 4         10 $w = Scalar::Util::refaddr $w;
67 4     7   25 List::UtilsBy::extract_by { Scalar::Util::refaddr($_) eq $w } @WATCHERS;
  7         61  
68             ()
69 4         33 }
70              
71             =head2 future
72              
73             Returns information about the given L instance.
74              
75             =cut
76              
77 4     4 1 2056 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 3231 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         2  
  1         846  
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   1746 my $f = shift;
106             # my $f = $destructor->(@_);
107 10         42 $_->invoke_event(destroy => $f) for grep defined, @WATCHERS;
108 10         450 my $entry = delete $FUTURE_MAP{$f};
109 10         106 $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         12 my $f = shift;
129              
130             # Grab the stacktrace first, so we know who started this
131 11         63 my (undef, $file, $line) = caller(1);
132 11         19 my $stack = do {
133 11         10 my @stack;
134 11         14 my $idx = 1;
135 11         68 while(my @x = caller($idx++)) {
136 23         152 unshift @stack, [ @x[0, 1, 2] ];
137             }
138             \@stack
139 11         18 };
140              
141             # I don't know why this is here.
142 11 100       35 if(exists $FUTURE_MAP{$f}) {
143 1 50       5 $FUTURE_MAP{$f}{type} = (exists $f->{subs} ? 'dependent' : 'leaf');
144 1         3 return $f;
145             }
146              
147             # We don't use this either
148 10         11 $f->{constructed_at} = do {
149 10         1122 my $at = Carp::shortmess( "constructed" );
150 10         16 chomp $at; $at =~ s/\.$//;
  10         35  
151 10         20 $at
152             };
153              
154             # This is our record, we'll update it when we're marked as ready
155 10 50       59 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         29 Scalar::Util::weaken($entry->{future});
167              
168 10         16 my $name = "$f";
169 10         21 $FUTURE_MAP{$name} = $entry;
170              
171             # Yes, this means we're modifying the callback list: if we later
172             # add suppoort for debugging the callbacks as well, we'd need to
173             # take this into account.
174             $f->on_ready(sub {
175 7         2641 my $f = shift;
176 7         37 my (undef, $file, $line) = caller(2);
177 7 100       38 $FUTURE_MAP{$f}->{status} =
    100          
178             $f->{failure}
179             ? "failed"
180             : $f->{cancelled}
181             ? "cancelled"
182             : "done";
183 7         30 $FUTURE_MAP{$f}->{ready_at} = "$file:$line";
184 7         8 $FUTURE_MAP{$f}->{ready_stack} = do {
185 7         9 my @stack;
186 7         10 my $idx = 1;
187 7         47 while(my @x = caller($idx++)) {
188 22         195 unshift @stack, [ @x[0,1,2] ];
189             }
190             \@stack
191 7         30 };
192              
193             # who's in charge of picking names around here? do we not have
194             # any interest in consistency?
195 7         37 $_->invoke_event(on_ready => $f) for grep defined, @WATCHERS;
196 10         55 });
197 1     1   8 };
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   1094 my $f = $constructor->(@_);
205 10         111 $prep->($f);
206             # hahaha
207 10         94 my ($sub) = (caller 1)[3];
208 10 100 100     37 unless($sub && $sub eq 'Future::_new_dependent') {
209 9         45 $_->invoke_event(create => $f) for grep defined, @WATCHERS;
210             }
211             $f
212 1         3 };
  10         572  
213             },
214              
215             # ->needs_all, ->want_any, etc.
216             _new_dependent => sub {
217 1         2 my $constructor = shift;
218             sub {
219 1     1   566 my @subs = @{$_[1]};
  1         3  
220 1         5 my $f = $constructor->(@_);
221 1         15 $prep->($f);
222 1         2 my $entry = $FUTURE_MAP{$f};
223 1         3 $entry->{subs} = \@subs;
224             # Inform subs that they have a new parent
225 1         2 for(@subs) {
226 3 50       7 die "missing future map entry for $_?" unless exists $FUTURE_MAP{$_};
227 3         4 push @{$FUTURE_MAP{$_}{deps}}, $f;
  3         7  
228 3         10 Scalar::Util::weaken($FUTURE_MAP{$_}{deps}[-1]);
229             }
230 1         6 $_->invoke_event(create => $f) for grep defined, @WATCHERS;
231 1         49 $f
232 1         5 };
233             },
234 1         6 );
235              
236 1         3 for my $k (keys %map) {
237 2         13 my $orig = Future->can($k);
238 2         10 my $code = $map{$k}->($orig);
239             {
240 1     1   5 no strict 'refs';
  1         2  
  1         29  
  2         3  
241 1     1   4 no warnings 'redefine';
  1         2  
  1         85  
242 2         2 *{'Future::' . $k} = $code;
  2         31  
243             }
244             }
245             }
246              
247             1;
248              
249             __END__