File Coverage

blib/lib/Role/RunAlone.pm
Criterion Covered Total %
statement 53 53 100.0
branch 20 20 100.0
condition n/a
subroutine 13 13 100.0
pod 1 1 100.0
total 87 87 100.0


line stmt bran cond sub pod time code
1             package Role::RunAlone;
2              
3 14     14   64365 use 5.006;
  14         88  
4 14     14   80 use strict;
  14         27  
  14         297  
5 14     14   69 use warnings;
  14         26  
  14         638  
6              
7             our $VERSION = 'v0.0.0_02';
8              
9 14     14   82 use Fcntl qw( :flock );
  14         26  
  14         2287  
10 14     14   101 use Carp qw( croak );
  14         38  
  14         673  
11              
12 14     14   1343 use Role::Tiny;
  14         8797  
  14         88  
13              
14             my %default_lock_args = (
15             noexit => 0,
16             attempts => 1,
17             interval => 1,
18             verbose => 0,
19             );
20              
21             my $data_pkg = 'main::DATA';
22             my $caller_cnt = 0;
23              
24             # use a block because the pragmas have lexical scope and we need
25             # to stop warnings/errors from the call to "tell()"
26             {
27 14     14   3128 no strict 'refs';
  14         45  
  14         495  
28 14     14   90 no warnings;
  14         26  
  14         6973  
29              
30             if ( tell( *{$data_pkg} ) == -1 ) {
31              
32             # if we reach this then the __END__ tag does not exist. swap in the
33             # calling script namespace to see if the __DATA__ tag exists. is
34             # there an easier way to find the last element in the caller stack?
35             while ( ++$caller_cnt ) {
36             my @call_info = caller($caller_cnt);
37             last if !@call_info;
38             $data_pkg = $call_info[0] . '::DATA';
39             }
40              
41             if ( ( tell( *{$data_pkg} ) == -1 ) ) {
42             warn "FATAL: No __DATA__ or __END__ tag found\n";
43             __PACKAGE__->_runalone_exit(2);
44             }
45             }
46             }
47              
48             # maybe the script wants to control this
49             __PACKAGE__->runalone_lock unless !!$ENV{RUNALONE_DEFER_LOCK};
50              
51             # is the argument validation over-engineered? maybe, but I'm paranoid.
52             sub runalone_lock {
53 39     39 1 49570 my $proto = shift;
54 39         113 my %args = @_;
55              
56             # set defaults as needed
57 39         136 for ( keys(%default_lock_args) ) {
58 156 100       347 $args{$_} = $default_lock_args{$_} unless defined( $args{$_} );
59             }
60              
61 39 100       203 croak 'ERROR: unknown argument present'
62             if scalar( keys(%args) ) != scalar( keys(%default_lock_args) );
63              
64             # validate integer args
65 38         68 for (qw( attempts interval )) {
66 71 100       1272 croak "$_: invalid value" unless $args{$_} =~ /^[1-9]$/;
67             }
68              
69             # coerce Boolean args
70 28         51 for (qw( noexit verbose )) {
71 56         120 $args{$_} = !!$args{$_};
72             }
73              
74 28         37 my $ret;
75 28         78 while ( $args{attempts}-- > 0 ) {
76 79 100       451 warn "Attempting to lock $data_pkg ...\n" if $args{verbose};
77 79 100       161 last if $ret = $proto->_runalone_lock;
78             warn "Failed, retrying $args{attempts} more time(s)\n"
79 75 100       306 if $args{verbose};
80              
81 75 100       188 sleep( $args{interval} ) if $args{attempts};
82             }
83              
84 28 100       84 if ($ret) {
    100          
85 4 100       16 warn "SUCCESS\n" if $args{verbose};
86             }
87             elsif ( !$args{noexit} ) {
88 5         46 warn "FATAL: A copy of '$0' is already running\n";
89 5         28 __PACKAGE__->_runalone_exit(1);
90             }
91              
92 23         74 return $ret;
93             }
94              
95             # broken out for easier retry testing
96             sub _runalone_lock {
97 2     2   4 my $proto = shift;
98              
99 14     14   115 no strict 'refs';
  14         55  
  14         2473  
100 2         3 return flock( *{$data_pkg}, LOCK_EX | LOCK_NB );
  2         38  
101             }
102              
103             # mock this out in tests
104             sub _runalone_exit {
105 4     4   10 my $proto = shift;
106              
107 4         26 exit( shift );
108             }
109              
110             # helper for test scripts
111             sub _runalone_tag_pkg {
112 8     8   81751 $data_pkg =~ /^(.+)::DATA$/;
113              
114 8         98 return { package => $1, caller => $caller_cnt };
115             }
116              
117             1;
118             __END__