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.20';
4              
5 3     3   236010 use warnings;
  3         14  
  3         136  
6 3     3   102 use strict;
  3         7  
  3         82  
7 3     3   388 use Moo;
  3         10189  
  3         23  
8 3     3   2071 use Carp;
  3         11  
  3         253  
9 3     3   771 use Guard qw(guard);
  3         1004  
  3         158  
10 3     3   21 use Path::Tiny qw(path);
  3         7  
  3         254  
11 3     3   480 use Try::Tiny;
  3         1240  
  3         247  
12 3     3   431 use Types::Standard -types;
  3         70597  
  3         40  
13 3     3   15334 use Proc::tored::LockFile;
  3         13  
  3         1788  
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   126 sub _build_file { path(shift->file_path) }
19              
20             has lockfile => (is => 'lazy', isa => InstanceOf['Proc::tored::LockFile']);
21 6     6   201 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 1478   my $self = shift;
28 22         81   $self->running_pid == $$;
29             }
30              
31              
32             sub running_pid {
33 44     44 1 2653   my $self = shift;
34 44         189   my $pid = $self->read_file;
35 44 100       1532   return 0 unless $pid;
36 20 100       365   return $pid if kill 0, $pid;
37 3         35   return 0;
38             }
39              
40              
41             sub read_file {
42 48     48 1 7078   my $self = shift;
43 48 100       1530   return 0 unless $self->file->is_file;
44 22 50       1157   my ($line) = $self->file->lines({count => 1, chomp => 1}) or return 0;
45 22         5059   my ($pid) = $line =~ /^(\d+)$/;
46 22   50     141   return $pid || 0;
47             }
48              
49              
50             sub write_file {
51 12     12 1 3782   my $self = shift;
52 12 100       55   my $lock = $self->write_lock or return 0;
53 11 100       67   return 0 if $self->running_pid;
54 9         332   $self->file->spew("$$\n");
55 9         4643   return 1;
56             }
57              
58              
59             sub clear_file {
60 12     12 1 1863   my $self = shift;
61 12 50       53   my $lock = $self->write_lock or return;
62 12 100       66   return unless $self->is_running;
63 9 50       232   return unless $self->file->exists;
64 9         448   $self->file->append({truncate => 1});
65 9     9   798   try { $self->file->remove }
66 0     0   0   catch { warn "error unlinking pid file: $_" }
67 9         123845 }
68              
69              
70             sub lock {
71 11     11 1 2739   my $self = shift;
72 11 100   9   219   return guard { $self->clear_file } if $self->write_file;
  9         56  
73 2         259   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 67   my $self = shift;
82 24 50       212   return unless $$ eq $self->mypid;
83 24 50       697   return unless $self->lockfile;
84 24         9382   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.20
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