File Coverage

blib/lib/Time/Out.pm
Criterion Covered Total %
statement 56 56 100.0
branch 22 22 100.0
condition 6 6 100.0
subroutine 12 12 100.0
pod 1 1 100.0
total 97 97 100.0


line stmt bran cond sub pod time code
1 8     8   245665 use strict;
  8         15  
  8         279  
2 8     8   61 use warnings;
  8         14  
  8         520  
3              
4             package Time::Out;
5              
6             # keeping the following $VERSION declaration on a single line is important
7             #<<<
8 8     8   3441 use version 0.9915; our $VERSION = version->declare( '1.0.0' );
  8         16153  
  8         54  
9             #>>>
10              
11 8     8   853 use Exporter qw( import );
  8         24  
  8         313  
12 8     8   42 use Scalar::Util qw( blessed reftype );
  8         15  
  8         751  
13 8     8   3907 use Time::Out::Exception qw();
  8         21  
  8         218  
14 8     8   3076 use Time::Out::ParamConstraints qw( assert_NonNegativeNumber assert_CodeRef );
  8         20  
  8         490  
15 8     8   3699 use Try::Tiny qw( finally try );
  8         16619  
  8         696  
16              
17             BEGIN {
18             # if possible use Time::HiRes drop-in replacements
19 8     8   26 for ( qw( alarm time ) ) {
20 16 100       1996 Time::HiRes->import( $_ ) if Time::HiRes->can( $_ );
21             }
22             }
23              
24             our @EXPORT_OK = qw( timeout );
25              
26             sub timeout( $@ ) {
27 20     20 1 2041909 my $context = wantarray;
28             # wallclock seconds
29 20         149 my $timeout = assert_NonNegativeNumber shift;
30 20         82 my $code = assert_CodeRef pop;
31 20         84 my @code_args = @_;
32              
33 20         41 my $exception;
34             # in scalar context store the result in the first array element
35             my @result;
36              
37             # disable previous timer and save the amount of time remaining on it
38 20         170 my $remaining_time_on_previous_timer = alarm 0;
39 20         47 my $start_time = time;
40              
41             {
42             # https://stackoverflow.com/questions/1194113/whats-the-difference-between-ignoring-a-signal-and-telling-it-to-do-nothing-in
43             # disable ALRM handling to prevent possible race condition between the end of the
44             # try block and the execution of alarm(0) in the finally block
45 20         37 local $SIG{ ALRM } = 'IGNORE';
  20         330  
46             try {
47 20     20   1421 local $SIG{ ALRM } = sub { die Time::Out::Exception->new( previous_exception => $@, timeout => $timeout ) }; ## no critic (RequireCarping)
  9         14002326  
48 20 100 100     92 if ( $remaining_time_on_previous_timer and $remaining_time_on_previous_timer < $timeout ) {
49             # a shorter timer was pending, let's use it instead
50 1         7 alarm $remaining_time_on_previous_timer;
51             } else {
52 19         114 alarm $timeout;
53             }
54 20 100       89 defined $context
    100          
55             ? $context
56             ? @result = $code->( @code_args ) # list context
57             : $result[ 0 ] = $code->( @code_args ) # scalar context
58             : $code->( @code_args ); # void context
59 7         4107348 alarm 0;
60             } finally {
61 20     20   944 alarm 0;
62 20 100       106 $exception = $_[ 0 ] if @_;
63             }
64 20         239 }
65              
66 20         625 my $elapsed_time = time - $start_time;
67 20         55 my $new_timeout = $remaining_time_on_previous_timer - $elapsed_time;
68              
69 20 100       131 if ( $new_timeout > 0 ) {
    100          
70             # rearm previous timer with new timeout
71 1         6 alarm $new_timeout;
72             } elsif ( $remaining_time_on_previous_timer ) {
73             # previous timer has already expired; send ALRM
74 1         46 kill 'ALRM', $$;
75             }
76              
77             # handle exceptions
78 19 100       61 if ( defined $exception ) {
79 13 100 100     126 if ( defined blessed( $exception ) and $exception->isa( 'Time::Out::Exception' ) ) {
80 8         20 $@ = $exception; ## no critic (RequireLocalizedPunctuationVars)
81 8         42 return;
82             }
83 5         46 die $exception; ## no critic (RequireCarping)
84             }
85              
86             return
87 6 100       55 defined $context
    100          
88             ? $context
89             ? return @result # list context
90             : $result[ 0 ] # scalar context
91             : (); # void context
92             }
93              
94             1;