'Can't send signals to process created by PTY.spawn() in Ruby

I'm running ruby in an Alpine docker container (it's a sidekiq worker, if that matters). At a certain point, my application receives instructions to shell out to a subcommand. I need to be able to stream STDOUT rather than have it buffer. This is why I'm using PTY instead of system() or another similar answer. I'm executing the following line of code:

stdout, stdin, pid = PTY.spawn(my_cmd)

When I connect to the docker container and run ps auxf, I see this:

root         7  0.0  0.4 187492 72668 ?        Sl   01:38   0:00 ruby_program
root     12378  0.0  0.0   1508   252 pts/4    Ss+  01:38   0:00  \_ sh -c my_cmd
root     12380  0.0  0.0  15936  6544 pts/4    S+   01:38   0:00      \_ my_cmd

Note how the child process of ruby is "sh -c my_cmd", which itself then has a child "my_cmd" process.

"my_cmd" can take a significant amount of time to run. It is designed so that sending a signal USR1 to the process causes it to save its state so it can be resumed later and abort cleanly.

The problem is that the pid returned from "PTY.spawn()" is the pid of the "sh -c my_cmd" process, not the "my_cmd" process. So when I execute:

Process.kill('USR1', pid)

it sends USR1 to sh, not to my_cmd, so it doesn't behave as it should.

Is there any way to get the pid that corresponds to the command I actually specified? I'm open to ideas outside the realm of PTY, but it needs to satisfy the following constraints:

1) I need to be able to stream output from both STDOUT and STDERR as they are written, without waiting for them to be flushed (since STDOUT and STDERR get mixed together into a single stream in PTY, I'm redirecting STDERR to a file and using inotify to get updates).

2) I need to be able to send USR1 to the process to be able to pause it.



Solution 1:[1]

I gave up on a clean solution. I finally just executed

pgrep -P #{pid}

to get the child pid, and then I could send USR1 to that process. Feels hacky, but when ruby gives you lemons...

Solution 2:[2]

You should send your arguments as arrays. So instead of

stdout, stdin, pid = PTY.spawn("my_cmd arg1 arg2")

You should use

stdout, stdin, pid = PTY.spawn("my_cmd", "arg1", "arg2")

etc.

Also see:

Process.spawn child does not receive TERM

https://zendesk.engineering/running-a-child-process-in-ruby-properly-febd0a2b6ec8

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 David Handy
Solution 2 Fatih