Need help understanding ruby's Process.detach - ruby

I'm learning systems programming through ruby and I'm having trouble understanding this behavior:
pid = fork do
Signal.trap("USR1") do
puts "hello!"
end
Signal.trap("TERM") do
puts "Terminating"
exit
end
loop do
end
end
Process.detach(pid)
Process.kill("USR1", pid)
Process.kill("USR1", pid)
Process.kill("USR1", pid)
Process.kill("USR1", pid)
Process.kill("TERM", pid)
This outputs as I expect:
hello!
hello!
hello!
hello!
Terminating
However if I comment out Process.detach, the child process seems to only respond to the signal once (and after termination?):
Terminating
hello!
I'm confused as to why this happens when I don't detach the process, even though I sent it USR1 four times. Can someone help explain this behavior? I think I'm not understanding what it means to detach a process.
Thanks so much!

It's all down to timing, I suspect - that is, the difference is due to how the instructions of your main process and the forked process are scheduled to run relative to each other.
When you do Process.detach, a new thread is created which waits on the exit result of the given process. You can replace the Process.detach with
Thread.new { Process.wait(pid) }
and get the same effect. I suspect that calling detach (and spawning a new thread) gives your forked process, as a side effect, a chance to be scheduled.
If you have no detach, then I would guess your forked process doesn't get a chance to run by the time you tell it to die.
You can see what I mean by relative timing by inserting some sleep calls in your code to see if you can get the same observed behavior without the detach.
For example, this seems to work for me, though your mileage may vary depending on your host platform:
pid = fork do
Signal.trap("USR1") do
puts "hello!"
end
Signal.trap("TERM") do
puts "Terminating"
exit
end
loop do
end
end
sleep(1)
Process.kill("USR1", pid)
Process.kill("USR1", pid)
Process.kill("USR1", pid)
Process.kill("USR1", pid)
sleep(1)
Process.kill("TERM", pid)
This produces:
hello!
hello!
hello!
hello!
Terminating

Related

[Ruby 1.9][Windows] Sending Ctrl-C interrupt signal to a spawned subprocess

