Exceptions occassionally lost by child threads

S

Sonny Chee

Hey Guys,

As I understand it, the correct way to pass exceptions between threads
is to explicitly raise them in the target thread... so something like
this works:

a = Thread.new(Thread.current) { |parent|
....
parent.raise err

}

What I'm curious about is that the following code also seems to work:

a = Thread.new {
begin
puts 'blue'
sleep 2;
raise ArgumentError.new('green')
rescue Exception => err
puts "Caught: #{err.message}"
raise err
end
}

begin
while a
sleep 1
a = a.join(1)
end
puts 'yellow'
rescue Exception => err
puts "Caught it again: #{err.message}"
end

#blue
#Caught: green
#Caught it again: green

However, if i move the sleep 1 statement to after a = a.join(1), the
exception seems to get lost and the output is:

#blue
#Caught: green
#yellow


Should exceptions automatically propagate from children up to the
parent? And if so, why should the positioning of something as innocuous
as sleep 1 matter?

Sonny.
 
S

Sylvain Joyeux

Should exceptions automatically propagate from children up to the
parent? And if so, why should the positioning of something as innocuous
as sleep 1 matter?
Because you have a timeout on the join (a.join(1))

In the first case it sleeps one second, and waits for 'a' to finish. It
works if 'a' raises within two seconds. Note that it could also fail
since 'sleep' is not perfect and it is possible for the main thread to
reach the timeout on 'join' before 'a' raises.

In the second case, the main thread only waits 'a' for one second. It is
useless because of the sleep(2) in 'a'.

It should work as expected if you replace a.join(1) by a.join
 
R

Robert Klemme

Because you have a timeout on the join (a.join(1))

In the first case it sleeps one second, and waits for 'a' to finish. It
works if 'a' raises within two seconds. Note that it could also fail
since 'sleep' is not perfect and it is possible for the main thread to
reach the timeout on 'join' before 'a' raises.

In the second case, the main thread only waits 'a' for one second. It is
useless because of the sleep(2) in 'a'.

It should work as expected if you replace a.join(1) by a.join

It seems to be a property of Thread#join and #value that they will
automatically propagate any uncaught exceptions of the the joined thread:

irb(main):003:0> t=Thread.new { sleep 20; raise "Foo" }
=> #<Thread:0x7ef7ef6c sleep>
irb(main):004:0> begin; t.join; rescue Exception => e; p e; end
#<RuntimeError: Foo>
=> nil

I think this behavior is documented, although a bit cryptic IMHO:

"If thr had previously raised an exception and the abort_on_exception
and $DEBUG flags are not set (so the exception has not yet been
processed) it will be processed at this time."
http://www.ruby-doc.org/core/classes/Thread.html#M000474

Kind regards

robert
 
S

Sonny Chee

Thanks guys. My bad. For some reason I thought join(1) returned nil
when the thread completed. That should learn me to post messages when
I'm half asleep:"P

In the following snippet the parent always catches the exception
regardless of the position of the sleep 1 statment.

a = Thread.new {
begin
puts 'blue'
sleep 2;
raise ArgumentError.new('green')
rescue Exception => err
puts "Caught: #{err.message}"
raise err
end
}

begin
b = a
while a
sleep 1 # position 1
b = a.join(1)
a = b if !a
sleep 1 # position 2
end
puts 'yellow'
rescue Exception => err
puts "Caught it again: #{err.message}"
end
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,781
Messages
2,569,615
Members
45,294
Latest member
LandonPigo

Latest Threads

Top