File Coverage

blib/lib/POE/Callback.pm
Criterion Covered Total %
statement 125 143 87.4
branch 55 76 72.3
condition 2 2 100.0
subroutine 13 13 100.0
pod 1 1 100.0
total 196 235 83.4


line stmt bran cond sub pod time code
1             # $Id: Callback.pm 184 2009-06-11 06:44:27Z rcaputo $
2              
3             =head1 NAME
4              
5             POE::Callback - object wrapper for callbacks with lexical closures
6              
7             =head1 SYNOPSIS
8              
9             # TODO - Make this a complete working example.
10             my $callback = POE::Callback->new(
11             name => "Pkg::sub",
12             code => \&coderef,
13             );
14             $callback->(@arguments);
15              
16             =head1 DESCRIPTION
17              
18             POE::Callback wraps coderefs in magic that makes certain lexical
19             variables persistent between calls.
20              
21             It's used internally by the classes that comprise POE::Stage.
22              
23             =cut
24              
25             package POE::Callback;
26              
27 1     1   30888 use warnings;
  1         4  
  1         37  
28 1     1   5 use strict;
  1         2  
  1         79  
29              
30 1     1   774 use PadWalker qw(var_name peek_my peek_sub);
  1         2452  
  1         91  
31 1     1   10 use Scalar::Util qw(blessed reftype weaken);
  1         2  
  1         109  
32 1     1   1775 use Devel::LexAlias qw(lexalias);
  1         42543  
  1         145  
33 1     1   13 use Carp qw(croak);
  1         3  
  1         123  
34              
35             # Track our wrappers to avoid wrapping them. Otherwise hilarity may
36             # ensue.
37              
38             my %callbacks;
39 1     1   7 use constant CB_SELF => 0;
  1         1  
  1         129  
40 1     1   6 use constant CB_NAME => 1;
  1         2  
  1         1686  