I have a main script in Ruby 1.9.3 running on Windows. It's will start another Ruby script that runs as a daemon, do its own stuff, then end the daemon by sending an "INT" signal. The main script and daemon don't otherwise do any data exchange.
The daemon itself can run as a standalone, and we terminate it with Ctrl-C. Here's the part that prepares it for the signal:
def setup_ctrl_c_to_quit
Thread.new do
trap("INT") do
puts "got INT signal"
exit
end
while true
sleep 1
end
end
end
I am currently having trouble having the main script launching and terminating the daemon. Currently, I can start the daemon through spawn and detach as such:
def startDaemon
#daemonPID = spawn("ruby c:/some_folder/daemon.rb", :new_pgroup=>true, :err=>:out)
puts "DaemonPID #{#daemonPID}"
daemonDetatch = Process.detach(#daemonPID)
puts "Detached Daemon . Entering sleep...."
sleep 15
puts "Is daemon detached thread alive? => #{daemonDetatch.alive?}"
puts "Attempt to kill daemon...."
Process.kill( "INT", #daemonPID )
sleep 5
puts "Is daemon detached thread still alive? => #{daemonDetatch.alive?}"
end
Ideally, the last puts statement should show daemonDetatch.alive? to be false. In reality, not only does daemonDetatch.alive? still ended up being true by the end, the daemon also can be found as still running in both the Task Manager and other 3rd party apps such as Process Explorer.
The first question I have is with the spawn(...) function. The official documentation said that :new_pgroup "is necessary for Process.kill(:SIGINT, pid) on the subprocess" send it determines whether the subprocess becomes a new group or not. I've toggled with this paramter, but it didn't seem to make a difference.
Also, I am planning to give this solution a try, which involves using the win32-process gem. I am just wondering if there are other solutions out there.
[Edit]
I have validated the PID of the daemon obtained in the main script, the daemon itself (with $$), and Process Explore, and they are all the same.
I have gotten suggestion from many others to just use "taskkill /f" to terminate the daemon. That will indeed end the daemon, but the daemon cannot trap the "TERM" or "KILL" signals the same way it traps "INT", meaning it will be unable to run its clean-up/quit routine.

Kill a process using its PID in ruby

I'm trying to kill a process using its PID after a given period of time, i.e, if the process keeps running for more than 10 minutes, I have to call a method to kill it.
I have two problems: first, i can't manage to kill the process, i'm using:
Process.kill('INT', pid)
but errors keep popping up like Bad file descriptor, or unsupported name 'SIGQUIT' when I use the QUIT signal instead of INT.
Second, how do I make the 10 minutes timer before I call the method to kill the process?
Thanks.
For your timing, you can use a thread:
Thread.new do
sleep 10 * 60
begin
Process.kill('QUIT', pid)
rescue Errno::ESRCH
# process exited normally
end
end
_, status = Process.wait2 pid
puts status.exited?
I am unsure of why QUIT is not working for you. I could not replicate your error.

Thread#terminate and handling SIGTERM

Here's a simplified version of some code I wrote:
class InfiniteLoop
def run
trap('SIGTERM') do
puts 'exiting'
exit
end
loop {}
end
end
If I run:
InfiniteLoop.new.run
I can ctrl+c and get:
exiting
However, when I do this:
t = Thread.new { InfiniteLoop.new.run }
sleep 1
t.terminate
I don't see:
exiting
Can someone point me in the right direction here? I'd like to have the same behavior when terminating the thread.
If you are not sending a SIGTERM signal (via ctrl+c) the trap block is not executed.
See also the Kernel method:
at_exit { puts 'exiting' }
trap('SIGTERM') will only respond to the signals sent from OS land.
Thread#terminate is ruby code that will kill the thread.
I don't know of a way to specify behavior for a thread to take before it is killed. That might be interesting. But I don't think it exists, because the semantics of Thread#kill/terminate/join wouldn't really allow that.
Try trap("EXIT"). SIGTERM is sent by ctrl-C or a kill command. From the ruby docs:
The special signal name “EXIT” or signal number zero will be invoked just prior to program termination.

Kill process and sub-processes in Ruby on Windows

Currently I'm doing this in one command prompt
require 'win32/process'
p = Process.spawn("C:/ruby193/bin/bundle exec rails s")
puts p
Process.waitpid(p)
and then in another
require 'win32/process'
Process.kill(1,<p>)
The problem is that the process I spawn (the Rails server in this case) spawns a chain of sub-processes. The kill command doesn't kill them, it just leaves them orphaned with no parent.
Any ideas how can I kill the whole spawned process and all its children?
I eventually solved this in the following manner
First I installed the sys-proctable gem
gem install 'sys-proctable'
then used the originally posted code to spawn the process, and the following to kill it (error handling omitted for brevity)
require 'win32/process'
require 'sys/proctable'
include Win32
include Sys
to_kill = .. // PID of spawned process
ProcTable.ps do |proc|
to_kill << proc.pid if to_kill.include?(proc.ppid)
end
Process.kill(9, *to_kill)
to_kill.each do |pid|
Process.waitpid(pid) rescue nil
end
You could change the kill 9 to something a little less offensive of course, but this is the gist of the solution.
One-script solution without any gems. Run the script, CTRL-C to stop everything:
processes = []
processes << Process.spawn("<your process>")
loop do
trap("INT") do
processes.each do |p|
Process.kill("KILL", p) rescue nil
Process.wait(p) rescue nil
end
exit 0
end
sleep(1)
end

How can I handle (or prevent) SIGCHLD signals from ruby backquote invocations?

I have a long-running process with some child processes that must be restarted if they exit. To handle clean restarts of these child processes, I trap the exit signal with
trap("CLD") do
cpid = Process.wait
... handle cleanup ...
end
The long-running process occasionally needs to invoke 'curl' using a backquote as in
`/usr/bin/curl -m 60 http://localhost/central/expire`
The problem is that the backquote invocation is causing me to get a SIGCHLD and making my trap fire. This then gets stuck in the CLD trap because Process.wait does not finish. If there happen to be no (non-backquote) child processes at that time, the Process.wait instead gives an Errno::ECHILD exception.
I can circumvent this problem by wrapping the backquote call with this line before:
sig_handler = trap("CLD", "IGNORE") # Ignore child traps
and this line after the backquote invocation:
trap("CLD", sig_handler) # replace the handler
but this means that I may miss a signal from the (non-backquote) child processes during that window, so I'm not really happy with that.
So, is there a better way to do this?
(I am using ruby 1.9.1p243 on GNU/Linux 2.6.22.6 if it matters)
Update:
The code below illustrates the problem (and my current solution for it).
There seems to be some strange timing issue here since I don't always get the ECHILD exception. But just once is enough to mess things up.
#!/usr/bin/env ruby
require 'pp'
trap("CLD") do
cpid = nil
begin
puts "\nIn trap(CLD); about to call Process.wait"
cpid = Process.wait
puts "In trap(CLD); Noting that ssh Child pid #{cpid}: terminated"
puts "Finished Child termination trap"
rescue Errno::ECHILD
puts "Got Errno::ECHILD"
rescue Exception => excep
puts "Exception in CLD trap for process [#{cpid}]"
puts PP.pp(excep, '')
puts excep.backtrace.join("\n")
end
end
#Backtick problem shown (we get an ECHILD most of the time)
puts "About to invoke backticked curl"
`/usr/bin/curl -m 6 http://developer.yahooapis.com/TimeService/V1/getTime?appid=YahooDemo`
sleep 2; sleep 2 # Need two sleeps because the 1st gets terminated early by the trap
puts "Backticked curl returns"
# Using spawn
puts "About to invoke curl using spawn"
cpid = spawn("/usr/bin/curl -m 6 http://developer.yahooapis.com/TimeService/V1/getTime?appid=YahooDemo")
puts "spawned child pid is #{cpid} at #{Time.now}"
Start monitored subprocesses from a subprocess
Just start your tracked and monitored children from a child of your main process that never exits. That way it won't notice the backtick children exiting...
And if you do this, you could avoid the use of SIGCHLD entirely, as you could just use a loop with a wait in it to notice children exit events.
Other ideas:
ignore one SIGCHLD every time you execute a backtick command. ISTM that you might ignore a "real" SIGCHLD by accident this way, but that won't matter, because you would then get a "spurious" one that you would process.

Resources