File Coverage

blib/lib/MooX/Role/RunAlone.pm
Criterion Covered Total %
statement 51 56 91.0
branch 14 18 77.7
condition 4 8 50.0
subroutine 11 13 84.6
pod 1 1 100.0
total 81 96 84.3


line stmt bran cond sub pod time code
1             package MooX::Role::RunAlone;
2              
3 2     2   1357 use 5.006;
  2         8  
4 2     2   11 use strict;
  2         4  
  2         41  
5 2     2   11 use warnings;
  2         3  
  2         56  
6              
7 2     2   10 use Fcntl qw( :flock );
  2         3  
  2         340  
8 2     2   23 use Carp qw( croak );
  2         5  
  2         109  
9              
10             #use Moo::Role;
11 2     2   1256 use Role::Tiny;
  2         8697  
  2         12  
12              
13             our $VERSION = 'v0.0.0_02';
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   418 no strict 'refs';
  2         4  
  2         53  
31 2     2   12 no warnings;
  2         4  
  2         1064  
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 20     20 1 28332 my $proto = shift;
52 20         71 my %args = @_;
53              
54             # set defaults as needed
55 20         65 for ( keys(%default_lock_args) ) {
56 80   100     238 $args{$_} //= $default_lock_args{$_};
57             }
58              
59 20 100       154 croak 'ERROR: unknown argument present'
60             if scalar( keys(%args) ) != scalar( keys(%default_lock_args) );
61              
62             # validate integer args
63 19         71 for (qw( attempts interval )) {
64 33 100       1146 croak "$_: invalid value" unless $args{$_} =~ /^[1-9]$/;
65             }
66              
67             # coerce Boolean args
68 9         15 for (qw( noexit verbose )) {
69 18         42 $args{$_} = !!$args{$_};
70             }
71              
72 9         14 my $ret = 1;
73 9         40 while ( $args{attempts}-- > 0 ) {
74 17 50       86 warn "attemting to lock $data_pkg ... " if $args{verbose};
75 17 100       44 last if $proto->_runalone_lock( $args{noexit} );
76             warn "failed. Retrying $args{attempts} more time(s)\n"
77 14 50       62 if $args{verbose};
78 14 100       29 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 5         56 warn "FATAL: A copy of '$0' is already running\n";
86 5         44 __PACKAGE__->_runalone_exit(1);
87             }
88             }
89 4 50 33     17 warn "SUCCESS\n" if $args{verbose} && $ret;
90              
91 4         20 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   4 my $proto = shift;
105 2         4 my $noexit = shift;
106              
107 2     2   18 no strict 'refs';
  2         5  
  2         307  
108 2         3 return flock( *{$data_pkg}, LOCK_EX | LOCK_NB );
  2         40  
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__