41              
42             =head2 new CODEREF
43              
44             Creates a new callback from a raw CODEREF. Returns the callback,
45             which is just the CODEREF blessed into POE::Callback.
46              
47             =cut
48              
49             sub new {
50 4     4 1 498 my ($class, $arg) = @_;
51              
52 4         10 foreach my $required (qw(name code)) {
53 8 50       24 croak "POE::Callback requires a '$required'" unless $arg->{$required};
54             }
55              
56 4         8 my $code = $arg->{code};
57 4         6 my $name = $arg->{name};
58              
59             # Don't wrap callbacks.
60 4 50       15 return $code if exists $callbacks{$code};
61              
62             # Gather the names of persistent variables.
63 4         26 my $pad = peek_sub($code);
64 15 100       96 my @persistent = grep {
65 4         12 /^\$(self|req|rsp)$/ || /^([\$\@\%])(req|rsp|arg|self)_(\S+)/
66             } keys %$pad;
67              
68             # No point in the wrapper if there are no persistent variables.
69              
70 4 50       13 unless (@persistent) {
71 0         0 my $self = bless $code, $class;
72 0         0 return $self->_track($name);
73             }
74              
75 4         6 my $b_self = ''; # build $self
76 4         5 my $b_rsp = ''; # build $rsp
77 4         5 my $b_req = ''; # build $req
78 4         8 my $b_arg = ''; # build $arg
79 4         3 my $b_req_id = ''; # build $req->get_id()
80 4         5 my $b_rsp_id = ''; # build $rsp->get_id()
81              
82 4         4 my $a_self = '';
83 4         6 my $a_rsp = '';
84 4         3 my $a_req = '';
85              
86 4         4 my @vars;
87              
88 4         7 foreach my $var_name (@persistent) {
89 30 100       52 if ($var_name eq '_b_self') {
90 1         1 $b_self = q{ my $self = POE::Stage::self();};
91 1         2 next;
92             }
93              
94 29 100       42 if ($var_name eq '_b_req') {
95 2 100       5 push @persistent, '$self' unless $b_self;
96 2         3 $b_req = q{ my $req = $self->_get_request();};
97             }
98              
99 29 100       47 if ($var_name eq '_b_rsp') {
100 2 50       4 push @persistent, '$self' unless $b_self;
101 2         2 $b_rsp = q{ my $rsp = $self->_get_response(); };
102             }
103              
104 29 100       44 if ($var_name eq '$self') {
105 5 100       11 push @persistent, '_b_self' unless $b_self;
106 5         4 $a_self = q{ lexalias($code, '$self', \$self);};
107 5         6 next;
108             }
109              
110 24 100       33 if ($var_name eq '_b_rsp_id') {
111 3 100       7 push @persistent, '_b_rsp' unless $b_rsp;
112 3         3 $b_rsp_id = q{ my $rsp_id = $rsp->get_id();};
113 3         3 next;
114             }
115              
116 21 100       35 if ($var_name eq '_b_req_id') {
117 3 100       9 push @persistent, '_b_req' unless $b_req;
118 3         4 $b_req_id = q{ my $req_id = $req->get_id();};
119 3         3 next;
120             }
121              
122 18 100       28 if ($var_name eq '$req') {
123 1 50       5 push @persistent, '_b_req' unless $b_req;
124 1         2 $a_req = q{ lexalias($code, '$req', \$req);};
125 1         2 next;
126             }
127              
128 17 100       27 if ($var_name eq '$rsp') {
129 1 50       4 push @persistent, '_b_rsp' unless $b_rsp;
130 1         1 $a_rsp = q{lexalias($code, '$rsp', \$rsp);};
131 1         2 next;
132             }
133              
134 16 100       75 next unless $var_name =~ /^([\$\@\%])(req|rsp|arg|self)_(\S+)/;
135              
136 12         34 my ($sigil, $prefix, $base_member_name) = ($1, $2, $3);
137 12         37 my $member_name = $sigil . $base_member_name;
138              
139             # Arguments don't need vivification, so they come before @vivify.
140              
141 12 100       28 if ($prefix eq 'arg') {
142 6   100     18 $b_arg ||= (
143             q/ my $arg; { package DB; my @x = caller(0); $arg = $DB::args[1]; }/
144             );
145              
146 6         12 my $def = (
147             qq/ \$var_reference = \$pad->{'$var_name'};/
148             );
149              
150 6 100       12 if ($sigil eq '$') {
151 2         5 push @vars, (
152             $def,
153             qq/ \$\$var_reference = \$arg->{'$base_member_name'};/
154             );
155 2         586 next;
156             }
157              
158 4 100       9 if ($sigil eq '@') {
159 2         4 push @vars, (
160             $def,
161             qq/ \@\$var_reference = \@{\$arg->{'$base_member_name'}};/
162             );
163 2         5 next;
164             }
165              
166 2 50       6 if ($sigil eq '%') {
167 2         5 push @vars, (
168             $def,
169             qq/ \%\$var_reference = \%{\$arg->{'$base_member_name'}};/
170             );
171 2         4 next;
172             }
173             }
174              
175             # Common vivification code.
176              
177 6         12 my @vivify = ( q/ unless( defined $member_ref ) {/ );
178 6 100       16 if ($sigil eq '$') {
    100          
    50          
179 2         4 push @vivify, q( my $new_scalar; $member_ref = \$new_scalar;);
180             }
181             elsif ($sigil eq '@') {
182 2         4 push @vivify, q( $member_ref = [];);
183             }
184             elsif ($sigil eq '%') {
185 2         3 push @vivify, q( $member_ref = {};);
186             }
187              
188             # Determine which object to use based on the prefix.
189              
190 6         9 my $obj;
191 6 100       12 if ($prefix eq 'req') {
192 3 50       8 push @persistent, '_b_req_id' unless $b_req;
193              
194             # Get the existing member reference.
195 3         8 push @vars, (
196             q{ $member_ref = } .
197             q{$self->_request_context_fetch(} .
198             qq{\$req_id, '$member_name');}
199             );
200              
201             # Autovivify if necessary.
202 3         13 push @vars, (
203             @vivify,
204             q{ $self->_request_context_store(} .
205             qq{\$req_id, '$member_name', \$member_ref);},
206             q( }),
207             # Alias the member.
208             qq{ lexalias(\$code, '$var_name', \$member_ref);}
209             );
210 3         5 next;
211             }
212              
213 3 50       7 if ($prefix eq 'rsp') {
214 3 50       8 push @persistent, '_b_rsp_id' unless $b_rsp;
215 3 50       7 push @persistent, '$self' unless $b_self;
216              
217             # Get the existing member reference.
218 3         7 push @vars, (
219             q{ $member_ref = } .
220             q{$self->_request_context_fetch(}.
221             qq{\$rsp_id, '$member_name');}
222             );
223              
224             # Autovivify if necessary.
225 3         12 push @vars, (
226             @vivify,
227             q{ $self->_request_context_store(} .
228             qq{ \$rsp_id, '$member_name', \$member_ref);},
229             qq( \}),
230             # Alias the member.
231             qq{ lexalias(\$code, '$var_name', \$member_ref);}
232             );
233 3         6 next;
234             }
235              
236 0 0       0 if ($prefix eq 'self') {
237 0 0       0 push @persistent, '$self' unless $b_self;
238              
239             # Get the existing member reference.
240 0         0 push @vars, (
241             qq{\$member_ref = \$self->_self_fetch('$member_name');}
242             );
243              
244             # Autovivify if necessary.
245 0         0 push @vars, (
246             @vivify,
247             qq{ \$self->_self_store('$member_name', \$member_ref);},
248             qq( \}),
249             # Alias the member.
250             qq{ lexalias(\$code, '$var_name', \$member_ref);}
251             );
252              
253 0         0 next;
254             }
255             }
256              
257 4         35 unshift @vars, (
258             $b_self, $b_arg, $b_req, $b_rsp, $b_req_id, $b_rsp_id,
259             $a_self, $a_rsp, $a_req,
260             );
261              
262 4         28 my $sub = join "\n", (
263             "sub {",
264             " my \$pad = peek_sub(\$code);",
265             " my (\$member_ref, \$var_reference);",
266             @vars,
267             " goto \$code;",
268             "};"
269             );
270             #warn $sub; # for debugging generated code
271 4         1133 my $coderef = eval $sub;
272 4 50       13 if( $@ ) {
273 0         0 while( $@ =~ /line (\d+)/g ) {
274 0         0 my $line = $1;
275 0         0 for( ($line-10) .. $line-4 ) {
276 0         0 warn $_+4, ": $vars[$_]\n";
277             }
278             }
279 0         0 die $@;
280             }
281              
282 4         10 my $self = bless $coderef, $class;
283 4         12 return $self->_track($name);
284             }
285              
286             # Track a callback so we don't accidentally wrap it.
287              
288             sub _track {
289 4     4   6 my ($self, $name) = @_;
290 4         20 $callbacks{$self} = [
291             $self, # CB_SELF
292             $name, # CB_NAME
293             ];
294 4         17 weaken($callbacks{$self}[CB_SELF]);
295 4         33 return $self;
296             }
297              
298             # When the callback object is destroyed, it's also removed from the
299             # tracking hash.
300              
301             sub DESTROY {
302 4     4   922 my $self = shift;
303 4 50       18 warn "!!! Destroying untracked callback $self" unless (
304             exists $callbacks{$self}
305             );
306 4         314 delete $callbacks{$self};
307             }
308              
309             # End-of-run leak checking.
310              
311             END {
312 1     1   3 my @leaks;
313 1         7 foreach my $callback (sort keys %callbacks) {
314 1     1   7 no strict 'refs';
  1         4  
  1         206  
315 0         0 my $cb_name = $callbacks{$callback}[CB_NAME];
316 0 0       0 next if *{$cb_name}{CODE} == $callbacks{$callback}[CB_SELF];
  0         0  
317 0         0 push @leaks, "!!! $callback = $cb_name\n";
318             }
319 1 50       10 if (@leaks) {
320 0         0 warn "\n!!! callback leak:";
321 0         0 warn @leaks;
322             }
323             }
324              
325             1;