Ruby daemon with clean shutdown - ruby

I would like to make a ruby daemon that would gracefully shutdown with a kill command.
I would like to add a signal trap that would wait until #code that could take some time to run finishes before shutting down. How would I add that to something like this:
pid = fork do
pid_file = "/tmp/pids/daemon6.pid"
File.open(pid, 'w'){ |f| f.write(Process.pid) }
loop do
begin
#code that could take some time to run
rescue Exception => e
Notifier.deliver_daemon_rescued_notification(e)
end
sleep(10)
end
end
Process.detach pid
Also, would it be better to have that in a separate script, like a separate kill script instead of having it as part of the daemon code? Like something monit or God would call to stop it?
I appreciate any suggestions.

You can catch Interrupt, like this:
pid = fork do
begin
loop do
# do your thing
sleep(10)
end
rescue Interrupt => e
# clean up
end
end
Process.detach(pid)
You can do the same with Signal.trap('INT') { ... }, but with sleep involved I think it's easier to catch an exception.
Update: this is a more traditional way of doing it, and it makes sure the loop always finishes a complete go before it stops:
pid = fork do
stop = false
Signal.trap('INT') { stop = true }
until stop
# do your thing
sleep(10)
end
end
The downside is that it will always do the sleep, so there will almost always be a delay until the process stops after you've killed it. You can probably get around that by sleeping in bursts, or doing a combination of the variants (rescuing the Interrupt just around the sleep or something).

Related

How to stop a udp_server_loop from the outside

