File Coverage

blib/lib/Proc/tored/PidFile.pm
Criterion Covered Total %
statement 61 62 98.3
branch 19 24 79.1
condition 1 2 50.0
subroutine 20 21 95.2
pod 6 7 85.7
total 107 116 92.2


line stmt bran cond sub pod time code
1             package Proc::tored::PidFile;
2             # ABSTRACT: Manage a service using a pid file
3             $Proc::tored::PidFile::VERSION = '0.18';
4              
5 3     3   239607 use warnings;
  3         10  
  3         141  
6 3     3   22 use strict;
  3         8  
  3         85  
7 3     3   704 use Moo;
  3         11259  
  3         21  
8 3     3   2026 use Carp;
  3         22  
  3         209  
9 3     3   1031 use Guard qw(guard);
  3         1098  
  3         190  
10 3     3   22 use Path::Tiny qw(path);
  3         8  
  3         131  
11 3     3   495 use Try::Tiny;
  3         1162  
  3         181  
12 3     3   513 use Types::Standard -types;
  3         74250  
  3         62  
13 3     3   18260 use Proc::tored::LockFile;
  3         19  
  3         2491  
14              
15             has file_path => (is => 'ro', isa => Str, required => 1);
16              
17             has file => (is => 'lazy', isa => InstanceOf['Path::Tiny'], handles => ['touch']);
18 6     6   144 sub _build_file { path(shift->file_path) }
19              
20             has lockfile => (is => 'lazy', isa => InstanceOf['Proc::tored::LockFile']);
21 6     6   279 sub _build_lockfile { Proc::tored::LockFile->new(file_path => shift->file_path . '.lock') }
22              
23             has mypid => (is => 'ro', isa => Int, default => sub { $$ }, init_arg => undef);
24              
25              
26             sub is_running {
27 22     22 1 1362   my $self = shift;
28 22         75   $self->running_pid == $$;
29             }
30              
31              
32             sub running_pid {
33 45     45 1 895   my $self = shift;
34 45         158   my $pid = $self->read_file;
35 45 100       1470   return 0 unless $pid;
36 21 100       294   return $pid if kill 0, $pid;
37 4         49   return 0;
38             }
39              
40              
41             sub read_file {
42 49     49 1 9272   my $self = shift;
43 49 100       1278   return 0 unless $self->file->is_file;
44 23 50       1157   my ($line) = $self->file->lines({count => 1, chomp => 1}) or return 0;
45 23         14610   my ($pid) = $line =~ /^(\d+)$/;
46 23   50     128   return $pid || 0;
47             }
48              
49              
50             sub write_file {
51 12     12 1 5177   my $self = shift;
52 12 100       58   my $lock = $self->write_lock or return 0;
53 11 100       72   return 0 if $self->running_pid;
54 9         246   $self->file->spew("$$\n");
55 9         4523   return 1;
56             }
57              
58              
59             sub clear_file {
60 12     12 1 1211   my $self = shift;
61 12 50       51   my $lock = $self->write_lock or return;
62 12 100       68   return unless $self->is_running;
63 9 50       232   return unless $self->file->exists;
64 9         480   $self->file->append({truncate => 1});
65 9     9   697   try { $self->file->remove }
66 0     0   0   catch { warn "error unlinking pid file: $_" }
67 9         2169 }
68              
69              
70             sub lock {
71 11     11 1 2110   my $self = shift;
72 11 100   9   57   return guard { $self->clear_file } if $self->write_file;
  9         47  
73 2         333   return;
74             }
75              
76             #-------------------------------------------------------------------------------
77             # Creates a .lock file based on $self->pid_file. While the file exists, the
78             # lock is considered to be held. Returns a Guard that removes the file.
79             #-------------------------------------------------------------------------------
80             sub write_lock {
81 24     24 0 65   my $self = shift;
82 24 50       224   return unless $$ eq $self->mypid;
83 24 50       645   return unless $self->lockfile;
84 24         8894   return $self->lockfile->lock;
85             }
86              
87             1;
88              
89             __END__
90            
91             =pod
92            
93             =encoding UTF-8
94            
95             =head1 NAME
96            
97             Proc::tored::PidFile - Manage a service using a pid file
98            
99             =head1 VERSION
100            
101             version 0.18
102            
103             =head1 SYNOPSIS
104            
105             use Proc::tored::PidFile;
106            
107             my $pidfile = Proc::tored::PidFile->new(file_path => $pid_file_path);
108            
109             if (my $lock = $pidfile->lock) {
110             run_service;
111             }
112             else {
113             die "service is already running under process id "
114             . $pidfile->running_pid;
115             }
116            
117             =head1 DESCRIPTION
118            
119             Allows the use of a pid file to manage a running service.
120            
121             =head1 METHODS
122            
123             =head2 is_running
124            
125             Returns true if the pid indicated by the pid file is the current process.
126            
127             =head2 running_pid
128            
129             Returns true if the pid indicated by the pid file is an active, running
130             process. This is determined by attempting to signal the process using C<kill(0,
131             $pid)>.
132            
133             =head2 read_file
134            
135             Returns the pid stored in the pid file or 0 if the pid file does not exist or
136             is empty.
137            
138             =head2 write_file
139            
140             Writes the current process id to the pid file. Returns true on success, false
141             if the pid file exists and contains a running process id or if unable to
142             atomically write the pid file out.
143            
144             =head2 clear_file
145            
146             Truncates the pid file and then unlinks it.
147            
148             =head2 lock
149            
150             Attempts to write the current process id to the pid file and returns a L<Guard>
151             that will truncate and unlink the pid file if it goes out of scope.
152            
153             {
154             my $lock = $pidfile->lock;
155             run_service;
156             }
157            
158             # $lock goes out of scope and pid file is truncated and unlinked
159            
160             =head1 AUTHOR
161            
162             Jeff Ober <sysread@fastmail.fm>
163            
164             =head1 COPYRIGHT AND LICENSE
165            
166             This software is copyright (c) 2017 by Jeff Ober.
167            
168             This is free software; you can redistribute it and/or modify it under
169             the same terms as the Perl 5 programming language system itself.
170            
171             =cut
172