pidfile.rb

 1# frozen_string_literal: true
 2
 3class PIDFile
 4	class NonExclusive < StandardError
 5		def initalize
 6			super("PID file exists and is locked by another process")
 7		end
 8	end
 9
10	class OpenPIDFile
11		def initialize(handle, pid)
12			@handle = handle
13			@pid = pid
14		end
15
16		def refresh
17			# This rewrites the file to move the modified time up
18			# I _very likely_ don't have to rewrite the contents, because
19			# hopefully no one else has dumped crap into my file since that
20			# last time, and I could just write nothing...
21			#
22			# But since I need to do this at least once, it's not worse to do
23			# it later too, it's so few cycles really, and it also heals the
24			# file if something weird happened...
25			@handle.rewind
26			@handle.write("#{@pid}\n")
27			@handle.flush
28			@handle.truncate(@handle.pos)
29		end
30	end
31
32	def initialize(filename, pid=Process.pid)
33		@pid = pid
34		@filename = filename
35	end
36
37	def lock
38		File.open(@filename, File::RDWR | File::CREAT, 0o644) do |f|
39			raise NonExclusive unless f.flock(File::LOCK_EX | File::LOCK_NB)
40
41			begin
42				open_pid = OpenPIDFile.new(f, @pid)
43
44				# Start us off by writing to the file at least once
45				open_pid.refresh
46
47				# Then run the block, which can optionally refresh if they like
48				yield open_pid
49			ensure
50				# Cleanup the pidfile on shutdown
51				# But only if we obtained the lock
52				File.delete(@filename)
53			end
54		end
55	end
56end