File Coverage

blib/lib/Linux/Pidfile.pm
Criterion Covered Total %
statement 11 13 84.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 16 18 88.8


line stmt bran cond sub pod time code
1             package Linux::Pidfile;
2             {
3             $Linux::Pidfile::VERSION = '0.16';
4             }
5             BEGIN {
6 1     1   31686 $Linux::Pidfile::AUTHORITY = 'cpan:TEX';
7             }
8             # ABSTRACT: Pidfile handling to help control processes.
9              
10 1     1   23 use 5.010_000;
  1         3  
  1         40  
11 1     1   1513 use mro 'c3';
  1         722  
  1         6  
12 1     1   37 use feature ':5.10';
  1         2  
  1         105  
13              
14 1     1   596 use Moose;
  0            
  0            
15             use namespace::autoclean;
16              
17             # use IO::Handle;
18             # use autodie;
19             # use MooseX::Params::Validate;
20              
21             use Carp;
22             use File::Blarf;
23              
24             has 'pidfile' => (
25             'is' => 'ro',
26             'isa' => 'Str',
27             'required' => 1,
28             );
29              
30             has 'timeout' => (
31             'is' => 'rw',
32             'isa' => 'Num',
33             'default' => 1,
34             );
35              
36             has 'restart_timeout' => (
37             'is' => 'rw',
38             'isa' => 'Num',
39             'default' => 30,
40             );
41              
42             has 'force_restart' => (
43             'is' => 'rw',
44             'isa' => 'Bool',
45             'default' => 0,
46             );
47              
48             with qw(Log::Tree::RequiredLogger);
49              
50             sub BUILD {
51             my $self = shift;
52              
53             if ( !-f $self->pidfile() ) {
54             my @path = split /\//, $self->pidfile();
55             my $pidfile_name = pop @path;
56             my $pidfile_dir = join( '/', @path );
57             if ( !-w $pidfile_dir && -w '/tmp' ) {
58             $self->{'pidfile'} = '/tmp/' . $pidfile_name;
59             }
60             }
61             return 1;
62             }
63              
64             sub DEMOLISH {
65             #
66             # BIG FAT WARNING
67             #
68             # YOU MUST NOT REMOVE THE PIDFILE ON DEMOLISH!
69             #
70             # THIS WOULD RENDER THIS MODULE USELESS!
71             #
72              
73             return 1;
74             }
75              
76             # Write a pidfile containig this process pid
77             # and return false if the file exists and a process
78             # with the name of this script and this pid (i.e. this script)
79             # is already running.
80             sub create {
81             my $self = shift;
82              
83             # Check if this script is already running.
84             if ( my $pid_from_file = $self->this_script_is_running() ) {
85             $self->logger()->log( message => 'This script is already running with the PID '.$pid_from_file.'. Checking if its myself or a timeout ...', level => 'debug', );
86             # if the enclosing script is called by e.g. start-stop-daemon
87             # this MAY have already created an (correct) pidfile.
88             # in that case we should just keep it and go on
89             if($pid_from_file == $$) {
90             # IT's ME!
91             $self->logger()->log( message => 'This script already has a correct pidfile aborting w/ success', level => 'debug', );
92             return 1;
93             }
94              
95             # Check if the script is running too long.
96             my $timeout = $self->timeout() || 1;
97             if ( -M $self->pidfile() >= $timeout ) {
98             my $runtime = -M $self->pidfile();
99             $self->logger()->log( message => 'Script is running too long, running since '.$runtime.' days.', level => 'warning', );
100              
101             # Should we kill the long-running script and start again?
102             # This can be dangerous since it can lead to corrupt backups.
103             if ( $self->force_restart() ) {
104             $self->logger()->log( message => 'force_restart requested. Killing long-running precedessor and restarting.', level => 'notice', );
105             my $cmd = 'kill '.$pid_from_file;
106             my $retval = system($cmd) >> 8;
107             $self->logger()->log( message => 'CMD '.$cmd.' gave NON-SUCCESS retval: '.$retval, level => 'warning', ) unless ( $retval == 0 );
108              
109             if($self->pid_is_running($pid_from_file)) {
110             $self->logger()->log( message => 'Sleeping '.$self->restart_timeout().' seconds to let kill take effekt.', level => 'info', );
111             sleep($self->restart_timeout());
112              
113             if($self->pid_is_running($pid_from_file)) {
114             $cmd = 'kill -9 '.$pid_from_file;
115             $retval = system($cmd) >> 8;
116             $self->logger()->log( message => 'CMD '.$cmd.' gave NON-SUCCESS retval: '.$retval, level => 'warning', ) unless ( $retval == 0 );
117             $self->logger()->log( message => 'Sleeping '.$self->restart_timeout().' seconds to let kill take effekt.', level => 'info', );
118             sleep($self->restart_timeout());
119             }
120             }
121             $self->remove();
122             return $self->_write();
123             }
124             } else {
125             $self->logger()->log( message => 'Script already running w/ pid '.$pid_from_file, level => 'warning', );
126             # Abort - script is already running
127             return;
128             }
129             } else {
130             $self->logger()->log( message => 'Stale Pidfile. Previous run exited abnormaly. Removing pidfile at '.$self->pidfile(), level => 'warning', );
131             $self->remove();
132             }
133             $self->logger()->log( message => 'Writing pid '.$$.' to pidfile at '.$self->pidfile(), level => 'debug', );
134              
135             return $self->_write();
136             }
137              
138             sub this_script_is_running {
139             my $self = shift;
140              
141             my $pid_from_file;
142             if($pid_from_file = $self->pidfile_is_running()) {
143             my $cmdline_file = '/proc/'.$pid_from_file.'/cmdline';
144             $self->logger()->log( message => 'Reading from file '.$cmdline_file, level => 'debug', );
145             my $cmdline = File::Blarf::slurp( $cmdline_file );
146             if($cmdline =~ m/\Q$0\E/i) {
147             $self->logger()->log( message => 'This script ('.$0.'/'.$cmdline.') is alread running w/ pid '.$pid_from_file, level => 'debug', );
148             return $pid_from_file;
149             }
150             }
151              
152             return;
153             }
154              
155             sub pidfile_is_running {
156             my $self = shift;
157              
158             # no pidfile defined, can't check
159             if(!$self->pidfile()) {
160             return;
161             }
162              
163             # no pidfile exists, can't be running
164             # note: if the pidfile is gone it's not our fault!
165             if(!-e $self->pidfile()) {
166             return;
167             }
168              
169             my $pid_from_pidfile = File::Blarf::slurp( $self->pidfile(), { Chomp => 1, Flock => 1, } );
170              
171             # no valid pid in pidfile
172             if(!$pid_from_pidfile || $pid_from_pidfile !~ m/^\d+$/) {
173             return;
174             }
175              
176             if(!-e '/proc/'.$pid_from_pidfile) {
177             return;
178             }
179              
180             return $pid_from_pidfile;
181             }
182              
183             sub pid_is_running {
184             my $self = shift;
185             my $pid = shift;
186              
187             return unless $pid;
188              
189             if(-e '/proc/'.$pid) {
190             return 1;
191             }
192              
193             return;
194             }
195              
196             sub _write {
197             my $self = shift;
198              
199             # Write this scripts pid.
200             return File::Blarf::blarf( $self->pidfile(), $$, { Flock => 1, } );
201             }
202              
203             sub remove {
204             my $self = shift;
205             my $force = shift || 0;
206              
207             if ( -f $self->pidfile() ) {
208              
209             # Check content
210             my $pid = File::Blarf::slurp( $self->pidfile(), { Chomp => 1, } );
211              
212             # A pidfile should only contain numbers
213             if ( $pid =~ m/^\d+$/ ) {
214              
215             # our pid or force
216             if ( $pid == $$ || $force ) {
217             unlink( $self->pidfile() );
218             return 1;
219             }
220              
221             # parent pid
222             elsif ( $pid == getppid() ) {
223             unlink( $self->pidfile() );
224             return 2;
225             }
226             else {
227              
228             # Not our pidfile
229             return 0;
230             }
231             }
232             else {
233              
234             # Invalid content. Doesn't look like a pidfile.
235             return 0;
236             }
237             }
238             else {
239              
240             # File not found
241             return 0;
242             }
243             }
244              
245             no Moose;
246             __PACKAGE__->meta->make_immutable();
247              
248             1;
249              
250             __END__
251              
252             =pod
253              
254             =encoding utf-8
255              
256             =head1 NAME
257              
258             Linux::Pidfile - Pidfile handling to help control processes.
259              
260             =head1 SYNOPSIS
261              
262             use Linux::Pidfile;
263             my $Pid = Linux::Pidfile::->new();
264             $Pid->create() or die('Already running!');
265             # ...
266             $Pid->remove();
267              
268             =head1 DESCRIPTION
269              
270             Pidfile handling to help processes avoid running multiple times.
271              
272             =head1 METHODS
273              
274             =head2 BUILD
275              
276             Initialize the pidfile location.
277              
278             =head2 DEMOLISH
279              
280             Placeholder.
281              
282             =head2 create
283              
284             Try to create a new pidfile, if the proc is already running exit with false.
285              
286             =head2 pid_is_running
287              
288             Return true if a process with the given pid is already running.
289              
290             =head2 pidfile_is_running
291              
292             Return true if the pidfile is configured, exists
293             and a process with this pid is running.
294              
295             =head2 this_script_is_running
296              
297             Return true if this script is running.
298              
299             =head2 remove
300              
301             Remove the pidfile. Should be called when the invoking process is about to exit.
302              
303             =head1 NAME
304              
305             Linux::Pidfile - Pidfile handling to help processes avoid running multiple times.
306              
307             =head1 AUTHOR
308              
309             Dominik Schulz <dominik.schulz@gauner.org>
310              
311             =head1 COPYRIGHT AND LICENSE
312              
313             This software is copyright (c) 2012 by Dominik Schulz.
314              
315             This is free software; you can redistribute it and/or modify it under
316             the same terms as the Perl 5 programming language system itself.
317              
318             =cut