What causes this deadlock in my Ruby `trap` block? - ruby

I'm reading through Jesse Storimer's excellent book, Working with Unix Processes. In a section on trapping signals from child processes which have exited, he gives a code sample.
I have modified that code slightly (see below) to get a bit more visibility into what's happening:
That the parent resumes its own execution between signals (which I can see with its puts),
That the wait executes for multiple children in one trap statement (sometimes I get "Received a CHLD signal" once followed by multiple "child pid exited").
Expected Output
Normally the output from the code below looks something like:
parent is working hard
Received a CHLD signal
child pid 73408 exited
parent is working hard
parent is working hard
parent is working hard
Received a CHLD signal
child pid 73410 exited
child pid 73409 exited
All children exited - parent exiting too.
The occasional error
But once in a while I get an error like this:
trapping_signals.rb:17:in `write': deadlock; recursive locking (ThreadError)
from trapping_signals.rb:17:in `puts'
from trapping_signals.rb:17:in `puts'
from trapping_signals.rb:17:in `block in <main>'
from trapping_signals.rb:17:in `call'
from trapping_signals.rb:17:in `write'
from trapping_signals.rb:17:in `puts'
from trapping_signals.rb:17:in `puts'
from trapping_signals.rb:17:in `block in <main>'
from trapping_signals.rb:40:in `call'
from trapping_signals.rb:40:in `sleep'
from trapping_signals.rb:40:in `block in <main>'
from trapping_signals.rb:38:in `loop'
from trapping_signals.rb:38:in `<main>
Can anyone explain to me what's going wrong here?
The code
child_processes = 3
dead_processes = 0
# We fork 3 child processes.
child_processes.times do
fork do
# Each sleeps between 0 and 5 seconds
sleep rand(5)
end
end
# Our parent process will be busy doing some work.
# But still wants to know when one of its children exits.
# By trapping the :CHLD signal our process will be notified by the kernel
# when one of its children exits.
trap(:CHLD) do
puts "Received a CHLD signal"
# Since Process.wait queues up any data that it has for us we can ask for it
# here, since we know that one of our child processes has exited.
# We loop over a non-blocking Process.wait to ensure that any dead child
# processes are accounted for.
# Here we wait without blocking.
while pid = Process.wait(-1, Process::WNOHANG)
puts "child pid #{pid} exited"
dead_processes += 1
# We exit ourselves once all the child processes are accounted for.
if dead_processes == child_processes
puts "All children exited - parent exiting too."
exit
end
end
end
# Work it.
loop do
puts "parent is working hard"
sleep 1
end

I looked through Ruby sources to see where that particular error is raised, and it's only ever raised when the current thread tries to acquire a lock, but that same lock is already taken by the current thread. This implies that locking is not re-entrant:
m = Mutex.new
m.lock
m.lock #=> same error as yours
Now at least we know what happens, but not yet why and where. The error message indicates that it happens during the call to puts. When it gets called, it finally ends up in io_binwrite. stdout is not synchronized, but it is buffered, so this if condition is fulfilled on the first call, and a buffer plus a write lock for that buffer will be set up. The write lock is important to guarantee the atomicity of writes to stdout, it shouldn't happen that two threads simultaneously writing to stdout mix up each other's output. To demonstrate what I mean:
t1 = Thread.new { 100.times { print "aaaaa" } }
t2 = Thread.new { 100.times { print "bbbbb" } }
t1.join
t2.join
Although both threads take turns in writing to stdout, it will never happen that a single write is broken up - you will always have the full 5 a's or b's in sequence. That's what the write lock is there for.
Now what goes wrong in your case is a race condition on that write lock. The parent process loops and writes to stdout every second("parent is working hard"). But the same thread also eventually executes the trap block and tries again to write to stdout("Received a CHLD signal"). You can verify that it's really the same thread by adding #{Thread.current} in your puts statements. If those two events happen closely enough, then you will have the same situation as in the first example: the same thread trying to obtain the same lock twice, and this ultimately triggers the error.

Related

Is it possible to have multiple traps for a signal

