Skip to content

Commit

Permalink
Merge pull request #188 from marshall-lee/enhancement/fork-monitoring
Browse files Browse the repository at this point in the history
Better monitoring of forked Puma processes
  • Loading branch information
Phil Toland authored Aug 2, 2022
2 parents 21501b4 + 11941fe commit d5b7282
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 7 deletions.
17 changes: 10 additions & 7 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@

Dir['./spec/support/**/*.rb'].each { |fn| require fn }

http_server_pid = Process.fork { PatronTestServer.start(false, 9001) }
https_server_pid = Process.fork { PatronTestServer.start(true, 9043) }
http_server = Fork.new { PatronTestServer.start(false, 9001) }

sleep 0.1 # Don't interfere the start up output of two processes.

https_server = Fork.new { PatronTestServer.start(true, 9043) }

RSpec.configure do |c|
c.after(:suite) do
Process.kill("INT", http_server_pid)
Process.kill("INT", https_server_pid)
Process.wait(http_server_pid)
Process.wait(https_server_pid)
http_server.kill("TERM")
https_server.kill("TERM")
http_server.wait
https_server.wait
end
end
end
70 changes: 70 additions & 0 deletions spec/support/fork.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
require 'thread'

class Fork
def initialize(&block)
@mu = Mutex.new
@cond = ConditionVariable.new
@pstate = nil
@pid = Process.fork(&block)
@killed = false

# Start monitoring the PID.
Thread.new { monitor }

# Kill the process anyway when the program exits.
ppid = Process.pid
at_exit do
if ppid == Process.pid # Make sure we are not inside another fork spawned by rspec example.
do_kill("KILL")
end
end
end

# Wait for process to exit.
def wait(timeout = nil)
@mu.synchronize do
next @pstate unless @pstate.nil?

@cond.wait(@mu, timeout)
@pstate
end
end

# Signal the process.
def kill(sig)
already_killed = @mu.synchronize do
old = @killed
@killed = true
old
end
signaled = do_kill(sig)
Thread.new { reaper } if signaled && !already_killed
signaled
end

private

# Signal the process.
def do_kill(sig)
Process.kill(sig, @pid)
true
rescue Errno::ESRCH # No such process
false
end

# Monitor the process state.
def monitor
_, pstate = Process.wait2(@pid)

@mu.synchronize do
@pstate = pstate
@cond.broadcast
end
end

# Wait 500 milliseconds and force terminate.
def reaper
pstate = wait(0.5)
do_kill("KILL") unless pstate
end
end

0 comments on commit d5b7282

Please sign in to comment.