Background Jobs#
Fork#
The simplest and the most obvious way to run the background process is to fork
the current process to split it to two processes. There are two ways to do it:
fork { block }
- when the block is given, it will be executed in a subprocess
fork
- without the block, fork
behaves the same as fork()
function from the Unix standard library: it returns PID of the process in the parent, and nil
in the child.
Notice that child process inherits all I/O from the parent, so stdout
, stdin
and all the open file descriptors.
pid = fork
if pid # pid is not nil, we are in the parent process
puts "Parent process, child pid is #{pid}"
puts `ps -ef | grep #{pid}`
sleep 5
puts "Parent process finishes"
else # pid is nil, we are in the child process
puts "Child process, pid is nil"
sleep 2
puts "Child process finishes"
end
The example above launches fork
to split into parent and child process. The parent process shows the child PID and waits for 5 seconds, while the child sleeps for only two seconds. This is to ensure that the child finishes before parent, otherwise the child process may became a zombie
.
The other way to use fork
is to put the child code into a block:
pid = fork do
puts "Child process, pid is nil"
sleep 2
puts "Child process finished"
end
puts "Parent process, child pid is #{pid}"
sleep 5
puts "Parent process finished"
Wait for the Background Process to Finish#
When running the backgroup process, parent process should not finish before ensure, that all the childs are not running. There is a method for that: Process.wait
. You can give the child pid as an argument - in this case it will wait for this specific process to finish. Without any arguments, it waits for any child process to finish (so if you run more than one child, you should control all of them).
pid1 = fork do
sleep 10
puts "Child 1 process finished"
end
pid2 = fork do
sleep 5
puts "Child 2 process finished"
end
puts "Parent process, childen pids are #{pid1}, #{pid2}"
Process.wait pid1
Process.wait pid2
puts "Parent process finished"
Prevent the Zombie Apocalypse#
When you do not want to wait for you child process to finish, you should detach
the children process from the parent. Then, you can clearly exit your parent, without the danger of zombie. In the example below, parent process quits and leaves the child running:
pid = fork do
10.times do
puts `date`
sleep 1
end
puts "Child process finished"
end
puts "Parent process, child pid is #{pid}"
Process.detach pid
puts "Parent process finished"
When the Child Becames a Daemon#
If you just detach the child process from the parent, it will work in a background. But it will not beacame a daemon
- when you close your terminal, the process may receive SIGHUP
signal, which by default causes the process to exit. The other problem is stdin
, stdout
and stderr
- they are attached to the terminal, so when you quit your shell they are lost, and the background process dies.
So to create a true daemon
, you must reopen the standard I/O and redirect it to somewhere else (a file or /dev/null
) and catch the SIGHUP
signal.
pid = fork do
$stdin.reopen "/dev/null" # ignore stdin
$stdout.reopen "/tmp/test", "a" # stdout to a file
$stderr.reopen "/tmp/test", "a" # stderr to a file
trap(:HUP) do # ignore SIGHUP
puts "SIGHUP received, ignoring"
end
60.times do
puts `date`
sleep 1
end
puts "Daemon process ended"
end
puts "Parent process, daemon pid is #{pid}"
Process.detach pid
puts "Parent process ended"
In the example above we for and detach the child process, as it was in the previous scripts. Then, in the child code, we reopen (redirect) all the standard I/O to file or the the /dev/null
. After this, we build a trap
for SIGHUP
- trap(:HUP) { block }
runs the block every time the process receives SIGHUP
.
Of course you can catch not only the SIGHUP
, see Signal.list
for the complete hash of signal name and the corresponding number. Notice that this numbers may vary in the different OS. And of course you cannot catch the SIGKILL
. There is a method Signal.kill(signal, pid)
to send the signal to the specified process.
Daemonize the Process with Daemons Gem#
For now we know how to run a daemon, and we can control it by sending some signals. But it would be nice, if we could have an interface to control the backgound job, like the standard start
/stop
/status
switches in Unix daemons. Well, There is a Gem for that. It is called daeamons
and provides the interface to control your background jobs:
require 'daemons'
Daemons.run_proc('lazyd') do # run daemon with name 'lazyd'
loop do
sleep(1) # do nothing, it is just an example
end
end
As you can see, all the server code is a block passed to Daemons.run_proc
method. You can assign the process name as the argument. Now, just run the script with start
, stop
or status
command line arguments:
% ruby daemon.rb start
% ruby daemon.rb status
lazyd: running [pid 50486]
% ps -ef|grep 50486
501 50486 1 0 8:54PM ?? 0:00.06 lazyd
501 50519 49230 0 8:54PM ttys003 0:00.00 grep 50486
% ruby daemon.rb stop
lazyd: trying to stop process with pid 50486...
lazyd: process with pid 50486 successfully stopped.