07 Jan 2014, 17:22
Prof_pragsmall

Dave Qorashi (5 posts)

Hi Jesse,

In chapter 18 in the book, you mentioned that:
“Consider for a moment: a Ruby script that shells out to a long-running shell command, eg. a long backup script. What happens if you kill the Ruby script with a Ctrl-C? If you try this out you’ll notice that the long-running backup script is not orphaned, it does not continue on when its parent is killed. We haven’t set up any code to forward the signal from the parent to the child, so how is this done? The terminal receives the signal and forwards it on to any process in the foreground process group. In this case, both the Ruby script and the long-running shell command would part of the same process group, so they would both be killed by the same signal. Your terminal handles session groups in a special way: sending a signal to the session leader will forward that signal to all the process groups in that session, which will forward it to all the processes in those process groups.”

This is sensible to me; but the problem aroused when I was reading Chapter 23 and I was trying to understand the provided sample program source code at page 142.

require 'socket'

# Open a socket.
socket = TCPServer.open('0.0.0.0', 8080)
puts "Process ID: #{Process.pid}"
# Preload app code.
# require 'config/environment'

wpids = []
# Forward any relevant signals to the child processes.
[:INT, :QUIT].each do |signal|
  Signal.trap(signal) {
    wpids.each { |wpid| Process.kill(signal, wpid) }
  }
end

# For keeping track of child process pids.

5.times {
  wpids << fork do
    loop {
      connection = socket.accept
      connection.puts 'Hello Readers!'
      connection.close
    }
  end
  p wpids
}

Process.waitall

Over there, we’re forking 5 new processes. My question is: why we need to forward signals to the children and manage clean up manually? Isn’t Kernel supposed to propagate signals through the forked processes?

I removed the signal forwarding from the sample code and I tried to kill the parent. I was hoping with killing it, children die too; but that was against the reality. Children lived afterward.

I wrote a sample C program:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    if ( argc != 2 )
    {
        printf( "usage: %s <Process Group ID>", argv[0] );
        return 1;
    }
    killpg(atoi(argv[1]),  SIGKILL);
    return 0;
}

I run the program with providing the pid of parent to the program. The program as you see will kill all processes with the same process group. It worked as I expected but I’m not getting why sending an interrupt to parent is not propagating the signal to the children as you mentioned in chapter 18?

07 Jan 2014, 13:05
Profile_pragsmall

Jesse Storimer (13 posts)

The ‘signal forwarding’ behaviour I mentioned is a shell feature. It only works if you launch the program from the command line and send signals with the keyboard shortcuts (like Ctrl-c).

However, in most production environments, your server will be running as a daemon (a.k.a. disconnected from any terminal). In this case you’ll no longer have the shell sending your signal to the whole process group. You’ll send the signal to the parent, then the parent must take care of propagating signals to its children if need be.

Does that clear it up?

07 Jan 2014, 17:22
Prof_pragsmall

Dave Qorashi (5 posts)

Hi Jesse,

Thanks very much for the answers. It makes sense to me, just one problem.

In the included prefork.rb that you provided:

require 'socket'

# Open a socket.
socket = TCPServer.open('0.0.0.0', 8080)
puts "Process ID: #{Process.pid}"
# Preload app code.
# require 'config/environment'

wpids = []
# Forward any relevant signals to the child processes.
[:INT, :QUIT].each do |signal|
  Signal.trap(signal) {
    wpids.each { |wpid| Process.kill(signal, wpid) }
  }
end

# For keeping track of child process pids.

5.times {
  wpids << fork do
    loop {
      connection = socket.accept
      connection.puts 'Hello Readers!'
      connection.close
    }
  end
}

Process.waitall

I run this program through my shell (both zsh and bash). According your description, I expected that if I send any signals to program with keyboard shortcuts (Ctrl-c) the master propagates the signals and kills the children. Although, that didn’t happen and program freezed. I think this is because we explicitly added signal catching to the program. Am I right?

[:INT, :QUIT].each do |signal|
  Signal.trap(signal) {
    wpids.each { |wpid| Process.kill(signal, wpid) }
  }
end

I removed above section from the program’s code, so the program was like:

require 'socket'

# Open a socket.
socket = TCPServer.open('0.0.0.0', 8080)
puts "Process ID: #{Process.pid}"
# Preload app code.
# require 'config/environment'

wpids = []

# For keeping track of child process pids.

5.times {
  wpids << fork do
    loop {
      connection = socket.accept
      connection.puts 'Hello Readers!'
      connection.close
    }
  end
}

Process.waitall

After running the program in shell and sending a signal using Ctrl-c I got “prefork.rb:22:in accept'prefork.rb:22:in accept’: Interrupt” which seems normal to me. All the children quitted afterward.

Back to original sample that you provided, could you please describe why sending signals (ex. Ctrl-c) to the program don’t do anything?

23 Jan 2014, 06:35
Profile_pragsmall

Jesse Storimer (13 posts)

Big omission by me. I forgot to add exit to the signal handlers. When you press Ctrl-C, it looks as though nothing happens because we forgot to exit the process!

When I add exit at the end of the Signal.trap block I get the expected behaviour. Let me know if that fixes it for you too.

24 Jan 2014, 05:55
Prof_pragsmall

Dave Qorashi (5 posts)

Thanks for your comment. I added ‘exit’ at the end of the block and program behaved as I expected. But there is an issue, if I remove ‘wpids.each { |wpid| Process.kill(signal, wpid) }’ line from trap method, it works well too.

require 'socket'

# Open a socket.
socket = TCPServer.open('0.0.0.0', 8080)
puts "Process ID: #{Process.pid}"
# Preload app code.
# require 'config/environment'

wpids = []
# Forward any relevant signals to the child processes.
[:INT, :QUIT].each do |signal|
  Signal.trap(signal) {
    exit
  }
end

# For keeping track of child process pids.

5.times {
  wpids << fork do
    loop {
      connection = socket.accept
      connection.puts 'Hello Readers!'
      connection.close
    }
  end
}

Process.waitall
My question is: do we really need “wpids.each { wpid Process.kill(signal, wpid) }” line in our code?

I checked all the instances of ruby process in the system with using “ps aux || grep ruby” command. After running above code and then sending Ctrl-C to it, all the instances died; so, it seems to me without the program is working perfectly.
What I’m talking about is that, it seems that with exiting from parent, all the children exited too, but I don’t know why this kind of behavior is happening!

31 Jan 2014, 03:44
Profile_pragsmall

Jesse Storimer (13 posts)

Right again David. So this goes back to what I was saying about the shell sending that signal to the whole process group. If you send Ctrl-C to the spawned process at the shell it will kill ALL the processes in the foreground process group.

If you want to see a situation where that bit of signal forwarding code is necessary, try adding Process.daemon right at the top of that example.

Then, when you spawn the server, it will immediately detach from the terminal and you’ll no longer see the output. If you find the master pid on the system and send it INT (for instance), in that the signal-forwarding code is necessary for it to properly cleanup the child processes.

Try it out and let me know how it goes!

  You must be logged in to comment