File Coverage

blib/lib/IPC/Run/Timer.pm
Criterion Covered Total %
statement 134 140 95.7
branch 53 80 66.2
condition 26 50 52.0
subroutine 28 29 96.5
pod 17 17 100.0
total 258 316 81.6


line stmt bran cond sub pod time code
1             package IPC::Run::Timer;
2              
3             =pod
4              
5             =head1 NAME
6              
7             IPC::Run::Timer -- Timer channels for IPC::Run.
8              
9             =head1 SYNOPSIS
10              
11             use IPC::Run qw( run timer timeout );
12             ## or IPC::Run::Timer ( timer timeout );
13             ## or IPC::Run::Timer ( :all );
14              
15             ## A non-fatal timer:
16             $t = timer( 5 ); # or...
17             $t = IO::Run::Timer->new( 5 );
18             run $t, ...;
19              
20             ## A timeout (which is a timer that dies on expiry):
21             $t = timeout( 5 ); # or...
22             $t = IO::Run::Timer->new( 5, exception => "harness timed out" );
23              
24             =head1 DESCRIPTION
25              
26             This class and module allows timers and timeouts to be created for use
27             by IPC::Run. A timer simply expires when it's time is up. A timeout
28             is a timer that throws an exception when it expires.
29              
30             Timeouts are usually a bit simpler to use than timers: they throw an
31             exception on expiration so you don't need to check them:
32              
33             ## Give @cmd 10 seconds to get started, then 5 seconds to respond
34             my $t = timeout( 10 );
35             $h = start(
36             \@cmd, \$in, \$out,
37             $t,
38             );
39             pump $h until $out =~ /prompt/;
40              
41             $in = "some stimulus";
42             $out = '';
43             $t->time( 5 )
44             pump $h until $out =~ /expected response/;
45              
46             You do need to check timers:
47              
48             ## Give @cmd 10 seconds to get started, then 5 seconds to respond
49             my $t = timer( 10 );
50             $h = start(
51             \@cmd, \$in, \$out,
52             $t,
53             );
54             pump $h until $t->is_expired || $out =~ /prompt/;
55              
56             $in = "some stimulus";
57             $out = '';
58             $t->time( 5 )
59             pump $h until $out =~ /expected response/ || $t->is_expired;
60              
61             Timers and timeouts that are reset get started by start() and
62             pump(). Timers change state only in pump(). Since run() and
63             finish() both call pump(), they act like pump() with respect to
64             timers.
65              
66             Timers and timeouts have three states: reset, running, and expired.
67             Setting the timeout value resets the timer, as does calling
68             the reset() method. The start() method starts (or restarts) a
69             timer with the most recently set time value, no matter what state
70             it's in.
71              
72             =head2 Time values
73              
74             All time values are in seconds. Times may be any kind of perl number,
75             e.g. as integer or floating point seconds, optionally preceded by
76             punctuation-separated days, hours, and minutes.
77              
78             Examples:
79              
80             1 1 second
81             1.1 1.1 seconds
82             60 60 seconds
83             1:0 1 minute
84             1:1 1 minute, 1 second
85             1:90 2 minutes, 30 seconds
86             1:2:3:4.5 1 day, 2 hours, 3 minutes, 4.5 seconds
87             'inf' the infinity perl special number (the timer never finishes)
88              
89             Absolute date/time strings are *not* accepted: year, month and
90             day-of-month parsing is not available (patches welcome :-).
91              
92             =head2 Interval fudging
93              
94             When calculating an end time from a start time and an interval, IPC::Run::Timer
95             instances add a little fudge factor. This is to ensure that no time will
96             expire before the interval is up.
97              
98             First a little background. Time is sampled in discrete increments. We'll
99             call the
100             exact moment that the reported time increments from one interval to the
101             next a tick, and the interval between ticks as the time period. Here's
102             a diagram of three ticks and the periods between them:
103              
104              
105             -0-0-0-0-0-0-0-0-0-0-1-1-1-1-1-1-1-1-1-1-2-...
106             ^ ^ ^
107             |<--- period 0 ---->|<--- period 1 ---->|
108             | | |
109             tick 0 tick 1 tick 2
110              
111             To see why the fudge factor is necessary, consider what would happen
112             when a timer with an interval of 1 second is started right at the end of
113             period 0:
114              
115              
116             -0-0-0-0-0-0-0-0-0-0-1-1-1-1-1-1-1-1-1-1-2-...
117             ^ ^ ^ ^
118             | | | |
119             | | | |
120             tick 0 |tick 1 tick 2
121             |
122             start $t
123              
124             Assuming that check() is called many times per period, then the timer
125             is likely to expire just after tick 1, since the time reported will have
126             lept from the value '0' to the value '1':
127              
128             -0-0-0-0-0-0-0-0-0-0-1-1-1-1-1-1-1-1-1-1-2-...
129             ^ ^ ^ ^ ^
130             | | | | |
131             | | | | |
132             tick 0 |tick 1| tick 2
133             | |
134             start $t |
135             |
136             check $t
137              
138             Adding a fudge of '1' in this example means that the timer is guaranteed
139             not to expire before tick 2.
140              
141             The fudge is not added to an interval of '0'.
142              
143             This means that intervals guarantee a minimum interval. Given that
144             the process running perl may be suspended for some period of time, or that
145             it gets busy doing something time-consuming, there are no other guarantees on
146             how long it will take a timer to expire.
147              
148             =head1 SUBCLASSING
149              
150             INCOMPATIBLE CHANGE: Due to the awkwardness introduced by ripping
151             pseudohashes out of Perl, this class I uses the fields
152             pragma.
153              
154             =head1 FUNCTIONS & METHODS
155              
156             =over
157              
158             =cut
159              
160 126     126   919 use strict;
  126         265  
  126         4037  
