File Coverage

blib/lib/IPC/Run/Timer.pm
Criterion Covered Total %
statement 131 137 95.6
branch 53 80 66.2
condition 26 50 52.0
subroutine 27 28 96.4
pod 17 17 100.0
total 254 312 81.4


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 117     117   844 use strict;
  117         287  
  117         3806  
161 117     117   696 use Carp;
  117         197  
  117         6057  
162 117     117   632 use Fcntl;
  117         271  
  117         21993  
163 117     117   893 use Symbol;
  117         374  
  117         6778  
164 117     117   823 use Exporter;
  117         390  
  117         3772  
165 117     117   682 use Scalar::Util ();
  117         257  
  117         3458  
166 117     117   697 use vars qw( $VERSION @ISA @EXPORT_OK %EXPORT_TAGS );
  117         362  
  117         16241  
167              
168             BEGIN {
169 117     117   484 $VERSION = '20200505.0';
170 117         2302 @ISA = qw( Exporter );
171 117         626 @EXPORT_OK = qw(
172             check
173             end_time
174             exception
175             expire
176             interval
177             is_expired
178             is_reset
179             is_running
180             name
181             reset
182             start
183             timeout
184             timer
185             );
186              
187 117         4351 %EXPORT_TAGS = ( 'all' => \@EXPORT_OK );
188             }
189              
190             require IPC::Run;
191 117     117   738 use IPC::Run::Debug;
  117         223  
  117         198956  
