Ruby threads and processes
I made quite a bit of progress in reading the Pick Axe Book over the weekend. I wrote a little program to reflect what I read and to scratch an itch of mine too.
I have three processes that I want to run at all times if it can be helped, but it's ok if there is some downtime for each. Whether these processes are started at system boot time or any time later doesn't make a difference to me, if they go down I want to start them back up. For a (very short) time I just blindly used the cron for my user account to start the processes once every X hours. Unfortunately this meant that five instances of the Unreal Tournament 2004 server would be running at once. svnserve would fail and send me email every few hours when it was invoked and JBoss would consume far too many resources because it was running too many instances. Using the cron to blindly kick off the servers was a bad idea.
Because I wanted to use the cron or an equivalent to periodically check on the servers I had to come up with a way to determine if the servers were already running before trying to start them again. I came up with two short Ruby scripts to solve this problem. The first is a class to represent the process to monitor and start.
class ProcessMonitor
def initialize(name, processString, restartCmd)
@name, @processString, @restartCmd = name, processString, restartCmd
end
def get_process_list
@ps = IO.popen("ps -ef", "r")
end
def is_running?
re = Regexp.new(".*?#{@processString}.*?")
@ps.each do | line |
if re.match(line)
return true
end
end
return false
end
def restart_process
puts "Process is not running. Trying to restart."
process = Process.fork { system("#{@restartCmd}") }
Process.detach(process)
end
def run
get_process_list
if !is_running?
restart_process
end
end
end
The constructor takes a name (which doesn't do anything at this point), a substring of the process to look for in the ps output and the command used to restart the process if it isn't running. The get_process_list serves only to get a ps listing of all the processes running.
The is_running? method examines each entry in the listing until it finds a process that matches the processString snippet passed in when the object was created. The run method will execute the restartCmd string only if the process is not running. The restart_process method uses the Process.fork method to start the command as a process outside the current Ruby interpreter. It then detaches any interest in that long running process, enabling the Ruby interpreter to exit normally.
I should point out that I had some trouble debugging what turned out to not be a problem. I developed this class in Eclipse using and the Ruby interpreter would not terminate when run from inside Eclipse. Running the program on the command line works without this strange hang.
Because I do not want to provide a cron entry to call this class for each process I want to monitor I have written a small driver class that takes input from a file.
require "process_monitor"filename = ARGV[0]
processes = Array.new
IO.foreach(filename) do | line |
params = Array.new
line.each("::") do | param |
paramsThis script takes one filename as its argument. Each line in the file should contain a "::"-delimited list of parameters to create a ProcessMonitor object with. After parsing each line a new ProcessMonitor object is created and run in a new thread. After all threads have been run the script waits for each to finish and the program exits. I plan to put error handling and write test cases sometime this week.




