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   65291 use 5.006;
  14         51  
4 14     14   79 use strict;
  14         25  
  14         282  
5 14     14   63 use warnings;
  14         24  
  14         619  
6              
7             our $VERSION = 'v0.0.0_03';
8              
9 14     14   97 use Fcntl qw( :flock );
  14         26  
  14         2217  
10 14     14   104 use Carp qw( croak );
  14         32  
  14         694  
11              
12 14     14   1285 use Role::Tiny;
  14         9066  
  14         115  
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   3294 no strict 'refs';
  14         41  
  14         506  
28 14     14   89 no warnings;
  14         28  
  14         6995  
29              
30             if ( tell( *{$data_pkg} ) == -1 ) {
31              
32             # if we reach this then the __END__ tag does not exist, and the
33             # __DATA__ tag is not in the "main" namespace. swap in the
34             # calling script namespace to see if the __DATA__ tag is in there.
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 50337 my $proto = shift;
54 39         115 my %args = @_;
55              
56             # set defaults as needed
57 39         127 for ( keys(%default_lock_args) ) {
58 156 100       343 $args{$_} = $default_lock_args{$_} unless defined( $args{$_} );
59             }
60              
61 39 100       204 croak 'ERROR: unknown argument present'
62             if scalar( keys(%args) ) != scalar( keys(%default_lock_args) );
63              
64             # validate integer args
65 38         55 for (qw( attempts interval )) {
66 71 100       1295 croak "$_: invalid value" unless $args{$_} =~ /^[1-9]$/;
67             }
68              
69             # coerce Boolean args
70 28         48 for (qw( noexit verbose )) {
71 56         115 $args{$_} = !!$args{$_};
72             }
73              
74 28         40 my $ret;
75 28         75 while ( $args{attempts}-- > 0 ) {
76 79 100       448 warn "Attempting to lock $data_pkg ...\n" if $args{verbose};
77 79 100       189 last if $ret = $proto->_runalone_lock;
78             warn "Failed, retrying $args{attempts} more time(s)\n"
79 75 100       300 if $args{verbose};
80              
81 75 100       204 sleep( $args{interval} ) if $args{attempts};
82             }
83              
84 28 100       79 if ($ret) {
    100          
85 4 100       15 warn "SUCCESS\n" if $args{verbose};
86             }
87             elsif ( !$args{noexit} ) {
88 5         48 warn "FATAL: A copy of '$0' is already running\n";
89 5         31 __PACKAGE__->_runalone_exit(1);
90             }
91              
92 23         90 return $ret;
93             }
94              
95             # broken out for easier retry testing
96             sub _runalone_lock {
97 2     2   3 my $proto = shift;
98              
99 14     14   123 no strict 'refs';
  14         52  
  14         2521  
100 2         4 return flock( *{$data_pkg}, LOCK_EX | LOCK_NB );
  2         39  
101             }
102              
103             # mock this out in tests
104             sub _runalone_exit {
105 4     4   11 my $proto = shift;
106              
107 4         21 exit( shift );
108             }
109              
110             # helper for test scripts
111             sub _runalone_tag_pkg {
112 8     8   84291 $data_pkg =~ /^(.+)::DATA$/;
113              
114 8         89 return { package => $1, caller => $caller_cnt };
115             }
116              
117             1;
118             __END__