161 126     126   633 use warnings;
  126         332  
  126         3349  
162 126     126   794 use Carp;
  126         296  
  126         6521  
163 126     126   860 use Fcntl;
  126         299  
  126         23877  
164 126     126   1326 use Symbol;
  126         297  
  126         6650  
165 126     126   871 use Exporter;
  126         263  
  126         4787  
166 126     126   778 use Scalar::Util ();
  126         243  
  126         4054  
167 126     126   720 use vars qw( $VERSION @ISA @EXPORT_OK %EXPORT_TAGS );
  126         1738  
  126         16556  
168              
169             BEGIN {
170 126     126   634 $VERSION = '20231003.0';
171 126         2882 @ISA = qw( Exporter );
172 126         655 @EXPORT_OK = qw(
173             check
174             end_time
175             exception
176             expire
177             interval
178             is_expired
179             is_reset
180             is_running
181             name
182             reset
183             start
184             timeout
185             timer
186             );
187              
188 126         4236 %EXPORT_TAGS = ( 'all' => \@EXPORT_OK );
189             }
190              
191             require IPC::Run;
192 126     126   905 use IPC::Run::Debug;
  126         265  
  126         224292  
193              
194             ##
195             ## Some helpers
196             ##
197             my $resolution = 1;
198              
199             sub _parse_time {
200 316     316   798 for ( $_[0] ) {
201 316         495 my $val;
202 316 100       915 if ( not defined $_ ) {
203 22         73 $val = $_;
204             }
205             else {
206 294         1241 my @f = split( /:/, $_, -1 );
207 294 100       894 if ( scalar @f > 4 ) {
208 2         414 croak "IPC::Run: expected <= 4 elements in time string '$_'";
209             }
210 292         622 for (@f) {
211 320 100       1378 if ( not Scalar::Util::looks_like_number($_) ) {
212 6         522 croak "IPC::Run: non-numeric element '$_' in time string '$_'";
213             }
214             }
215 286         788 my ( $s, $m, $h, $d ) = reverse @f;
216 286   100     2816 $val = ( ( ( $d || 0 ) * 24 + ( $h || 0 ) ) * 60 + ( $m || 0 ) ) * 60 + ( $s || 0 );
      100        
      100        
      100        
217             }
218 308         966 return $val;
219             }
220             }
221              
222             sub _calc_end_time {
223 26     26   63 my IPC::Run::Timer $self = shift;
224 26         122 my $interval = $self->interval;
225 26 50       215 $interval += $resolution if $interval;
226 26         74 $self->end_time( $self->start_time + $interval );
227             }
228              
229             =item timer
230              
231             A constructor function (not method) of IPC::Run::Timer instances:
232              
233             $t = timer( 5 );
234             $t = timer( 5, name => 'stall timer', debug => 1 );
235              
236             $t = timer;
237             $t->interval( 5 );
238              
239             run ..., $t;
240             run ..., $t = timer( 5 );
241              
242             This convenience function is a shortened spelling of
243              
244             IPC::Run::Timer->new( ... );
245            
246             . It returns a timer in the reset state with a given interval.
247              
248             If an exception is provided, it will be thrown when the timer notices that
249             it has expired (in check()). The name is for debugging usage, if you plan on
250             having multiple timers around. If no name is provided, a name like "timer #1"
251             will be provided.
252              
253             =cut
254              
255             sub timer {
256 2     2 1 1728 return IPC::Run::Timer->new(@_);
257             }
258              
259             =item timeout
260              
261             A constructor function (not method) of IPC::Run::Timer instances:
262              
263             $t = timeout( 5 );
264             $t = timeout( 5, exception => "kablooey" );
265             $t = timeout( 5, name => "stall", exception => "kablooey" );
266              
267             $t = timeout;
268             $t->interval( 5 );
269              
270             run ..., $t;
271             run ..., $t = timeout( 5 );
272              
273             A This convenience function is a shortened spelling of
274              
275             IPC::Run::Timer->new( exception => "IPC::Run: timeout ...", ... );
276            
277             . It returns a timer in the reset state that will throw an
278             exception when it expires.
279              
280             Takes the same parameters as L, any exception passed in overrides
281             the default exception.
282              
283             =cut
284              
285             sub timeout {
286 14     14 1 10744 my $t = IPC::Run::Timer->new(@_);
287 14 50       68 $t->exception( "IPC::Run: timeout on " . $t->name )
288             unless defined $t->exception;
289 14         193 return $t;
290             }
291              
292             =item new
293              
294             IPC::Run::Timer->new() ;
295             IPC::Run::Timer->new( 5 ) ;
296             IPC::Run::Timer->new( 5, exception => 'kablooey' ) ;
297              
298             Constructor. See L for details.
299              
300             =cut
301              
302             my $timer_counter;
303              
304             sub new {
305 16     16 1 46 my $class = shift;
306 16   33     130 $class = ref $class || $class;
307              
308 16         55 my IPC::Run::Timer $self = bless {}, $class;
309              
310 16         151 $self->{STATE} = 0;
311 16         86 $self->{DEBUG} = 0;
312 16         131 $self->{NAME} = "timer #" . ++$timer_counter;
313              
314 16         100 while (@_) {
315 16         57 my $arg = shift;
316 16 50       112 if ( $arg eq 'exception' ) {
    50          
    50          
317 0         0 $self->exception(shift);
318             }
319             elsif ( $arg eq 'name' ) {
320 0         0 $self->name(shift);
321             }
322             elsif ( $arg eq 'debug' ) {
323 0         0 $self->debug(shift);
324             }
325             else {
326 16         66 $self->interval($arg);
327             }
328             }
329              
330             _debug $self->name . ' constructed'
331 16 50 33     518 if $self->{DEBUG} || _debugging_details;
332              
333 16         61 return $self;
334             }
335              
336             =item check
337              
338             check $t;
339             check $t, $now;
340             $t->check;
341              
342             Checks to see if a timer has expired since the last check. Has no effect
343             on non-running timers. This will throw an exception if one is defined.
344              
345             IPC::Run::pump() calls this routine for any timers in the harness.
346              
347             You may pass in a version of now, which is useful in case you have
348             it lying around or you want to check several timers with a consistent
349             concept of the current time.
350              
351             Returns the time left before end_time or 0 if end_time is no longer
352             in the future or the timer is not running
353             (unless, of course, check() expire()s the timer and this
354             results in an exception being thrown).
355              
356             Returns undef if the timer is not running on entry, 0 if check() expires it,
357             and the time left if it's left running.
358              
359             =cut
360              
361             sub check {
362 198     198 1 453 my IPC::Run::Timer $self = shift;
363 198 100       395 return undef if !$self->is_running;
364 192 50       549 return 0 if $self->is_expired;
365              
366 192         514 my ($now) = @_;
367 192         531 $now = _parse_time($now);
368 192 50       503 $now = time unless defined $now;
369              
370 192 50 33     5410 _debug( "checking ", $self->name, " (end time ", $self->end_time, ") at ", $now ) if $self->{DEBUG} || _debugging_details;
371              
372 192         670 my $left = $self->end_time - $now;
373 192 100       754 return $left if $left > 0;
374              
375 16         93 $self->expire;
376 6         82 return 0;
377             }
378              
379             =item debug
380              
381             Sets/gets the current setting of the debugging flag for this timer. This
382             has no effect if debugging is not enabled for the current harness.
383              
384             =cut
385              
386             sub debug {
387 0     0 1 0 my IPC::Run::Timer $self = shift;
388 0 0       0 $self->{DEBUG} = shift if @_;
389 0         0 return $self->{DEBUG};
390             }
391              
392             =item end_time
393              
394             $et = $t->end_time;
395             $et = end_time $t;
396              
397             $t->end_time( time + 10 );
398              
399             Returns the time when this timer will or did expire. Even if this time is
400             in the past, the timer may not be expired, since check() may not have been
401             called yet.
402              
403             Note that this end_time is not start_time($t) + interval($t), since some
404             small extra amount of time is added to make sure that the timer does not
405             expire before interval() elapses. If this were not so, then
406              
407             Changing end_time() while a timer is running will set the expiration time.
408             Changing it while it is expired has no affect, since reset()ing a timer always
409             clears the end_time().
410              
411             =cut
412              
413             sub end_time {
414 258     258 1 555 my IPC::Run::Timer $self = shift;
415 258 100       627 if (@_) {
416 40         329 $self->{END_TIME} = shift;
417             _debug $self->name, ' end_time set to ', $self->{END_TIME}
418 40 50 33     1030 if $self->{DEBUG} > 2 || _debugging_details;
419             }
420 258         834 return $self->{END_TIME};
421             }
422              
423             =item exception
424              
425             $x = $t->exception;
426             $t->exception( $x );
427             $t->exception( undef );
428              
429             Sets/gets the exception to throw, if any. 'undef' means that no
430             exception will be thrown. Exception does not need to be a scalar: you
431             may ask that references be thrown.
432              
433             =cut
434              
435             sub exception {
436 54     54 1 182 my IPC::Run::Timer $self = shift;
437 54 100       170 if (@_) {
438 14         66 $self->{EXCEPTION} = shift;
439             _debug $self->name, ' exception set to ', $self->{EXCEPTION}
440 14 50 33     422 if $self->{DEBUG} || _debugging_details;
441             }
442 54         7327 return $self->{EXCEPTION};
443             }
444              
445             =item interval
446              
447             $i = interval $t;
448             $i = $t->interval;
449             $t->interval( $i );
450              
451             Sets the interval. Sets the end time based on the start_time() and the
452             interval (and a little fudge) if the timer is running.
453              
454             =cut
455              
456             sub interval {
457 140     140 1 7773 my IPC::Run::Timer $self = shift;
458 140 100       319 if (@_) {
459 56         191 $self->{INTERVAL} = _parse_time(shift);
460             _debug $self->name, ' interval set to ', $self->{INTERVAL}
461 48 50 33     1364 if $self->{DEBUG} > 2 || _debugging_details;
462              
463 48 50       143 $self->_calc_end_time if $self->state;
464             }
465 132         445 return $self->{INTERVAL};
466             }
467              
468             =item expire
469              
470             expire $t;
471             $t->expire;
472              
473             Sets the state to expired (undef).
474             Will throw an exception if one
475             is defined and the timer was not already expired. You can expire a
476             reset timer without starting it.
477              
478             =cut
479              
480             sub expire {
481 16     16 1 37 my IPC::Run::Timer $self = shift;
482 16 50       107 if ( defined $self->state ) {
483             _debug $self->name . ' expired'
484 16 50 33     630 if $self->{DEBUG} || _debugging;
485              
486 16         69 $self->state(undef);
487 16 100       134 croak $self->exception if $self->exception;
488             }
489 6         10 return undef;
490             }
491              
492             =item is_running
493              
494             =cut
495              
496             sub is_running {
497 421     421 1 896 my IPC::Run::Timer $self = shift;
498 421 100       1049 return $self->state ? 1 : 0;
499             }
500              
501             =item is_reset
502              
503             =cut
504              
505             sub is_reset {
506 75     75 1 3920 my IPC::Run::Timer $self = shift;
507 75   100     306 return defined $self->state && $self->state == 0;
508             }
509              
510             =item is_expired
511              
512             =cut
513              
514             sub is_expired {
515 241     241 1 481 my IPC::Run::Timer $self = shift;
516 241         565 return !defined $self->state;
517             }
518              
519             =item name
520              
521             Sets/gets this timer's name. The name is only used for debugging
522             purposes so you can tell which freakin' timer is doing what.
523              
524             =cut
525              
526             sub name {
527 14     14 1 39 my IPC::Run::Timer $self = shift;
528              
529 14 50       50 $self->{NAME} = shift if @_;
530             return
531             defined $self->{NAME} ? $self->{NAME}
532 14 0       94 : defined $self->{EXCEPTION} ? 'timeout'
    50          
533             : 'timer';
534             }
535              
536             =item reset
537              
538             reset $t;
539             $t->reset;
540              
541             Resets the timer to the non-running, non-expired state and clears
542             the end_time().
543              
544             =cut
545              
546             sub reset {
547 2     2 1 702 my IPC::Run::Timer $self = shift;
548 2         6 $self->state(0);
549 2         8 $self->end_time(undef);
550             _debug $self->name . ' reset'
551 2 50 33     40 if $self->{DEBUG} || _debugging;
552              
553 2         4 return undef;
554             }
555              
556             =item start
557              
558             start $t;
559             $t->start;
560             start $t, $interval;
561             start $t, $interval, $now;
562              
563             Starts or restarts a timer. This always sets the start_time. It sets the
564             end_time based on the interval if the timer is running or if no end time
565             has been set.
566              
567             You may pass an optional interval or current time value.
568              
569             Not passing a defined interval causes the previous interval setting to be
570             re-used unless the timer is reset and an end_time has been set
571             (an exception is thrown if no interval has been set).
572              
573             Not passing a defined current time value causes the current time to be used.
574              
575             Passing a current time value is useful if you happen to have a time value
576             lying around or if you want to make sure that several timers are started
577             with the same concept of start time. You might even need to lie to an
578             IPC::Run::Timer, occasionally.
579              
580             =cut
581              
582             sub start {
583 26     26 1 60 my IPC::Run::Timer $self = shift;
584              
585 26         91 my ( $interval, $now ) = map { _parse_time($_) } @_;
  16         50  
586 26         72 $now = _parse_time($now);
587 26 100       100 $now = time unless defined $now;
588              
589 26 100       84 $self->interval($interval) if defined $interval;
590              
591             ## start()ing a running or expired timer clears the end_time, so that the
592             ## interval is used. So does specifying an interval.
593 26 100 100     100 $self->end_time(undef) if !$self->is_reset || $interval;
594              
595 26 50 33     111 croak "IPC::Run: no timer interval or end_time defined for " . $self->name
596             unless defined $self->interval || defined $self->end_time;
597              
598 26         162 $self->state(1);
599 26         181 $self->start_time($now);
600             ## The "+ 1" is in case the START_TIME was sampled at the end of a
601             ## tick (which are one second long in this module).
602 26 50       83 $self->_calc_end_time
603             unless defined $self->end_time;
604              
605             _debug(
606             $self->name, " started at ", $self->start_time,
607             ", with interval ", $self->interval, ", end_time ", $self->end_time
608 26 50 33     670 ) if $self->{DEBUG} || _debugging;
609 26         87 return undef;
610             }
611              
612             =item start_time
613              
614             Sets/gets the start time, in seconds since the epoch. Setting this manually
615             is a bad idea, it's better to call L() at the correct time.
616              
617             =cut
618              
619             sub start_time {
620 52     52 1 96 my IPC::Run::Timer $self = shift;
621 52 100       260 if (@_) {
622 26         106 $self->{START_TIME} = _parse_time(shift);
623             _debug $self->name, ' start_time set to ', $self->{START_TIME}
624 26 50 33     805 if $self->{DEBUG} > 2 || _debugging;
625             }
626              
627 52         190 return $self->{START_TIME};
628             }
629              
630             =item state
631              
632             $s = state $t;
633             $t->state( $s );
634              
635             Get/Set the current state. Only use this if you really need to transfer the
636             state to/from some variable.
637             Use L, L, L, L, L,
638             L.
639              
640             Note: Setting the state to 'undef' to expire a timer will not throw an
641             exception.
642              
643             =back
644              
645             =cut
646              
647             sub state {
648 893     893 1 1425 my IPC::Run::Timer $self = shift;
649 893 100       2033 if (@_) {
650 44         167 $self->{STATE} = shift;
651             _debug $self->name, ' state set to ', $self->{STATE}
652 44 50 33     1219 if $self->{DEBUG} > 2 || _debugging;
653             }
654 893         3725 return $self->{STATE};
655             }
656              
657             1;
658              
659             =pod
660              
661             =head1 TODO
662              
663             use Time::HiRes; if it's present.
664              
665             Add detection and parsing of [[[HH:]MM:]SS formatted times and intervals.
666              
667             =head1 AUTHOR
668              
669             Barrie Slaymaker
670              
671             =cut