File Coverage

blib/lib/Sub/Deferrable.pm
Criterion Covered Total %
statement 51 51 100.0
branch 14 14 100.0
condition 2 2 100.0
subroutine 11 11 100.0
pod 6 6 100.0
total 84 84 100.0


line stmt bran cond sub pod time code
1             package Sub::Deferrable;
2              
3 2     2   26920 use warnings;
  2         4  
  2         75  
4 2     2   17 use strict;
  2         4  
  2         1237  
5              
6             =head1 NAME
7              
8             Sub::Deferrable - Optionally queue sub invocations for later.
9              
10             =head1 VERSION
11              
12             Version 0.05
13              
14             =cut
15              
16             our $VERSION = '0.05';
17              
18             =head1 SYNOPSIS
19              
20             use Sub::Deferrable;
21             my $queue = Sub::Deferrable->new();
22             my $sub = $queue->make_deferrable( \&some_sub );
23             $sub->(@args); # Executes immediately
24             $queue->defer;
25             $sub->(@more_args); # Not executed
26             $sub->(@yet_more_args); # Not executed
27             $queue->undefer; # Both calls now executed synchronously;
28             # subsequent calls execute immediately.
29              
30             Sub::Deferrable provides methods for wrapping a sub reference,
31             giving it a split personality. In "normal" mode the wrapper simply
32             calls the sub, passing along any arguments. In "deferred" mode, the
33             wrapper creates an invocation object and saves it on a queue. When
34             the queue is returned to "normal" mode, all invocations on the queue
35             are executed immediately.
36              
37             =head1 EXPORT
38              
39             No exports.
40              
41             =head1 METHODS
42              
43             =head2 new
44              
45             Returns a new Sub::Deferrable object with an empty queue.
46              
47             =cut
48              
49             sub new {
50 1     1 1 1005 my $class = shift;
51 1         5 my $self = { deferring => 0, queue => [] };
52 1         4 bless $self, $class;
53 1         11 return $self;
54             }
55              
56             =head2 $self->mk_deferrable( \&some_sub )
57              
58             Returns a new sub reference, which normally behaves like \&some_sub, but
59             which saves an invocation of \&some_sub on the queue when in "deferred"
60             mode.
61              
62             An optional extra argument provides a sub reference to be applied
63             to the invocation arguments I. If this argument
64             is supplied, it is probably a reference to C,
65             which will create a deep copy of the arguments and so break any
66             reference pointers. This might be needed if, say, the arguments at
67             invocation time might change before the queued sub is run.
68              
69             =cut
70              
71             sub mk_deferrable {
72 6     6 1 1671 my $self = shift;
73 6         9 my $sub = shift;
74 6   100 5   55 my $transform = shift || sub {return shift};
  5         9  
75             sub {
76 23 100   23   445 my $args = @_ ? $transform->(\@_) : undef;
77 23 100       53 if ($self->deferring) {
78 18         24 push @{$self->{queue}}, [$sub, $args];
  18         651  
79             }
80             else {
81 5 100       21 defined $args ? $sub->(@$args) : $sub->();
82             }
83 6         46 };
84             }
85              
86             =head2 $self->deferring
87              
88             Returns I when deferrable subs are queued; I when they are
89             than invoked immediately.
90              
91             =cut
92              
93             sub deferring {
94 25     25 1 35 my $self = shift;
95 25         78 return $self->{deferring};
96             }
97              
98             =head2 $self->defer
99              
100             Stop executing deferrable subs, and start queueing them instead. Repeated
101             calls to $self->defer are equivalent to a single call; in particular, one
102             call to $self->undefer will turn off deferral mode.
103              
104             =cut
105              
106             sub defer {
107 6     6 1 24 my $self = shift;
108 6         20 $self->{deferring} = 1;
109             }
110              
111             =head2 $self->undefer
112              
113             Stop queueing subs, and start executing them immediately. Any subs already
114             queued are executed before undefer() returns.
115              
116             =cut
117              
118             sub undefer {
119 9     9 1 10112 my $self = shift;
120 9         26 $self->{deferring} = 0;
121 9 100       13 return unless @{$self->{queue}};
  9         140  
122              
123             # This tortured way of doing the loop is (surpringly) significantly faster.
124 6         10 my $died = 0;
125 6         11 my $final_idx = $#{$self->{queue}};
  6         17  
126 6         10 for my $idx (0..$#{$self->{queue}}) {
  6         25  
127 13         18 my ($sub, $args) = @{$self->{queue}[$idx]};
  13         33  
128              
129             # Only way to return false is to die...
130 13 100       18 my $status = eval { defined $args ? $sub->(@$args) : $sub->(); 1 };
  13         45  
  10         46  
131 13 100       67 do { $died = 1; $final_idx = $idx; last } unless $status;
  3         6  
  3         13  
  3         7  
132             }
133              
134 6         24 splice @{$self->{queue}}, 0, $final_idx+1;
  6         28  
135 6 100       38 die $@ if $died;
136              
137 3         12 return;
138             }
139              
140             =head2 $self->cancel
141              
142             Stop queueing subs, but discard any subs already queued.
143              
144             =cut
145              
146             sub cancel {
147 1     1 1 1111 my $self = shift;
148 1         4 $self->{queue} = [];
149 1         7 $self->undefer;
150             }
151              
152             =head2 DESTROY
153              
154             On destruction, all queued subs are invoked. This is a failsafe;
155             please do not write code that relies on this behavior. By the time
156             this object is destroyed, it's likely too late to invoke your subs
157             anyway, so this will probably crash your app. As you so richly
158             deserve.
159              
160             =cut
161              
162             sub DESTROY {
163 1     1   934 my $self = shift;
164 1         4 $self->undefer;
165             }
166              
167             =head1 AUTHOR
168              
169             Budney, Len, C<< >>
170              
171             =head1 BUGS
172              
173             Not all subs are deferrable, by their nature. If the sub interacts
174             with an open file or socket, for example, execution may fail later
175             because the file or socket is closed. Presumably, you thought of
176             that before you decided to make your sub deferrable.
177              
178              
179             =head1 ACKNOWLEDGEMENTS
180              
181             =head1 COPYRIGHT & LICENSE
182              
183             Copyright 2005-2012 Grant Street Group. All rights reserved.
184              
185             This program is free software; you can redistribute it and/or modify it
186             under the same terms as Perl itself.
187              
188             =cut
189              
190             1; # End of Sub::Deferrable