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 121     121   709 use strict;
  121         234  
  121         3210  
161 121     121   577 use warnings;
  121         213  
  121         2874  
162 121     121   510 use Carp;
  121         208  
  121         5150  
163 121     121   766 use Fcntl;
  121         202  
  121         17668  
164 121     121   636 use Symbol;
  121         228  
  121         4988  
165 121     121   572 use Exporter;
  121         215  
  121         3973  
166 121     121   738 use Scalar::Util ();
  121         167  
  121         2499  
167 121     121   1263 use vars qw( $VERSION @ISA @EXPORT_OK %EXPORT_TAGS );
  121         205  
  121         14238  
168              
169             BEGIN {
170 121     121   417 $VERSION = '20220807.0';
171 121         2029 @ISA = qw( Exporter );
172 121         638 @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 121         3738 %EXPORT_TAGS = ( 'all' => \@EXPORT_OK );
189             }
190              
191             require IPC::Run;
192 121     121   705 use IPC::Run::Debug;
  121         223  
  121         184502  
193              
194             ##
195             ## Some helpers
196             ##
197             my $resolution = 1;
198              
199             sub _parse_time {
200 325     325   594 for ( $_[0] ) {
201 325         369 my $val;
202 325 100       661 if ( not defined $_ ) {
203 22         81 $val = $_;
204             }
205             else {
206 303         872 my @f = split( /:/, $_, -1 );
207 303 100       690 if ( scalar @f > 4 ) {
208 2         338 croak "IPC::Run: expected <= 4 elements in time string '$_'";
209             }
210 301         497 for (@f) {
211 329 100       1424 if ( not Scalar::Util::looks_like_number($_) ) {
212 6         450 croak "IPC::Run: non-numeric element '$_' in time string '$_'";
213             }
214             }
215 295         615 my ( $s, $m, $h, $d ) = reverse @f;
216 295   100     2198 $val = ( ( ( $d || 0 ) * 24 + ( $h || 0 ) ) * 60 + ( $m || 0 ) ) * 60 + ( $s || 0 );
      100        
      100        
      100        
217             }
218 317         729 return $val;
219             }
220             }
221              
222             sub _calc_end_time {
223 26     26   99 my IPC::Run::Timer $self = shift;
224 26         55 my $interval = $self->interval;
225 26 50       102 $interval += $resolution if $interval;
226 26         62 $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 2226 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 8567 my $t = IPC::Run::Timer->new(@_);
287 14 50       43 $t->exception( "IPC::Run: timeout on " . $t->name )
288             unless defined $t->exception;
289 14         111 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 49 my $class = shift;
306 16   33     137 $class = ref $class || $class;
307              
308 16         39 my IPC::Run::Timer $self = bless {}, $class;
309              
310 16         85 $self->{STATE} = 0;
311 16         60 $self->{DEBUG} = 0;
312 16         92 $self->{NAME} = "timer #" . ++$timer_counter;
313              
314 16         98 while (@_) {
315 16         27 my $arg = shift;
316 16 50       88 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         46 $self->interval($arg);
327             }
328             }
329              
330             _debug $self->name . ' constructed'
331 16 50 33     360 if $self->{DEBUG} || _debugging_details;
332              
333 16         48 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 207     207 1 269 my IPC::Run::Timer $self = shift;
363 207 100       339 return undef if !$self->is_running;
364 201 50       428 return 0 if $self->is_expired;
365              
366 201         399 my ($now) = @_;
367 201         494 $now = _parse_time($now);
368 201 50       414 $now = time unless defined $now;
369              
370 201 50 33     4255 _debug( "checking ", $self->name, " (end time ", $self->end_time, ") at ", $now ) if $self->{DEBUG} || _debugging_details;
371              
372 201         564 my $left = $self->end_time - $now;
373 201 100       594 return $left if $left > 0;
374              
375 16         65 $self->expire;
376 6         36 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 267     267 1 432 my IPC::Run::Timer $self = shift;
415 267 100       509 if (@_) {
416 40         81 $self->{END_TIME} = shift;
417             _debug $self->name, ' end_time set to ', $self->{END_TIME}
418 40 50 33     831 if $self->{DEBUG} > 2 || _debugging_details;
419             }
420 267         559 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 84 my IPC::Run::Timer $self = shift;
437 54 100       110 if (@_) {
438 14         45 $self->{EXCEPTION} = shift;
439             _debug $self->name, ' exception set to ', $self->{EXCEPTION}
440 14 50 33     291 if $self->{DEBUG} || _debugging_details;
441             }
442 54         4281 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 8851 my IPC::Run::Timer $self = shift;
458 140 100       265 if (@_) {
459 56         124 $self->{INTERVAL} = _parse_time(shift);
460             _debug $self->name, ' interval set to ', $self->{INTERVAL}
461 48 50 33     1121 if $self->{DEBUG} > 2 || _debugging_details;
462              
463 48 50       125 $self->_calc_end_time if $self->state;
464             }
465 132         441 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 45 my IPC::Run::Timer $self = shift;
482 16 50       43 if ( defined $self->state ) {
483             _debug $self->name . ' expired'
484 16 50 33     441 if $self->{DEBUG} || _debugging;
485              
486 16         48 $self->state(undef);
487 16 100       93 croak $self->exception if $self->exception;
488             }
489 6         8 return undef;
490             }
491              
492             =item is_running
493              
494             =cut
495              
496             sub is_running {
497 439     439 1 612 my IPC::Run::Timer $self = shift;
498 439 100       825 return $self->state ? 1 : 0;
499             }
500              
501             =item is_reset
502              
503             =cut
504              
505             sub is_reset {
506 75     75 1 3817 my IPC::Run::Timer $self = shift;
507 75   100     191 return defined $self->state && $self->state == 0;
508             }
509              
510             =item is_expired
511              
512             =cut
513              
514             sub is_expired {
515 250     250 1 348 my IPC::Run::Timer $self = shift;
516 250         371 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 21 my IPC::Run::Timer $self = shift;
528              
529 14 50       33 $self->{NAME} = shift if @_;
530             return
531             defined $self->{NAME} ? $self->{NAME}
532 14 0       77 : 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 728 my IPC::Run::Timer $self = shift;
548 2         8 $self->state(0);
549 2         8 $self->end_time(undef);
550             _debug $self->name . ' reset'
551 2 50 33     60 if $self->{DEBUG} || _debugging;
552              
553 2         12 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 44 my IPC::Run::Timer $self = shift;
584              
585 26         58 my ( $interval, $now ) = map { _parse_time($_) } @_;
  16         38  
586 26         59 $now = _parse_time($now);
587 26 100       84 $now = time unless defined $now;
588              
589 26 100       80 $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     63 $self->end_time(undef) if !$self->is_reset || $interval;
594              
595 26 50 33     63 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         68 $self->state(1);
599 26         88 $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       127 $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     538 ) if $self->{DEBUG} || _debugging;
609 26         131 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 88 my IPC::Run::Timer $self = shift;
621 52 100       95 if (@_) {
622 26         54 $self->{START_TIME} = _parse_time(shift);
623             _debug $self->name, ' start_time set to ', $self->{START_TIME}
624 26 50 33     543 if $self->{DEBUG} > 2 || _debugging;
625             }
626              
627 52         98 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 920     920 1 1120 my IPC::Run::Timer $self = shift;
649 920 100       1551 if (@_) {
650 44         84 $self->{STATE} = shift;
651             _debug $self->name, ' state set to ', $self->{STATE}
652 44 50 33     979 if $self->{DEBUG} > 2 || _debugging;
653             }
654 920         2981 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