192              
193             ##
194             ## Some helpers
195             ##
196             my $resolution = 1;
197              
198             sub _parse_time {
199 299     299   798 for ( $_[0] ) {
200 299         488 my $val;
201 299 100       747 if ( not defined $_ ) {
202 22         61 $val = $_;
203             }
204             else {
205 277         1088 my @f = split( /:/, $_, -1 );
206 277 100       1236 if ( scalar @f > 4 ) {
207 2         402 croak "IPC::Run: expected <= 4 elements in time string '$_'";
208             }
209 275         621 for (@f) {
210 303 100       1549 if ( not Scalar::Util::looks_like_number($_) ) {
211 6         560 croak "IPC::Run: non-numeric element '$_' in time string '$_'";
212             }
213             }
214 269         734 my ( $s, $m, $h, $d ) = reverse @f;
215 269   100     2657 $val = ( ( ( $d || 0 ) * 24 + ( $h || 0 ) ) * 60 + ( $m || 0 ) ) * 60 + ( $s || 0 );
      100        
      100        
      100        
216             }
217 291         923 return $val;
218             }
219             }
220              
221             sub _calc_end_time {
222 26     26   53 my IPC::Run::Timer $self = shift;
223 26         71 my $interval = $self->interval;
224 26 50       93 $interval += $resolution if $interval;
225 26         104 $self->end_time( $self->start_time + $interval );
226             }
227              
228             =item timer
229              
230             A constructor function (not method) of IPC::Run::Timer instances:
231              
232             $t = timer( 5 );
233             $t = timer( 5, name => 'stall timer', debug => 1 );
234              
235             $t = timer;
236             $t->interval( 5 );
237              
238             run ..., $t;
239             run ..., $t = timer( 5 );
240              
241             This convenience function is a shortened spelling of
242              
243             IPC::Run::Timer->new( ... );
244            
245             . It returns a timer in the reset state with a given interval.
246              
247             If an exception is provided, it will be thrown when the timer notices that
248             it has expired (in check()). The name is for debugging usage, if you plan on
249             having multiple timers around. If no name is provided, a name like "timer #1"
250             will be provided.
251              
252             =cut
253              
254             sub timer {
255 2     2 1 1714 return IPC::Run::Timer->new(@_);
256             }
257              
258             =item timeout
259              
260             A constructor function (not method) of IPC::Run::Timer instances:
261              
262             $t = timeout( 5 );
263             $t = timeout( 5, exception => "kablooey" );
264             $t = timeout( 5, name => "stall", exception => "kablooey" );
265              
266             $t = timeout;
267             $t->interval( 5 );
268              
269             run ..., $t;
270             run ..., $t = timeout( 5 );
271              
272             A This convenience function is a shortened spelling of
273              
274             IPC::Run::Timer->new( exception => "IPC::Run: timeout ...", ... );
275            
276             . It returns a timer in the reset state that will throw an
277             exception when it expires.
278              
279             Takes the same parameters as L, any exception passed in overrides
280             the default exception.
281              
282             =cut
283              
284             sub timeout {
285 14     14 1 12719 my $t = IPC::Run::Timer->new(@_);
286 14 50       57 $t->exception( "IPC::Run: timeout on " . $t->name )
287             unless defined $t->exception;
288 14         260 return $t;
289             }
290              
291             =item new
292              
293             IPC::Run::Timer->new() ;
294             IPC::Run::Timer->new( 5 ) ;
295             IPC::Run::Timer->new( 5, exception => 'kablooey' ) ;
296              
297             Constructor. See L for details.
298              
299             =cut
300              
301             my $timer_counter;
302              
303             sub new {
304 16     16 1 69 my $class = shift;
305 16   33     175 $class = ref $class || $class;
306              
307 16         106 my IPC::Run::Timer $self = bless {}, $class;
308              
309 16         185 $self->{STATE} = 0;
310 16         109 $self->{DEBUG} = 0;
311 16         328 $self->{NAME} = "timer #" . ++$timer_counter;
312              
313 16         133 while (@_) {
314 16         74 my $arg = shift;
315 16 50       172 if ( $arg eq 'exception' ) {
    50          
    50          
316 0         0 $self->exception(shift);
317             }
318             elsif ( $arg eq 'name' ) {
319 0         0 $self->name(shift);
320             }
321             elsif ( $arg eq 'debug' ) {
322 0         0 $self->debug(shift);
323             }
324             else {
325 16         69 $self->interval($arg);
326             }
327             }
328              
329             _debug $self->name . ' constructed'
330 16 50 33     559 if $self->{DEBUG} || _debugging_details;
331              
332 16         73 return $self;
333             }
334              
335             =item check
336              
337             check $t;
338             check $t, $now;
339             $t->check;
340              
341             Checks to see if a timer has expired since the last check. Has no effect
342             on non-running timers. This will throw an exception if one is defined.
343              
344             IPC::Run::pump() calls this routine for any timers in the harness.
345              
346             You may pass in a version of now, which is useful in case you have
347             it lying around or you want to check several timers with a consistent
348             concept of the current time.
349              
350             Returns the time left before end_time or 0 if end_time is no longer
351             in the future or the timer is not running
352             (unless, of course, check() expire()s the timer and this
353             results in an exception being thrown).
354              
355             Returns undef if the timer is not running on entry, 0 if check() expires it,
356             and the time left if it's left running.
357              
358             =cut
359              
360             sub check {
361 181     181 1 316 my IPC::Run::Timer $self = shift;
362 181 100       409 return undef if !$self->is_running;
363 175 50       525 return 0 if $self->is_expired;
364              
365 175         570 my ($now) = @_;
366 175         569 $now = _parse_time($now);
367 175 50       508 $now = time unless defined $now;
368              
369 175 50 33     5432 _debug( "checking ", $self->name, " (end time ", $self->end_time, ") at ", $now ) if $self->{DEBUG} || _debugging_details;
370              
371 175         758 my $left = $self->end_time - $now;
372 175 100       668 return $left if $left > 0;
373              
374 16         122 $self->expire;
375 6         22 return 0;
376             }
377              
378             =item debug
379              
380             Sets/gets the current setting of the debugging flag for this timer. This
381             has no effect if debugging is not enabled for the current harness.
382              
383             =cut
384              
385             sub debug {
386 0     0 1 0 my IPC::Run::Timer $self = shift;
387 0 0       0 $self->{DEBUG} = shift if @_;
388 0         0 return $self->{DEBUG};
389             }
390              
391             =item end_time
392              
393             $et = $t->end_time;
394             $et = end_time $t;
395              
396             $t->end_time( time + 10 );
397              
398             Returns the time when this timer will or did expire. Even if this time is
399             in the past, the timer may not be expired, since check() may not have been
400             called yet.
401              
402             Note that this end_time is not start_time($t) + interval($t), since some
403             small extra amount of time is added to make sure that the timer does not
404             expire before interval() elapses. If this were not so, then
405              
406             Changing end_time() while a timer is running will set the expiration time.
407             Changing it while it is expired has no affect, since reset()ing a timer always
408             clears the end_time().
409              
410             =cut
411              
412             sub end_time {
413 241     241 1 495 my IPC::Run::Timer $self = shift;
414 241 100       797 if (@_) {
415 40         102 $self->{END_TIME} = shift;
416             _debug $self->name, ' end_time set to ', $self->{END_TIME}
417 40 50 33     1152 if $self->{DEBUG} > 2 || _debugging_details;
418             }
419 241         813 return $self->{END_TIME};
420             }
421              
422             =item exception
423              
424             $x = $t->exception;
425             $t->exception( $x );
426             $t->exception( undef );
427              
428             Sets/gets the exception to throw, if any. 'undef' means that no
429             exception will be thrown. Exception does not need to be a scalar: you
430             may ask that references be thrown.
431              
432             =cut
433              
434             sub exception {
435 54     54 1 136 my IPC::Run::Timer $self = shift;
436 54 100       408 if (@_) {
437 14         106 $self->{EXCEPTION} = shift;
438             _debug $self->name, ' exception set to ', $self->{EXCEPTION}
439 14 50 33     597 if $self->{DEBUG} || _debugging_details;
440             }
441 54         8833 return $self->{EXCEPTION};
442             }
443              
444             =item interval
445              
446             $i = interval $t;
447             $i = $t->interval;
448             $t->interval( $i );
449              
450             Sets the interval. Sets the end time based on the start_time() and the
451             interval (and a little fudge) if the timer is running.
452              
453             =cut
454              
455             sub interval {
456 140     140 1 8101 my IPC::Run::Timer $self = shift;
457 140 100       349 if (@_) {
458 56         226 $self->{INTERVAL} = _parse_time(shift);
459             _debug $self->name, ' interval set to ', $self->{INTERVAL}
460 48 50 33     1507 if $self->{DEBUG} > 2 || _debugging_details;
461              
462 48 50       156 $self->_calc_end_time if $self->state;
463             }
464 132         507 return $self->{INTERVAL};
465             }
466              
467             =item expire
468              
469             expire $t;
470             $t->expire;
471              
472             Sets the state to expired (undef).
473             Will throw an exception if one
474             is defined and the timer was not already expired. You can expire a
475             reset timer without starting it.
476              
477             =cut
478              
479             sub expire {
480 16     16 1 46 my IPC::Run::Timer $self = shift;
481 16 50       66 if ( defined $self->state ) {
482             _debug $self->name . ' expired'
483 16 50 33     725 if $self->{DEBUG} || _debugging;
484              
485 16         115 $self->state(undef);
486 16 100       122 croak $self->exception if $self->exception;
487             }
488 6         12 return undef;
489             }
490              
491             =item is_running
492              
493             =cut
494              
495             sub is_running {
496 387     387 1 722 my IPC::Run::Timer $self = shift;
497 387 100       1125 return $self->state ? 1 : 0;
498             }
499              
500             =item is_reset
501              
502             =cut
503              
504             sub is_reset {
505 75     75 1 5242 my IPC::Run::Timer $self = shift;
506 75   100     341 return defined $self->state && $self->state == 0;
507             }
508              
509             =item is_expired
510              
511             =cut
512              
513             sub is_expired {
514 224     224 1 436 my IPC::Run::Timer $self = shift;
515 224         475 return !defined $self->state;
516             }
517              
518             =item name
519              
520             Sets/gets this timer's name. The name is only used for debugging
521             purposes so you can tell which freakin' timer is doing what.
522              
523             =cut
524              
525             sub name {
526 14     14 1 65 my IPC::Run::Timer $self = shift;
527              
528 14 50       96 $self->{NAME} = shift if @_;
529             return
530             defined $self->{NAME} ? $self->{NAME}
531 14 0       182 : defined $self->{EXCEPTION} ? 'timeout'
    50          
532             : 'timer';
533             }
534              
535             =item reset
536              
537             reset $t;
538             $t->reset;
539              
540             Resets the timer to the non-running, non-expired state and clears
541             the end_time().
542              
543             =cut
544              
545             sub reset {
546 2     2 1 768 my IPC::Run::Timer $self = shift;
547 2         10 $self->state(0);
548 2         30 $self->end_time(undef);
549             _debug $self->name . ' reset'
550 2 50 33     40 if $self->{DEBUG} || _debugging;
551              
552 2         18 return undef;
553             }
554              
555             =item start
556              
557             start $t;
558             $t->start;
559             start $t, $interval;
560             start $t, $interval, $now;
561              
562             Starts or restarts a timer. This always sets the start_time. It sets the
563             end_time based on the interval if the timer is running or if no end time
564             has been set.
565              
566             You may pass an optional interval or current time value.
567              
568             Not passing a defined interval causes the previous interval setting to be
569             re-used unless the timer is reset and an end_time has been set
570             (an exception is thrown if no interval has been set).
571              
572             Not passing a defined current time value causes the current time to be used.
573              
574             Passing a current time value is useful if you happen to have a time value
575             lying around or if you want to make sure that several timers are started
576             with the same concept of start time. You might even need to lie to an
577             IPC::Run::Timer, occasionally.
578              
579             =cut
580              
581             sub start {
582 26     26 1 102 my IPC::Run::Timer $self = shift;
583              
584 26         94 my ( $interval, $now ) = map { _parse_time($_) } @_;
  16         30  
585 26         70 $now = _parse_time($now);
586 26 100       104 $now = time unless defined $now;
587              
588 26 100       135 $self->interval($interval) if defined $interval;
589              
590             ## start()ing a running or expired timer clears the end_time, so that the
591             ## interval is used. So does specifying an interval.
592 26 100 100     95 $self->end_time(undef) if !$self->is_reset || $interval;
593              
594 26 50 33     99 croak "IPC::Run: no timer interval or end_time defined for " . $self->name
595             unless defined $self->interval || defined $self->end_time;
596              
597 26         105 $self->state(1);
598 26         148 $self->start_time($now);
599             ## The "+ 1" is in case the START_TIME was sampled at the end of a
600             ## tick (which are one second long in this module).
601 26 50       83 $self->_calc_end_time
602             unless defined $self->end_time;
603              
604             _debug(
605             $self->name, " started at ", $self->start_time,
606             ", with interval ", $self->interval, ", end_time ", $self->end_time
607 26 50 33     798 ) if $self->{DEBUG} || _debugging;
608 26         113 return undef;
609             }
610              
611             =item start_time
612              
613             Sets/gets the start time, in seconds since the epoch. Setting this manually
614             is a bad idea, it's better to call L() at the correct time.
615              
616             =cut
617              
618             sub start_time {
619 52     52 1 87 my IPC::Run::Timer $self = shift;
620 52 100       148 if (@_) {
621 26         98 $self->{START_TIME} = _parse_time(shift);
622             _debug $self->name, ' start_time set to ', $self->{START_TIME}
623 26 50 33     798 if $self->{DEBUG} > 2 || _debugging;
624             }
625              
626 52         172 return $self->{START_TIME};
627             }
628              
629             =item state
630              
631             $s = state $t;
632             $t->state( $s );
633              
634             Get/Set the current state. Only use this if you really need to transfer the
635             state to/from some variable.
636             Use L, L, L, L, L,
637             L.
638              
639             Note: Setting the state to 'undef' to expire a timer will not throw an
640             exception.
641              
642             =back
643              
644             =cut
645              
646             sub state {
647 842     842 1 1407 my IPC::Run::Timer $self = shift;
648 842 100       2066 if (@_) {
649 44         115 $self->{STATE} = shift;
650             _debug $self->name, ' state set to ', $self->{STATE}
651 44 50 33     1393 if $self->{DEBUG} > 2 || _debugging;
652             }
653 842         3953 return $self->{STATE};
654             }
655              
656             1;
657              
658             =pod
659              
660             =head1 TODO
661              
662             use Time::HiRes; if it's present.
663              
664             Add detection and parsing of [[[HH:]MM:]SS formatted times and intervals.
665              
666             =head1 AUTHOR
667              
668             Barrie Slaymaker
669              
670             =cut