I've written little UDP server in Ruby:
def listen
puts "Started UDP server on #{#port}..."
Socket.udp_server_loop(#port) do |message, message_source|
puts "Got \"#{message}\" from #{message_source}"
handle_incoming_message(message)
end
end
I start it in a separate thread:
thread = Thread.new { listen }
Is there a way to gracefully stop the udp_server_loop from outside the thread without just killing it (thread.kill)? I also dont't want to stop it from the inside by receiving any UDP message. Is udp_server_loop maybe not the right tool for me?
I don’t think you can do this with udp_server_loop (although you might be able to use some of the methods it uses). You are going to have to call IO::select in a loop of your own with some way of signalling it to exit, and some way of waking the thread so you don’t have to send a packet to stop it.
A simple way would be to use the timeout option to select with a variable to set to indicate you want the thread to end, something like:
#halt_loop = false
def listen
puts "Started UDP server on #{#port}..."
sockets = Socket.udp_server_sockets(#port)
loop do
readable, _, _ = IO.select(sockets, nil, nil, 1) # timeout 1 sec
break if #halt_loop
next unless readable # select returns nil on timeout
Socket.udp_server_recv(readable) do |message, message_source|
puts "Got \"#{message}\" from #{message_source}"
handle_incoming_message(message)
end
end
end
You then set #halt_loop to true when you want to stop the thread.
The downside to this is that it is effectively polling. If you decrease the timeout then you potentially do more work on an empty loop, and if you increase it you have to wait longer when stopping the thread.
Another, slightly more complex solution would be to use a pipe and have the select listen on it along with the sockets. You could then signal directly to finish the select and exit the thread.
#read, #write = IO.pipe
#halt_loop = false
def listen
puts "Started UDP server on #{#port}..."
sockets = Socket.udp_server_sockets(#port)
sockets << #read
loop do
readable, _, _ = IO.select(sockets)
break if #halt_loop
readable.delete #read
Socket.udp_server_recv(readable) do |message, message_source|
puts "Got \"#{message}\" from #{message_source}"
handle_incoming_message(message)
end
end
end
def end_loop
#halt_loop = true
#write.puts "STOP!"
end
To exit the thread you just call end_loop which sets the #halt_loop flag then writes to the pipe, making the other end readable and causing the other thread to return from select.
You could have this code check the readable IOs and exit if one of them is the read end of the pipe instead of using the variable, but at least on Linux there is a potential bug where a call to select might return a file descriptor as readable when it actuallt isn’t. I don’t know if Ruby deals with this, so better safe than sorry.
Also be sure to remove the pipe from the readable array before passing it to udp_server_recv. It’s not a socket so will cause an exception if you don’t.
A downside to this technique is that pipes are “[n]ot available on all platforms".
Although I doubt I understand what would be wrong with Thread::kill and/or Thread#exit, you might use the thread local variable for that.
def listen
Socket.udp_server_loop(#port) do |message, message_source|
break :interrupted if Thread.current[:break]
handle_incoming_message(message)
end
end
and do
thread[:break] = true
from the outside.

How to terminate a child process as part of terminating the thread that created it

I have a Ruby application that spawns a thread on-demand which in turn does a system call to execute a native binary.
I want to abort this call before the native call completes.
I tried using all options the Thread documentation provided, like kill, raise and terminate, but nothing seems to help.
This is what I'm trying to do:
class Myserver < Grape::API
##thr = nil
post "/start" do
puts "Starting script"
##thr = Thread.new do
op=`sh chumma_sh.sh`
puts op
end
puts ##thr.status
end
put "/stop" do
##thr.terminate
##thr.raise
Thread.kill(##thr)
puts ##thr.status
end
end
The thread appears to enter a sleep state as an IO operation is in process, but how do I kill the thread so that all child processes it created are terminated and not attached to root.
Doing ps-ef | grep for the script returns the pid, and I could try Process.kill pid but wanted to know if there are better options.
I don't have the option at this moment of modifying how the script is executed as it is part of an inherited library.
Using ps is the only approach I've found that works. If you also want to kill child threads, you could use something like this:
def child_pids_recursive(pid)
# get children
pipe = IO.popen("ps -ef | grep #{pid}")
child_pids = pipe.readlines.map do |line|
parts = line.split(/\s+/)
parts[2] if parts[3] == pid.to_s && parts[2] != pipe.pid.to_s
end.compact
pipe.close
# get grandchildren
grandchild_pids = child_pids.map do |cpid|
child_pids_recursive(cpid)
end.flatten
child_pids + grandchild_pids
end
def kill_all(pid)
child_pids_recursive(pid).reverse.each do |p|
begin
Process.kill('TERM', p.to_i)
rescue
# ignore
end
end
end

Ruby Wait for SubProcess to Finish

I'm forking another ruby script because there's no good way to catch exceptions on that script and it occasionally errors out. If and when it finishes and/or errors out I want to re-spawn it and let it continue where it left off from. Here's my script:
def runit(number)
r = spawn("cmd /c ruby c:\\bot\\myscript.rb", [:out, :err] => ["c:\\bot\\myscript#{number}.log", 'w'], :new_pgroup => true)
Process.detach(r)
r
end
def runme
r = runit(1)
Process.wait(r)
end
something = "nothing"
while something == 'nothing'
runme
end
My problem is that when the subprocess errors out so does the main process. This causes the loop to end.
How can I fork a process and restart it in case it dies or errors out?

How to wait for the spawned process

I'm trying to write a simple script which can execute mongodb server in the background. Currently I use Process.spawn method. It works but I have to wait some time for mongod to be process fully operational (boot process is completed and the database is waiting for new connections).
def run!
return if running?
FileUtils.mkdir_p(MONGODB_DBPATH)
command = "mongod --port #{port} --dbpath #{MONGODB_DBPATH} --nojournal"
log_file = File.open(File.expand_path("log/test_mongod.log"), "w+")
#pid = Process.spawn(command, out: log_file)
# TODO wait for the connection (waiting for connections on port xxxx)
sleep 2
yield port if block_given?
end
Here is the full this script: https://github.com/lucassus/mongo_browser/blob/master/spec/support/mongod.rb#L22
Is it somehow possible to remove this ugly arbitrary sleep 2 from this code?
My first guess is to connect a pipe to the spawned process and wait until "waiting for connections on port xxxx" message is written to the pipe. But I don't know how to implement it.
Here is a pattern for waiting on some output from a child process:
def run_and_wait_for_this regexp_to_wait_for, *cmd
rd, wr = IO.pipe
pid = Process.spawn(*cmd, out: wr)
pid_waiter = Thread.new { Process.wait(pid); wr.close }
thr = Thread.new do
buffer = ''
until buffer =~ regexp_to_wait_for
buffer << rd.readpartial(100)
end
end
thr.join
rescue EOFError
ensure
rd.close
end
run_and_wait_for_this( /waiting for connections on port xxxx/, 'mongo', '--port', port, '--dbpath', MONGODB_PATH, '--nojournal' )
It blocks until the process flushes the expected output into the pipe.

Fork child process with timeout and capture output

Say I have a function like below, how do I capture the output of the Process.spawn call? I should also be able to kill the process if it takes longer than a specified timeout.
Note that the function must also be cross-platform (Windows/Linux).
def execute_with_timeout!(command)
begin
pid = Process.spawn(command) # How do I capture output of this process?
status = Timeout::timeout(5) {
Process.wait(pid)
}
rescue Timeout::Error
Process.kill('KILL', pid)
end
end
Thanks.
You can use IO.pipe and tell Process.spawn to use the redirected output without the need of external gem.
Of course, only starting with Ruby 1.9.2 (and I personally recommend 1.9.3)
The following is a simple implementation used by Spinach BDD internally to capture both out and err outputs:
# stdout, stderr pipes
rout, wout = IO.pipe
rerr, werr = IO.pipe
pid = Process.spawn(command, :out => wout, :err => werr)
_, status = Process.wait2(pid)
# close write ends so we could read them
wout.close
werr.close
#stdout = rout.readlines.join("\n")
#stderr = rerr.readlines.join("\n")
# dispose the read ends of the pipes
rout.close
rerr.close
#last_exit_status = status.exitstatus
The original source is in features/support/filesystem.rb
Is highly recommended you read Ruby's own Process.spawn documentation.
Hope this helps.
PS: I left the timeout implementation as homework for you ;-)
I followed Anselm's advice in his post on the Ruby forum here.
The function looks like this -
def execute_with_timeout!(command)
begin
pipe = IO.popen(command, 'r')
rescue Exception => e
raise "Execution of command #{command} unsuccessful"
end
output = ""
begin
status = Timeout::timeout(timeout) {
Process.waitpid2(pipe.pid)
output = pipe.gets(nil)
}
rescue Timeout::Error
Process.kill('KILL', pipe.pid)
end
pipe.close
output
end
This does the job, but I'd rather use a third-party gem that wraps this functionality. Anyone have any better ways of doing this? I have tried Terminator, it does exactly what I want but it does not seem to work on Windows.

Resources