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