I'm trying to understand Ruby's traps for standard signals.
In specific, I'm trying to have multiple signal handlers ("traps") for the same signal. It seems impossible. Here's a super simplified code to demonstrate the problem:
file traps.rb:
should_stop = false
Signal.trap 'INT' do
# won't be executed :(
puts 'int --> A'
should_stop = true
end
Signal.trap 'INT' do
# will be executed
puts 'int --> B'
should_stop = true
end
times = 0
until should_stop
puts 'waiting to stop'
sleep 1
times += 1
break if times >= 5
end
puts 'done'
Run the code:
ruby traps.rb
Output without pressing CTRL+C:
waiting to stop
waiting to stop
waiting to stop
waiting to stop
waiting to stop
done
Output with pressing CTRL+C after 2 seconds:
waiting to stop
waiting to stop
^Cint --> B
done
It seems that only the last signal trap to be declared is the one which would be executed.
Is this behavior by design?
If not, how can we have multiple handlers executed to the same signal?
The main reason for asking this is that third party libraries might add their trap to a signal.
If we have two different third party libraries that add their trap to the same signal, only one of them would actually be executed. That's where the fun begins :(
It seems that only the last signal trap to be declared is the one which would be executed.
Is this behavior by design?
It is not very explicit in the documentation of Signal::trap, but it is by design:
The command or block specifies code to be run when the signal is raised.
Note the use of the singular, and the absence of any mention of something like "The command or block is added to the list of trap handlers to be run when the signal is raised."
It becomes clearer if you look at the POSIX trap shell builtin after which Signal::trap is modeled:
The action of trap shall override a previous action (either default action or one explicitly set).
The POSIX sigaction function which is the C equivalent to trap says more or less the same thing. Note, however, that the sigaction function also gives a way of retrieving the function pointer to the old action, so theoretically, you could set the action to a function pointer to a new action which uses the function pointer to the old action to call the old action as part of itself, thus in some way chaining the actions.
Note, however, that this would require the old and new action cooperate in some way. Also note that this mode of operation is not modeled by POSIX trap.
If not, how can we have multiple handlers executed to the same signal?
From the documentation:
trap returns the previous handler for the given signal.
So, Signal::trap implements the behavior from sigaction that gives you access to the "old" handler. You could save that old handler somewhere and chain the calls by explicitly calling it from your new handler.
Like with sigaction, this requires some form of cooperation between the handlers.
Combining the answer from Jörg W Mittag and the documentation, here's a simplified solution:
# file traps.rb
should_stop = false
Signal.trap('INT') do
puts 'int --> A'
should_stop = true
end
$prev_trap = Signal.trap('INT') do
puts 'int --> B'
should_stop = true
$prev_trap&.call
end
times = 0
until should_stop
puts 'waiting to stop'
sleep 1
times += 1
break if times >= 5
end
puts 'done'
After running ruby traps.rb, and pressing CTRL+C after 3 seconds, the output looks like this:
waiting to stop
waiting to stop
waiting to stop
^Cint --> B
int --> A
done

How to stop a process from within the tests, when testing a never-ending process?

