Peter Peter - 1 year ago 45
Ruby Question

Running and terminating an external process from Ruby

I want to run an external process under control of a Ruby (MRI) process.

Is there a way to start a long/infinite running job under Ruby, and then programmatically control its termination? I thought this would be relatively easy, but seem to be really struggling to figure out a way to do this.

Some example code, which does not work:

pid = fork { system "yes > /dev/null" }
sleep 1 # yes is happily running
Process.kill "TERM", pid

If I run this as a Ruby file, it runs happily. However, if I then
pgrep yes
I see that
is still running. If I
pkill yes
it disappears successfully. The
signal is no more successful than the

How would I alter this code to run
in the background but then allow its termination?

(In case it matters, which I doubt, this is with MRI Ruby 2.3 on macOS (haha) 10.11.)

Answer Source

You've got the right idea here but you're missing out on a subtle thing. Calling fork creates a child process, but system also does this as it's implemented as a fork/exec pair, so in effect you're killing the wrong process.

You can tell if you puts pid in there to see what process you're trying to kill. The one you're trying to kill will typically be the PID of the yes process that's actually running minus one.

What you probably want:

pid = fork do
  exec "/usr/bin/yes", out: File::NULL

sleep 1

Process.kill 'TERM', pid

You'll have to be very careful when using exec because at the slightest hint of something irregular then Ruby will create a shell process to execute your command which introduces yet another child process. Killing that won't actually kill the yes command. Here I've used the out: option to suppress STDOUT rather than leaning on the > /dev/null trick which automatically engages the shell wrapper.

In some quick testing I found that calling exec 'yes' caused an intermediate shell to be launched but specifying the full path avoided this.