Daemonizing a child process consequently changes its PID - ruby

pid = Process.fork
#sleep 4
Process.daemon nil, true
if pid.nil? then
job.exec
else
#Process.detach(pid)
end
The pid returned by Process.fork is changed as soon as Process.daemon(nil, true) is run. Is there a way to preserve/track the pid of a forked child process that is subsequently daemonized?
I want to know the pid of the child process from within the parent process. So far the only way I've been able to communicate the pid is through IO.pipe writing the Process.pid to IO#write and then using IO#read from the parent to read it. Less than ideal

Process.daemon does it's own fork, that's why the pid is changed. If you need to know the daemon's pid, why not use Process.pid in the forked part of if?
pid = Process.fork
#sleep 4
Process.daemon nil, true
if pid.nil? then
job.exec
else
Process.detach(Process.pid)
end

The solutions I've come up with involve using Ruby's IO to pass the pid from the child process to the parent.
r, w = IO.pipe
pid = Process.fork
Process.daemon nil, true
w.puts Process.pid
if pid.nil? then
job.exec
else
#Process.detach(pid)
end
pid = r.gets
Another solution involves invoking the process status of all processes that have controlling terminals.
def Process.descendant_processes(base=Process.pid)
descendants = Hash.new{|ht,k| ht[k]=[k]}
Hash[*`ps -eo pid,ppid`.scan(/\d+/).map{|x|x.to_i}].each{|pid,ppid|
descendants[ppid] << descendants[pid]
}
descendants[base].flatten - [base]
end

Related

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

Double fork and stdin

I wrote this code to run my process in a daemon. The goal is to make this process running even if I close its parent. Now, i would like to be able to write something in its stdin. What should I do ? Here's the code.
def daemonize(cmd, options = {})
rd, wr = IO.pipe
p1 = Process.fork {
Process.setsid
p2 = Process.fork {
$0 = cmd #Name of the command
pidfile = File.new(options[:pid_file], 'w')
pidfile.chmod( 0644 )
pidfile.puts "#{Process.pid}"
pidfile.close
Dir.chdir(ENV["PWD"] = options[:working_dir].to_s) if options[:working_dir]
File.umask 0000
STDIN.reopen '/dev/null'
STDOUT.reopen '/dev/null', 'a'
STDERR.reopen STDOUT
Signal.trap("USR1") do
Console.show 'I just received a USR1', 'warning'
end
::Kernel.exec(*Shellwords.shellwords(cmd)) #Executing the command in the parent process
exit
}
raise 'Fork failed!' if p2 == -1
Process.detach(p2) # divorce p2 from parent process (p1)
rd.close
wr.write p2
wr.close
exit
}
raise 'Fork failed!' if p1 == -1
Process.detach(p1) # divorce p1 from parent process (shell)
wr.close
daemon_id = rd.read.to_i
rd.close
daemon_id
end
Is there a way to reopen stdin in something like a pipe instead of /dev/null in which I would be able to write ?
How about a fifo? In linux, you can use the mkfifo command:
$ mkfifo /tmp/mypipe
Then you can reopen STDIN on that pipe:
STDIN.reopen '/tmp/mypipe'
# Do read-y things
Anything else can write to that pipe:
$ echo "roflcopter" > /tmp/mypipe
allowing that data to be read by the ruby process.
(Update) Caveat with blocking
Since fifos block until there's a read and write (e.g. a read is blocked unless there's a write, and vice-versa), it's best handled with multiple threads. One thread should do the reading, passing the data to a queue, and another should handle that input. Here's an example of that situation:
require 'thread'
input = Queue.new
threads = []
# Read from the fifo and add to an input queue (glorified array)
threads << Thread.new(input) do |ip|
STDIN.reopen 'mypipe'
loop do
if line = STDIN.gets
puts "Read: #{line}"
ip.push line
end
end
end
# Handle the input passed by the reader thread
threads << Thread.new(input) do |ip|
loop do
puts "Ouput: #{ip.pop}"
end
end
threads.map(&:join)

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.

Change STDIN with a pipe and it's a directory

I have this
pipe_in, pipe_out = IO.pipe
fork do
# child 1
pipe_in.close
STDOUT.reopen pipe_out
STDERR.reopen pipe_out
puts "Hello World"
pipe_out.close
end
fork do
# child 2
pipe_out.close
STDIN.reopen pipe_in
while line = gets
puts 'child2:' + line
end
pipe_in.close
end
Process.wait
Process.wait
get will always raise an error saying "gets: Is a directory", which doesn't make sense to me. If I change gets to pipe_in.gets it works. What I want to know is, why doesn't STDIN.reopen pipe_in and gets not work?
It works for me, with the following change:
pipe_in.close
end
+pipe_in.close
+pipe_out.close
+
Process.wait
Process.wait
Without this change, you still have the pipes open in the original process, so the reader will never see an end of file. That is, process doing the wait still had the write pipe open leading to a deadlock.

interprocess signal handling in Ruby

I run this script:
t = fork do
Signal.trap "INT" do
puts "child"
exit
end
sleep 10
end
Signal.trap "INT" do
puts "parent"
Process.kill "INT", t
Process.waitpid t
exit
end
Process.waitpid t
When I do CTRL+C, I get
$ ruby sigtest.rb
^Cchild
parent
You can see that "INT" passed to every process and Process.kill "INT", t try to kill process which already died. Is there way to do so that user INT signal will be passed only to the parent? And output will be:
$ ruby sigtest.rb
^Cparent
child
Solution
Rules:
When you press ctrl+c, SIGINT is passed to whole process group.
When you fork new process, signal handlers are not passed to new process
So if you want to control child process signals manually, you have to change GID of the process.
See
http://corelib.rubyonrails.org/classes/Process/Sys.html#M001961
http://ruby.runpaint.org/processes (paragraph "Options Hash")
def system cmd
pid = fork do
exec cmd, {:pgroup => true}
end
Process.wait pid
$?.success?
end
def ` cmd # `make syntax highlight happy
readme, writeme = IO.pipe
pid = fork do
$stdout.reopen writeme
readme.close
exec cmd, {:pgroup => true}
end
writeme.close
data = readme.read
Process.wait pid
data
end
You could always have the child ignore the INT signal.

Resources