I am developing a long-running program in Ruby. I am writing some integration tests for this. These tests need to kill or stop the program after starting it; otherwise the tests hang.
For example, with a file bin/runner
#!/usr/bin/env ruby
while true do
puts "Hello World"
sleep 10
end
The (integration) test would be:
class RunReflectorTest < TestCase
test "it prints a welcome message over and over" do
out, err = capture_subprocess_io do
system "bin/runner"
end
assert_empty err
assert_includes out, "Hello World"
end
end
Only, obviously, this will not work; the test starts and never stops, because the system call never ends.
How should I tackle this? Is the problem in system itself, and would Kernel#spawn provide a solution? If so, how? Somehow the following keeps the out empty:
class RunReflectorTest < TestCase
test "it prints a welcome message over and over" do
out, err = capture_subprocess_io do
pid = spawn "bin/runner"
sleep 2
Process.kill pid
end
assert_empty err
assert_includes out, "Hello World"
end
end
. This direction also seems like it will cause a lot of timing-issues (and slow tests). Ideally, a reader would follow the stream of STDOUT and let the test pass as soon as the string is encountered and then immediately kill the subprocess. I cannot find how to do this with Process.
Test Behavior, Not Language Features
First, what you're doing is a TDD anti-pattern. Tests should focus on behaviors of methods or objects, not on language features like loops. If you must test a loop, construct a test that checks for a useful behavior like "entering an invalid response results in a re-prompt." There's almost no utility in checking that a loop loops forever.
However, you might decide to test a long-running process by checking to see:
If it's still running after t time.
If it's performed at least i iterations.
If a loop exits properly given certain input or upon reaching a boundary condition.
Use Timeouts or Signals to End Testing
Second, if you decide to do it anyway, you can just escape the block with Timeout::timeout. For example:
require 'timeout'
# Terminates block
Timeout::timeout(3) { `sleep 300` }
This is quick and easy. However, note that using timeout doesn't actually signal the process. If you run this a few times, you'll notice that sleep is still running multiple times as a system process.
It's better is to signal the process when you want to exit with Process::kill, ensuring that you clean up after yourself. For example:
pid = spawn 'sleep 300'
Process::kill 'TERM', pid
sleep 3
Process::wait pid
Aside from resource issues, this is a better approach when you're spawning something stateful and don't want to pollute the independence of your tests. You should almost always kill long-running (or infinite) processes in your test teardown whenever you can.
Ideally, a reader would follow the stream of STDOUT and let the test pass as soon as the string is encountered and then immediately kill the subprocess. I cannot find how to do this with Process.
You can redirect stdout of spawned process to any file descriptor by specifying out option
pid = spawn(command, :out=>"/dev/null") # write mode
Documentation
Example of redirection
With the answer from CodeGnome on how to use Timeout::timeout and the answer from andyconhin on how to redirect Process::spawn IO, I came up with two Minitest helpers that can be used as follows:
it "runs a deamon" do
wait_for(timeout: 2) do
wait_for_spawned_io(regexp: /Hello World/, command: ["bin/runner"])
end
end
The helpers are:
def wait_for(timeout: 1, &block)
Timeout::timeout(timeout) do
yield block
end
rescue Timeout::Error
flunk "Test did not pass within #{timeout} seconds"
end
def wait_for_spawned_io(regexp: //, command: [])
buffer = ""
begin
read_pipe, write_pipe = IO.pipe
pid = Process.spawn(command.shelljoin, out: write_pipe, err: write_pipe)
loop do
buffer << read_pipe.readpartial(1000)
break if regexp =~ buffer
end
ensure
read_pipe.close
write_pipe.close
Process.kill("INT", pid)
end
buffer
end
These can be used in a test which allows me to start a subprocess, capture the STDOUT and as soon as it matches the test Regular Expression, it passes, else it will wait 'till timeout and flunk (fail the test).
The loop will capture output and pass the test once it sees matching output. It uses a IO.pipe because that is most transparant for subprocesses (and their children) to write to.
I doubt this will work on Windows. And it needs some cleaning up of the wait_for_spawned_io which is doing slightly too much IMO. Antoher problem is that the Process.kill('INT') might not reach the children which are orphaned but still running after this test has ran. I need to find a way to ensure the entire subtree of processes is killed.

How to forward terminating signals to Open4 child process

I am using the open4 gem to wrap system calls to a potentially long-running third-party command line tool. The tool may sometimes fail, keeping two processes busy, and partially blocking a pipeline, as the parent process is part of a pool of worker scripts (serving a Beanstalk queue). From outside of the system, I can identify a stuck worker script and its process id programatically, based on the data model of what is being processed. Inside the Open4.open4 block, I can identify the child process id.
I'd like to set up the Open4 block so that when I send a SIGTERM to the parent worker process, it forwards on the SIGTERM to the child. In addition, if the child process has still failed to exit after a short wait, I want to send a SIGKILL to the child process. In both cases, I'd then like the parent process to respond as normal to the SIGTERM it was sent.
This is all being done so I can expose a "stop" button in a customer services app, so non-technical team members have a tool to manage their way out of a situation with a blocked queue.
I have found some related questions in SO - e.g. How to make child process die after parent exits? - but the answers are not really usable for me from Ruby application code.
Here is a current implementation in Ruby that I have tested on my Mac:
Test stand-in for "bad" process that won't always respond to SIGTERM:
# Writing to a log file shows whether or not a detached process continues
# once the parent has closed IO to it.
$f = open( 'log.txt', 'w' );
def say m
begin
$f.puts m
$f.flush
$stderr.puts m
rescue Exception => e
# When the parent process closes, we get
# #<Errno::EPIPE: Broken pipe - <STDERR>> in this
# test, but with a stuck child process, this is not
# guaranteed to happen or cause the child to exit.
$f.puts e.inspect
$f.flush
end
end
Signal.trap( "TERM" ) { say "Received and ignored TERM" }
# Messages get logged, and sleep allows test of manual interrupts
say "Hello"
sleep 3
say "Foo Bar Baz"
sleep 3
say "Doo Be Doo"
sleep 3
say "Goodbye"
$f.close
Test Open4 block (part of a "worker" test script):
Open4.open4(#command) do | pid, stdin, stdout, stderr |
begin
stderr.each { |l|
puts "[#{pid}] STDERR: #{l}" if l
}
rescue SignalException => e
puts "[#{$$}] Received signal (#{e.signo} #{e.signm}) in Open4 block"
# Forward a SIGTERM to child, upgrade to SIGKILL if it doesn't work
if e.signo == 15
begin
puts "[#{$$}] Sending TERM to child process"
Process.kill( 'TERM', pid )
timeout(3.0) { Process.waitpid( pid ) }
rescue Timeout::Error
puts "[#{$$}] Sending KILL to child process"
Process.kill( 'KILL', pid )
end
end
raise e
end
end
Typical output if I start this up, and run e.g. kill -15 16854:
[16855] STDERR: Hello
[16854] Received signal (15 SIGTERM) in Open4 block
[16854] Sending TERM to child process
[16854] Sending KILL to child process
Contents of log file for same test:
Hello
Received and ignored TERM
Foo Bar Baz
The code is IMO a bit unwieldy, although it appears to work as I want. My questions:
Is the above attempt ok, or fatally flawed in the use case I need it for?
Have I missed a cleaner way of doing the same thing using existing Open4 and core Ruby methods?

Fork callback in ruby using trap

I'm looking for a reliable way of implementing a callback on a forked process, once it has finished.
I tried using trap (see the code below), but it appears to fail from time to time.
trap("CLD") {
pid = Process.wait
# do stuff
}
pid = fork {
# do stuff
}
While I did found (via google) possible explanations why this may be happening, I'm having a hard time figuring out a possible solution.
The only solution I see so far is to open a pipe between processes (parent - read end, child - write end). Then put the parent processes thread on blocking read and trap "broken pipe" or "pipe closed" exceptions.
Any of these exceptions will obviously mean that child process is dead.
UPDATE: If I'm not wrong normally closed pipe will result into EOF result of blocking read, and broken pipe (if child process crashed) will result into Errno::EPIPE exception.
#Openning a pipe
p_read, p_write = IO.pipe
pid = fork {
#We are only "using" write end here, thus closing read end in child process
#and let the write end hang open in the process
p_read.close
}
#We are only reading in parent, thus closing write end here
p_write.close
Thread.new {
begin
p_write.read
#Should never get here
rescue EOFError
#Child normally closed its write end
#do stuff
rescue Errno::EPIPE
#Chiled didn't normally close its write end
#do stuff (maybe the same stuff as in EOFError handling)
end
#Should never get here
}
#Do stuff in parents main thread

How do you spawn a child process in Ruby?

I want to offload a block of code in my main process to child process to make it run concurrently. I also want to have the PID of the spawned child process so I can monitor and kill it if necessary.
In addition to Chris' great answer, remember to call Process.wait from your master in order to reap your child process, else you'll leave zombies behind.
Example as requested in comments:
pid = Process.fork do
puts "child, pid #{Process.pid} sleeping..."
sleep 5
puts "child exiting"
end
puts "parent, pid #{Process.pid}, waiting on child pid #{pid}"
Process.wait
puts "parent exiting"
You can use the fork kernel method. Here is an example:
#!/usr/bin/env ruby
puts "This is the master process."
child_pid = fork do
puts "This is the child process"
exit
end
puts "The PID of the child process is #{child_pid}"
The fork method returns the PID of the process it forks and executes any code in the block passed. Like regular Ruby blocks it keeps the bindings of the parent process.
It is a good idea to make your forked process exit.
In 1.9 you can use Process.spawn command.
See also http://en.wikibooks.org/wiki/Ruby_Programming/Running_Multiple_Processes
If you are happy to use Threads, rather than Processes, then something like this may be a bit more scaleable to more-than-one fork:
def doit(x)
sleep(rand(10))
puts "Done... #{x}"
end
thingstodo = ["a","b","c","d","e","f","g"]
tasklist = []
# Set the threads going
thingstodo.each { |thing|
task = Thread.new(thing) { |this| doit(this) }
tasklist << task
}
# Wait for the threads to finish
tasklist.each { |task|
task.join
}
Please see John Topley's excellent comments and reference, below, regarding the Ruby execution model and its restrictions.
Just edited to correct a glaring error (no assignment to task), and to follow #(Jason King)'s advice.

Resources