File Coverage

blib/lib/Alarm/Concurrent.pm
Criterion Covered Total %
statement 30 101 29.7
branch 3 40 7.5
condition 2 43 4.6
subroutine 9 16 56.2
pod 6 6 100.0
total 50 206 24.2


line stmt bran cond sub pod time code
1             package Alarm::Concurrent;
2              
3             $VERSION = 1.0;
4              
5 1     1   27425 use strict;
  1         2  
  1         86  
6              
7             =head1 NAME
8              
9             Alarm::Concurrent - Allow multiple, concurrent alarms.
10              
11             =head1 DESCRIPTION
12              
13             This module is an attempt to enhance Perl's built-in
14             alarm/C<$SIG{ALRM}> functionality.
15              
16             This function, and its associated signal handler, allow you
17             to arrange for your program to receive a SIGALRM signal,
18             which you can then catch and deal with appropriately.
19              
20             Unfortunately, due to the nature of the design of these
21             signals (at the OS level), you can only have one alarm
22             and handler active at any given time. That's where this
23             module comes in.
24              
25             This module allows you to define multiple alarms, each
26             with an associated handler. These alarms are sequenced
27             (in a queue) but concurrent, which means that their order
28             is preserved but they always go off as their set time
29             expires, regardless of the state of the other alarms.
30             (If you'd like to have the alarms only go off in the order
31             you set them, see Alarm::Queued.)
32              
33             To set an alarm, call the C function with the
34             set time of the alarm and a reference to the subroutine
35             to be called when the alarm goes off. You can then go on
36             with your program and the alarm will be called after the
37             set time has passed.
38              
39             It is also possible to set an alarm that does
40             not have a handler associated with it using
41             C. (This function can also
42             be imported into your namespace, in which case it will
43             replace Perl's built-in alarm for your package only.)
44              
45             If an alarm that does not have a handler associated
46             with it goes off, the default handler, pointed to by
47             C<$Alarm::Concurrent::DEFAULT_HANLDER>, is called. You can
48             change the default handler by assigning to this variable.
49              
50             The default C<$Alarm::Concurrent::DEFAULT_HANDLER> simply
51             dies with the message "Alarm clock!\n".
52              
53             =head1 IMPORT/EXPORT
54              
55             No methods are exported by default but you can import any
56             of the functions in the L section.
57              
58             You can also import the special tag C<:ALL> which will import
59             all the functions in the L section
60             (except C).
61              
62             =head1 OVERRIDE
63              
64             If you import the special tag C<:OVERRIDE>, this module
65             will override Perl's built-in alarm function for
66             B and it will take over Perl's magic
67             C<%SIG> variable, changing any attempts to read or
68             write C<$SIG{ALRM}> into calls to C and
69             C, respectively (reading and writing to
70             other keys in C<%SIG> is unaffected).
71              
72             This can be useful when you are calling code that tries to
73             set its own alarm "the old fashioned way." It can also,
74             however, be dangerous. Overriding alarm is documented
75             and should be stable but taking over C<%SIG> is more risky
76             (see L).
77              
78             Note that if you do I override alarm and
79             C<%SIG>, any code you use that sets "legacy alarms"
80             will disable all of your concurrent alarms. You can
81             call C to reinstall the
82             Alarm::Concurrent handler. This function can not be
83             imported.
84              
85             =cut
86              
87             # In case they want to take over $SIG{ALRM}.
88 1     1   445 use Alarm::_TieSIG;
  1         2  
  1         20  
89 1     1   4 use Carp;
  1         1  
  1         45  
90              
91 1     1   6 use Exporter;
  1         1  
  1         35  
92 1     1   4 use vars qw( @ISA @EXPORT_OK %EXPORT_TAGS );
  1         2  
  1         173  
93             @ISA = qw(Exporter);
94             @EXPORT_OK = qw(
95             setalarm
96             clearalarm
97             alarm
98             sethandler
99             gethandler
100             );
101             %EXPORT_TAGS = (
102             ALL => [@EXPORT_OK],
103             );
104              
105             #
106             # Exporter doesn't allow hooks for handling
107             # special tags. So, we have to do it ourselves.
108             #
109             sub import {
110 1     1   8 my $thispkg = shift;
111              
112             # Look for and remove special :OVERRIDE tag.
113 1         1 my $override = 0;
114 1 0       2 @_ = grep { ($_ eq ':OVERLOAD') ? ($override = 1, 0) : 1 } @_;
  0         0  
115              
116 1 50       3 if($override) {
117 0         0 $thispkg->export('CORE::GLOBAL', 'alarm');
118 0         0 Alarm::_TieSIG::tiesig(); # ALL YOUR %SIG ARE BELONG TO US!!!
119             };
120              
121 1         72 $thispkg->export_to_level(1, $thispkg, @_); # export the rest
122             }
123              
124             # Called for an alarm with no defined handler.
125             sub _default_handler {
126 0     0   0 die "Alarm clock!\n";
127             }
128              
129 1     1   4 use vars '$DEFAULT_HANDLER';
  1         1  
  1         48  
130             $DEFAULT_HANDLER = \&_default_handler; # Overeridable.
131              
132             #
133             # Each element of @ALARM_QUEUE should be a pointer
134             # to an array containing exactly four elements:
135             #
136             # 0) The duration of the alarm in seconds
137             # 1) The time at which the alarm was set
138             # 2) A pointer to a subroutine that should be called
139             # when the alarm goes off.
140             #
141 1     1   4 use vars qw( @ALARM_QUEUE $ACTIVE_ALARM );
  1         1  
  1         1028  
142             @ALARM_QUEUE = ();
143             $ACTIVE_ALARM = -1;
144              
145             # Install our alarm handler. (& is to avoid prototype warning)
146             &restore(1);
147              
148             # Custom alarm handler.
149             sub _alrm {
150 0 0   0   0 return unless(@ALARM_QUEUE);
151              
152             # Call handler for this alarm and remove it from the queue.
153 0         0 my $handler = splice(@ALARM_QUEUE, $ACTIVE_ALARM, 1)->[2];
154 0   0     0 $handler ||= $DEFAULT_HANDLER;
155 0         0 $handler->();
156 0         0 $ACTIVE_ALARM = -1;
157              
158             # Have to use a C-style loop because we modify
159             # the index variable inside the loop. :-(
160 0         0 for(my $i = 0; $i < @ALARM_QUEUE; ++$i) {
161 0         0 my $time_remaining = $ALARM_QUEUE[$i][1]+$ALARM_QUEUE[$i][0]-time;
162              
163 0         0 my $active_time;
164 0         0 $active_time = $ALARM_QUEUE[$ACTIVE_ALARM][1];
165 0         0 $active_time += $ALARM_QUEUE[$ACTIVE_ALARM][0];
166 0         0 $active_time -= time;
167              
168 0 0 0     0 if($time_remaining <= 0) {
    0          
169              
170             # Note the -- on $i. When we splice out an alarm, all the ones
171             # after it shift down by one. We have to account for this.
172 0   0     0 $handler = splice(@ALARM_QUEUE, $i--, 1)->[2] || $DEFAULT_HANDLER;
173 0         0 $handler->(); # Call handler for this alarm.
174              
175             } elsif($ACTIVE_ALARM == -1 or $time_remaining < $active_time) {
176 0         0 $ACTIVE_ALARM = $i;
177 0         0 CORE::alarm($time_remaining);
178             }
179             }
180             }
181              
182              
183             #********************************************************************#
184              
185             =head1 FUNCTIONS
186              
187             The following functions are available for use.
188              
189             =over 4
190              
191             =item setalarm SECONDS CODEREF
192              
193             Sets a new alarm and associates a handler with it.
194             The handler is called when the specified number of seconds
195             have elapsed. See L for more
196             information.
197              
198             =cut
199             sub setalarm($$) {
200 0     0 1 0 my ($alarm, $code) = @_;
201              
202 0 0 0     0 unless(not defined($code) or UNIVERSAL::isa($code, 'CODE')) {
203 0         0 croak("Alarm handler must be CODEREF");
204             }
205              
206 0         0 push( @ALARM_QUEUE, [ $alarm, time(), $code ] );
207              
208 0         0 my $time_remaining;
209 0         0 $time_remaining = $ALARM_QUEUE[$ACTIVE_ALARM][1];
210 0         0 $time_remaining += $ALARM_QUEUE[$ACTIVE_ALARM][0];
211 0         0 $time_remaining -= time;
212              
213 0 0 0     0 if($ACTIVE_ALARM == -1 or $alarm < $time_remaining) {
214 0         0 $ACTIVE_ALARM = $#ALARM_QUEUE;
215 0         0 CORE::alarm($alarm);
216             }
217             }
218              
219             =item clearalarm INDEX LENGTH
220              
221             =item clearalarm INDEX
222              
223             =item clearalarm
224              
225             Clears one or more previously set alarms. The index is
226             an array index, with 0 being the currently active alarm
227             and -1 being the last (most recent) alarm that was set.
228              
229             INDEX defaults to 0 and LENGTH defaults to 1.
230              
231             =cut
232             sub clearalarm(;$$) {
233 0   0 0 1 0 my $index = shift || 0;
234 0   0     0 my $length = shift || 1;
235              
236 0         0 splice @ALARM_QUEUE, $index, $length;
237 0 0       0 if(($index < 0 ? $#ALARM_QUEUE+$index : $index) == $ACTIVE_ALARM) {
    0          
238 0         0 $ACTIVE_ALARM = -1;
239 0         0 CORE::alarm(0);
240             }
241              
242             # Have to use a C-style loop because we modify the index
243             # variable inside the loop. :-(
244 0         0 for(my $i = 0; $i < @ALARM_QUEUE; ++$i) {
245 0         0 my $time_remaining = $ALARM_QUEUE[$i][1]+$ALARM_QUEUE[$i][0]-time;
246              
247 0         0 my $active_time;
248 0         0 $active_time = $ALARM_QUEUE[$ACTIVE_ALARM][1];
249 0         0 $active_time += $ALARM_QUEUE[$ACTIVE_ALARM][0];
250 0         0 $active_time -= time;
251              
252 0 0 0     0 if($time_remaining <= 0) {
    0          
253             # Note the -- on $i. When we splice out an alarm, all the ones
254             # after it shift down by one. We have to account for this.
255 0         0 my $handler = splice(@ALARM_QUEUE,$i--,1)->[2];
256 0   0     0 $handler ||= $DEFAULT_HANDLER;
257 0         0 $handler->(); # Call handler for this alarm.
258             } elsif($ACTIVE_ALARM == -1 or $time_remaining < $active_time) {
259 0         0 $ACTIVE_ALARM = $i;
260 0         0 CORE::alarm($time_remaining);
261             }
262             }
263             }
264              
265             =item alarm SECONDS
266              
267             =item alarm
268              
269             Creates a new alarm with no handler. A handler can
270             later be set for it via sethandler() or C<$SIG{ALRM}>,
271             if overridden.
272              
273             For the most part, this function behaves exactly like
274             Perl's built-in alarm function, except that it sets up a
275             concurrent alarm instead. Thus, each call to alarm does
276             not disable previous alarms unless called with a set time
277             of 0.
278              
279             Calling C with a set time of 0 will disable the
280             last alarm set.
281              
282             If SECONDS is not specified, the value stored in C<$_>
283             is used.
284              
285             =cut
286             sub alarm(;$) {
287 0 0   0 1 0 my $alarm = @_ ? shift : $_;
288              
289 0 0       0 if($alarm == 0) {
290 0         0 clearalarm(-1);
291             } else {
292 0         0 push( @ALARM_QUEUE, [ $alarm, time(), undef ] );
293            
294 0         0 my $time_remaining;
295 0         0 $time_remaining = $ALARM_QUEUE[$ACTIVE_ALARM][1];
296 0         0 $time_remaining += $ALARM_QUEUE[$ACTIVE_ALARM][0];
297 0         0 $time_remaining -= time;
298              
299 0 0 0     0 if($ACTIVE_ALARM == -1 or $alarm < $time_remaining) {
300 0         0 $ACTIVE_ALARM = $#ALARM_QUEUE;
301 0         0 CORE::alarm($alarm);
302             }
303             }
304             }
305              
306             =item sethandler INDEX CODEREF
307              
308             =item sethandler CODEREF
309              
310             Sets a handler for the alarm found at INDEX in the queue.
311             This is an array index, so negative values may be used to
312             indicate position relative to the end of the queue.
313              
314             If INDEX is not specified, the handler is set for the
315             last alarm in the queue that doesn't have one associated
316             with it. This means that if you set multiple alarms
317             using C, you should arrange their respective
318             C's in the I order.
319              
320             =cut
321             sub sethandler($;$) {
322              
323 0 0 0 0 1 0 unless(not defined($_[-1]) or UNIVERSAL::isa($_[-1], 'CODE')) {
324 0         0 croak("Alarm handler must be CODEREF");
325             }
326              
327 0 0       0 if(@_ == 2) {
328 0         0 $ALARM_QUEUE[$_[0]]->[2] = $_[1];
329             } else {
330 0         0 foreach my $alarm (reverse @ALARM_QUEUE) {
331 0 0       0 if(not defined $alarm->[2]) {
332 0         0 $alarm->[2] = shift();
333 0         0 last;
334             }
335             }
336             }
337             }
338              
339             =item gethandler INDEX
340              
341             =item gethandler
342              
343             Returns the handler for the alarm found at INDEX in the queue.
344             This is an array index, so negative values may be used.
345              
346             If INDEX is not specified, returns the handler for the currently
347             active alarm.
348              
349             =cut
350             sub gethandler(;$) {
351 0   0 0 1 0 my $index = shift || $ACTIVE_ALARM;
352             return(
353 0 0 0     0 ($index < @ALARM_QUEUE and $index > -1)
354             ?
355             $ALARM_QUEUE[$index][2]
356             :
357             undef
358             );
359             }
360              
361             =item restore FLAG
362              
363             =item restore
364              
365             This function reinstalls the Alarm::Concurrent alarm handler
366             if it has been replaced by a "legacy alarm handler."
367              
368             If FLAG is present and true, C will save the
369             current handler by setting it as a new concurrent alarm (as
370             if you had called C for it).
371              
372             This function may not be imported.
373              
374             Note: Do B call this function if you have imported
375             the C<:OVERLOAD> symbol. It can have unpredictable results.
376              
377             =cut
378             sub restore(;$) {
379 1 50 33 1 1 11 return if(defined($SIG{ALRM}) and $SIG{ALRM} == \&_alrm);
380              
381 1         13 my $oldalrm = CORE::alarm(0);
382              
383 1 50 33     4 if($oldalrm and shift) {
384             # Save legacy alarm.
385 0         0 setalarm($oldalrm, $SIG{ALRM});
386             }
387              
388             # Install our alarm handler.
389 1         18 $SIG{ALRM} = \&_alrm;
390             }
391              
392             =head1 CAVEATS
393              
394             =over 4
395              
396             =item *
397              
398             C<%SIG> is Perl magic and should probably not be messed
399             with, though I have not witnessed any problems in the
400             (admittedly limited) testing I've done. I would be
401             interested to hear from anyone who performs extensive
402             testing, with different versions of Perl, of the
403             reliability of doing this.
404              
405             Moreover, since there is no way to just take over
406             C<$SIG{ALRM}>, the entire magic hash is usurped and any
407             other C<%SIG}> accesses are simply passed through to the
408             original magic hash. This means that if there I any
409             problems, they will most likely affect all other signal
410             handlers you have defined, including C<$SIG{__WARN__}>
411             and C<$SIG{__DIE__}> and others.
412              
413             In other words, if you're going to use the C<:OVERRIDE>
414             option, you do so at your own risk (and you'd better be
415             pretty damn sure of yourself, too).
416              
417             =item *
418              
419             The default C<$DEFAULT_HANDLER> simply dies with the
420             message "Alarm clock!\n".
421              
422             =item *
423              
424             All warnings about alarms possibly being off by up to a full
425             second still apply. See the documentation for alarm for more
426             information.
427              
428             =item *
429              
430             The alarm handling routine does not make any allowances
431             for systems that clear the alarm handler before it is
432             called. This may be changed in the future.
433              
434             =item *
435              
436             According to L, doing just about I
437             in signal handling routines is dangerous because it might
438             be called during a non-re-entrant system library routines
439             which could cause a memory fault and core dump.
440              
441             The Alarm::Concurrent alarm handling routine does quite a bit.
442              
443             You have been warned.
444              
445             =back
446              
447             =head1 AUTHOR
448              
449             Written by Cory Johns (c) 2001.
450              
451             =cut
452              
453             1;