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.19';
4              
5 3     3   131306 use warnings;
  3         8  
  3         84  
6 3     3   14 use strict;
  3         5  
  3         50  
7 3     3   292 use Moo;
  3         7296  
  3         14  
8 3     3   1491 use Carp;
  3         12  
  3         150  
9 3     3   528 use Guard qw(guard);
  3         729  
  3         148  
10 3     3   18 use Path::Tiny qw(path);
  3         5  
  3         95  
11 3     3   280 use Try::Tiny;
  3         909  
  3         137  
12 3     3   317 use Types::Standard -types;
  3         51933  
  3         31  
13 3     3   11780 use Proc::tored::LockFile;
  3         6  
  3         1447  
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   73 sub _build_file { path(shift->file_path) }
19              
20             has lockfile => (is => 'lazy', isa => InstanceOf['Proc::tored::LockFile']);
21 6     6   123 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 709   my $self = shift;
28 22         41   $self->running_pid == $$;
29             }
30              
31              
32             sub running_pid {
33 44     44 1 481   my $self = shift;
34 44         89   my $pid = $self->read_file;
35 44 100       838   return 0 unless $pid;
36 20 100       177   return $pid if kill 0, $pid;
37 3         18   return 0;
38             }
39              
40              
41             sub read_file {
42 48     48 1 4429   my $self = shift;
43 48 100       847   return 0 unless $self->file->is_file;
44 22 50       660   my ($line) = $self->file->lines({count => 1, chomp => 1}) or return 0;
45 22         3079   my ($pid) = $line =~ /^(\d+)$/;
46 22   50     73   return $pid || 0;
47             }
48              
49              
50             sub write_file {
51 12     12 1 1395   my $self = shift;
52 12 100       29   my $lock = $self->write_lock or return 0;
53 11 100       35   return 0 if $self->running_pid;
54 9         131   $self->file->spew("$$\n");
55 9         2352   return 1;
56             }
57              
58              
59             sub clear_file {
60 12     12 1 694   my $self = shift;
61 12 50       23   my $lock = $self->write_lock or return;
62 12 100       37   return unless $self->is_running;
63 9 50       159   return unless $self->file->exists;
64 9         257   $self->file->append({truncate => 1});
65 9     9   480   try { $self->file->remove }
66 0     0   0   catch { warn "error unlinking pid file: $_" }
67 9         1322 }
68              
69              
70             sub lock {
71 11     11 1 1044   my $self = shift;
72 11 100   9   23   return guard { $self->clear_file } if $self->write_file;
  9         25  
73 2         189   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 31   my $self = shift;
82 24 50       93   return unless $$ eq $self->mypid;
83 24 50       371   return unless $self->lockfile;
84 24         5438   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.19
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