By Andreas Fast

This post is inspired on a talk about Ruby & Linux at the RubyConf Uruguay 2014.

The Linux Process in Ruby

Creating a new process

In linux we use fork to create a new child process. In Ruby we have access to this by using  Process::fork

It accepts an optional block. We can use either Process.fork or simply fork. When a block is given the child process runs the block and terminates with a status of zero. When no block is given the fork call returns twice, once in the parent process returning the child pid and once in the child process returning nil. When using fork only the thread calling fork will be running in the child process, no other thread will be copied. Since ruby 2 there’s been an improvement to copy on write with relation to the GC. Instead of duplicating all the data it just copies the data when the shared memory is modified by one of the processes.

Files & Network Connections

When using a fork the open files and network connections are shared between processes. This can be a good or a bad thing depending on what you are doing. Sometimes you are interested in writing to the same file from both processes at the same time. But in case of a database connection you could run into weird scenarios. So remember to re-connect and close/open files if you are not interested in using the same connection as the father process did.

Fork Example

Example run

Process ID (PID)

To get a process’ PID we can use Process::pid and to get the parent’s pid we use Process::ppid

Reaping a child’s status

When using fork in linux the child’s exit status needs to be collected, otherwise the operating system will accumulate zombies. There are a number of ways to do this in ruby, here’s a list:

  • Process.wait(pid=-1, flags=0)
    • pid > 0  Waits for the child whose process ID equals pid.
    • 0  Waits for any child whose process group ID equals that of the calling process.
    • -1  Waits for any child process (the default if no pid is given).
    • < -1  Waits for any child whose process group ID equals the absolute value of pid.
  • Process.waitall
    • Waits for all children, returning an array of pid/status pairs (where status is a Process::Status object).
  • Process.detach
    • Sets up a separate Ruby thread whose sole job is to reap the status of the process pid when it terminates.
    • Use detach only when you do not intent to explicitly wait for the child to terminate.

Spawn

Spawn executes the specified command and return its pid. It does not wait for the command to finish and the parent process should wait for it to finish or use detach if they don’t care about the return status.

Daemonize

Process::daemon  allows a ruby process to detach from the controlling terminal and run as a system daemon.

Signals

Trapping Signals

Signals play a big part in IPC. To handle a signal in ruby you can use

It receives the signal and a command or block. There are special commands that you can look up in the Signal::trap documentation. The more interesting scenario here is to pass a block to the trap and execute whatever it is we want to do when receiving the signal. Note that signal handlers need to be reentrant and that signals are deferred. This means that your process might receive the signal only once if it was triggered many times in quick succession. There’s no way to tell beforehand.

Sending Signals

To send a signal in ruby use

With kill you indicate the signal and the pid. This will send the signal to the process indicated by pid.

Example

Execution

Reentrant

When trapping signals you need to make sure your code is reentrant. This means that it has to be able to be executed many times and even if it’s being executed, because basically it may be interrupting the handling of an interruption. A good practice here is to store the signal in a queue and work through the queue in another thread or in the main process.

Deferred

Not all signals will make it to the process, because signals are not queued, they are pending. So if you send a signal and the same signal is still pending it won’t make it to the process.

Pipes

In Linux on the command line we like to combine commands using the pipe operator “|”. This is very useful to connect one process with the other and have small programs to specific stuff and use pipes to combine and get more complex stuff done. In ruby we can access the pipe functionality using IO::pipe

Pipes are write atomic up until 512 bytes, but there are limits to how much data a pipe can buffer. The size is provided by the Kernel and is 64 kb, after that the pipe start losing data.

IPC using pipes

Now the interesting part is how to communicate processes using pipes. After a fork the pipes remain open and are shared. So it’s only a matter of closing the writer on the child’s end and the reader on the parent’s end and we have a communication channel from the parent to the child.

We can start opening more processes and keep communication back and forth through pipes. One per direction per process.

Conclusion

Linux has small but very powerful Inter Process Communication tools and they are usable through the ruby API. It’s up to us finding the right tool for the right job and leverage proven tools that work fast and have been around for years. They’re going nowhere so we can definitely rely on them.