man 7 signal says:

Each signal has a current disposition, which determines how the process behaves when it is delivered the signal.

When a process receives a signal, the process can react in different ways to that signals. UNIX has a set of standard signals that expect a certain behavior from a process when sent the signal. For example if you have evert hit CTRL+C from your shell to kill a process, that sends the SIGINT signal to the process.

Within your own process you can override or extend the behaviour when a signal is sent to your process. A common pattern to use is the Signal Queue. The idea behind this pattern is to trap a signal and store it in a local queue within your process. At some point in your process when it is safe to respond to the signal your process and dequeue the next signal in the queue and respond to it.

Here’s a quick example:

SIGNAL_QUEUE = []
p Process.pid

signals = {:USR1 => "HI", :USR2 => "BYE", :HUP => "HICCUP", :INT => "INTERRUPT", :QUIT => "QUIT", :TERM => "TERMINATE"}
signals.keys.each do |signal|
  Signal.trap(signal) { SIGNAL_QUEUE.push(signal) }
end

loop do
  item = SIGNAL_QUEUE.shift
  if item
    p "received #{item} in #{Process.pid}"
    p signals[item] if item
  else
    # do your job
    sleep 2
  end
end

In the above code I am trapping several signals and storing it in a queue. This allows me to choose when to respond to the signal. When it is safe to do so, the process will dequeue the next signal from the queue and respond to it.

An easy way to send a signal to a process is to use the kill command.

man 1 kill says:

kill - send a signal to a process

A common usage of the kill command is:

$ kill -9 <PID>

What this does is sends the KILL signal to the process with the specified process id.

You can also send other signals to a process using the kill command like so:

$ kill -USR1 <PID>

If you use unicorn as your app server you probably have a shell script that sends the USR2 signal to the unicorn process to tell it to reexec itself to do a zero downtime deployment.

If we take a peek at the unicorn code base, it comes with an example init shell script that looks like this:

 1 #!/bin/sh
 2 set -e
 3 # Example init script, this can be used with nginx, too,
 4 # since nginx and unicorn accept the same signals
 5 
 6 # Feel free to change any of the following variables for your app:
 7 TIMEOUT=${TIMEOUT-60}
 8 APP_ROOT=/home/x/my_app/current
 9 PID=$APP_ROOT/tmp/pids/unicorn.pid
10 CMD="/usr/bin/unicorn -D -c $APP_ROOT/config/unicorn.rb"
11 INIT_CONF=$APP_ROOT/config/init.conf
12 action="$1"
13 set -u
14 
15 test -f "$INIT_CONF" && . $INIT_CONF
16 
17 old_pid="$PID.oldbin"
18 
19 cd $APP_ROOT || exit 1
20 
21 sig () {
22 	test -s "$PID" && kill -$1 `cat $PID`
23 }
24 
25 oldsig () {
26 	test -s $old_pid && kill -$1 `cat $old_pid`
27 }
28 
29 case $action in
30 start)
31 	sig 0 && echo >&2 "Already running" && exit 0
32 	$CMD
33 	;;
34 stop)
35 	sig QUIT && exit 0
36 	echo >&2 "Not running"
37 	;;
38 force-stop)
39 	sig TERM && exit 0
40 	echo >&2 "Not running"
41 	;;
42 restart|reload)
43 	sig HUP && echo reloaded OK && exit 0
44 	echo >&2 "Couldn't reload, starting '$CMD' instead"
45 	$CMD
46 	;;
47 upgrade)
48 	if sig USR2 && sleep 2 && sig 0 && oldsig QUIT
49 	then
50 		n=$TIMEOUT
51 		while test -s $old_pid && test $n -ge 0
52 		do
53 			printf '.' && sleep 1 && n=$(( $n - 1 ))
54 		done
55 		echo
56 
57 		if test $n -lt 0 && test -s $old_pid
58 		then
59 			echo >&2 "$old_pid still exists after $TIMEOUT seconds"
60 			exit 1
61 		fi
62 		exit 0
63 	fi
64 	echo >&2 "Couldn't upgrade, starting '$CMD' instead"
65 	$CMD
66 	;;
67 reopen-logs)
68 	sig USR1
69 	;;
70 *)
71 	echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop|reopen-logs>"
72 	exit 1
73 	;;
74 esac

If you scan down to line 35, 39, 43 or 48 you will notice that the shell script is sending a signal to the unicorn process using kill command and it’s process id. Unicorn will react to each of these signals differently and that’s how you can control the process.

resources

comments powered by Disqus