File Coverage

blib/lib/MooX/Role/RunAlone.pm
Criterion Covered Total %
statement 51 56 91.0
branch 17 18 94.4
condition 5 8 62.5
subroutine 11 13 84.6
pod 1 1 100.0
total 85 96 88.5


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