Ruby code that opens a pipe to send data from a forked process to its parent and cleans up zombie threads
On and off, I’ve been working on a piece of Ruby code that uses phantomjs to automate web requests. Under certain situations, and heavy concurrent requests, it’s been known to leave behind zombie threads. In a previous blog post I shared some code that checks for zombie child threads and spawns a new process to remove them. For this particular scenario, the code was wrapped in Sinatra and served via Thin to provide web services. Restarting the parent process (in this case, Thin) was clearly unacceptable, so here is an alternative. The following code opens an IO pipe (which is shared with child processes), forks, executes the code, serializes the result, and writes it to the pipe. The parent process waits for it to finish, reads the result form the pipe, and checks for child processes left behind. If found, they are detached (assuming a parent pid of 1) and then killed.
#!/usr/bin/env ruby
class PipeForker
def self.main
# ensure block was given
raise "Block required" unless block_given?
# open IO pipe
reader, writer = IO.pipe
# fork child process to execute code
child_pid = Process.fork do
# close reader, not needed
reader.close
# do code
result = nil
begin
result = yield
rescue
end
# serialize response, write to pipe
writer.write Marshal.dump result
end
# wait for child process to finish
Process.waitpid child_pid
# get response from io pipe
writer.close
result = Marshal.load reader.gets
# check if child processes still exist, potentially zombie
output = `ps -eo pid,ppid,comm | grep #{child_pid}`
output = output.split("\n").reverse
output.each do |s|
# use regex to split up pid, ppid, comm
matches = /^\s*(\d+)\s+(\d+)\s+(.*)$/.match s
# check if child processes are still running
if matches[1].to_i == child_pid || matches[2].to_i == child_pid
# detach and remove
Process.detach matches[1].to_i
Process.kill 9, matches[1].to_i
end
end
# debug
puts result
result
end
end
# sample execution:
PipeForker.main { "do something crazy" }