Handling exceptions raised in a Ruby thread - ruby

I am looking for a solution of classic problem of exception handling. Consider following piece of code:
def foo(n)
puts " for #{n}"
sleep n
raise "after #{n}"
end
begin
threads = []
[5, 15, 20, 3].each do |i|
threads << Thread.new do
foo(i)
end
end
threads.each(&:join)
rescue Exception => e
puts "EXCEPTION: #{e.inspect}"
puts "MESSAGE: #{e.message}"
end
This code catches the exception after 5 seconds.
But if I change the array as [15, 5, 20, 3], above code catch the exception after 15 seconds. In short, it always catch the exception raised in first thread.
Any idea, why so. Why doesn't it catch the exception after 3 seconds each time? How do I catch the first raised exception by any thread?

If you want any unhandled exception in any thread to cause the interpreter to exit, you need to set Thread::abort_on_exception= to true. Unhandled exception cause the thread to stop running. If you don't set this variable to true, exception will only be raised when you call Thread#join or Thread#value for the thread. If set to true it will be raised when it occurs and will propagate to the main thread.
Thread.abort_on_exception=true # add this
def foo(n)
puts " for #{n}"
sleep n
raise "after #{n}"
end
begin
threads = []
[15, 5, 20, 3].each do |i|
threads << Thread.new do
foo(i)
end
end
threads.each(&:join)
rescue Exception => e
puts "EXCEPTION: #{e.inspect}"
puts "MESSAGE: #{e.message}"
end
Output:
for 5
for 20
for 3
for 15
EXCEPTION: #<RuntimeError: after 3>
MESSAGE: after 3
Note: but if you want any particular thread instance to raise exception this way there are similar abort_on_exception= Thread instance method:
t = Thread.new {
# do something and raise exception
}
t.abort_on_exception = true

Thread.class_eval do
alias_method :initialize_without_exception_bubbling, :initialize
def initialize(*args, &block)
initialize_without_exception_bubbling(*args) {
begin
block.call
rescue Exception => e
Thread.main.raise e
end
}
end
end

Postponed exceptions processing (Inspired by #Jason Ling)
class SafeThread < Thread
def initialize(*args, &block)
super(*args) do
begin
block.call
rescue Exception => e
#exception = e
end
end
end
def join
raise_postponed_exception
super
raise_postponed_exception
end
def raise_postponed_exception
Thread.current.raise #exception if #exception
end
end
puts :start
begin
thread = SafeThread.new do
raise 'error from sub-thread'
end
puts 'do something heavy before joining other thread'
sleep 1
thread.join
rescue Exception => e
puts "Caught: #{e}"
end
puts 'proper end'

This will wait for the first thread to either raise or return (and re-raise):
require 'thwait'
def wait_for_first_block_to_complete(*blocks)
threads = blocks.map do |block|
Thread.new do
block.call
rescue StandardError
$!
end
end
waiter = ThreadsWait.new(*threads)
value = waiter.next_wait.value
threads.each(&:kill)
raise value if value.is_a?(StandardError)
value
end

Jason Ling's answer will miss out any arguments passed to Thread.new. This will break Puma and other gems. To avoid this problem, you can use:
Thread.class_eval do
alias_method :initialize_without_exception_bubbling, :initialize
def initialize(*args, &block)
initialize_without_exception_bubbling(*args) {
begin
block.call(*args)
rescue Exception => e
Thread.main.raise e
end
}
end
end

Related

Ruby: how to handler thread exception?

my code here...
require 'thread'
$temp = Thread.new do
loop do
puts 'loop me'
begin
puts "try thread"
raise Exception.new('QwQ') if rand > 0.5
puts "skip try"
rescue
puts "QwQ"
end
sleep(0.5)
end
puts '...WTF'
end
loop do
puts "runner #{Thread.list.length} #{$temp.status}"
sleep(2)
end
how to keep runner and loop thread running? and how to fix it like this code?
I tried like Thread.abort_on_exception , but it will kill the process...
Catch the exception inside the thread, and set the error in a variable accessible by the main thread (for testing you could use a global variable like so: $thread_error).
If the error-variable exists, then raise it from the main thread.
You could also use a queue to communicate between the threads, but then it wouldn't be able to utilize multiple threads.
require 'thread'
$temp = Thread.new do
begin
loop do
puts 'loop me'
begin
puts "try thread"
raise Exception.new('QwQ') if rand > 0.5
puts "skip try"
rescue
puts "QwQ"
end
sleep(0.5)
end
puts '...WTF'
rescue Exception => e
$thread_error = e
raise e
end
end
loop do
puts "runner #{Thread.list.length} #{$temp.status}"
raise $thread_error if $thread_error
sleep(2)
end

rescue that calls a method with a rescue

This only prints rescue 1, is there a way to print both rescue 1 and rescue 2?
def mimiti
raise 'hi there!'
rescue
puts 'rescue 1'
end
begin
mimiti
rescue
puts 'rescue 2'
end
Yes, you can re-raise an exception after catching and handling it:
def mimiti
raise 'hi there!'
rescue StandardError => e
puts 'rescue 1'
raise e
end

Ruby, catching library thread exceptions?

I'm using a gem that's throwing an exception in a background thread as below. I'd like to catch this exception but not sure about how to go about it. How would one go about handling exceptions in library threads?
#this class is in my code
class MQTT
def self.connect
#client = Client.connect(options)
end
ende
This class is in the library which is packaged as a gem, so I technically don't have access to it:
class Client
def self.connect(*args, &block)
client = Client.new(*args)
client.connect(&block)
return client
end
def connect(clientid=nil)
# Start packet reading thread
#read_thread = Thread.new(Thread.current) do |parent|
Thread.current[:parent] = parent
loop { receive_packet }
end
end
def receive_packet
begin
# Poll socket - is there data waiting?
result = IO.select([#socket], nil, nil, SELECT_TIMEOUT)
# Pass exceptions up to parent thread
rescue Exception => exp
unless #socket.nil?
#socket.close
#socket = nil
end
Thread.current[:parent].raise(exp)
end
end
end
I think you have 3 options.
You could return the exception to the calling thread:
def receive_packet
raise "Exception in #{Thread.current}"
rescue Exception => exp
return exp
end
t1 = Thread.new do
receive_packet
end
puts "t1: #{t1.value.inspect}"
You could catch the exception on joining the thread (note you could reraise here or use an ensure block to make sure your socket is closed):
def receive_packet
raise "Exception in #{Thread.current}"
rescue Exception => exp
# reraise the exception
raise exp
end
t = Thread.new do
receive_packet
end
begin
t.join
rescue => e
puts "Exception caught from joined thread #{e.message} "
end
or you set #abort_on_exception = true so that exceptions kill all threads:
Thread.abort_on_exception = true
begin
Thread.new do
receive_packet
end
sleep 1
rescue => e
puts "Exception raised immediately to main thread: #{e.message}"
end
Update Based on what you have above and your comment I guess you need to wait for the threads calling receive_packet to finish. So you would have to join them:
class Client
def self.connect(*args, &block)
client = Client.new(*args)
client.connect(&block)
return client
end
def initialize(args)
#count = 0
end
def connect(clientid=nil)
puts "Connecting. Thread.current is #{Thread.current}"
# Start packet reading thread
#read_thread = Thread.new(Thread.current) do |parent|
Thread.current[:parent] = parent
loop { receive_packet }
end
end
def receive_packet
begin
# Poll socket - is there data waiting?
# result = IO.select([#socket], nil, nil, SELECT_TIMEOUT)
sleep 0.1
#count += 1
puts "count is now #{#count}"
if #count == 3
raise "WOOT: #{#count}"
end
# Pass exceptions up to parent thread
rescue Exception => exp
unless #socket.nil?
#socket.close
#socket = nil
end
puts "Reraising error #{exp.inspect} from #{Thread.current} to #{Thread.current[:parent]}"
Thread.current[:parent].raise(exp)
end
end
end
class MQTT
def self.connect
#client = Client.connect(options = {})
end
end
begin
MQTT.connect
Thread.list.each do |t|
# Wait for the thread to finish if it isn't this thread (i.e. the main thread).
t.join if t != Thread.current
end
rescue => e
puts "Exception from child thread: #{e.inspect}"
end

How to resume from rescue clause in Ruby?

How can I write resuming into loops in Ruby? Here is a sample code.
#!/usr/bin/ruby
#
a = [1,2,3,4,5]
begin
a.each{|i|
puts i
if( i==4 ) then raise StandardError end # Dummy exception case
}
rescue =>e
# Do error handling here
next # Resume into the next item in 'begin' clause
end
However, when running, Ruby returns the error message
test1.rb:13: Invalid next
test1.rb: compile error (SyntaxError)
I'm using Ruby 1.9.3.
You should use retry instead of next; But this will cause infinite loop (retry restart from the beginning of the begin)
a = [1,2,3,4,5]
begin
a.each{|i|
puts i
if i == 4 then raise StandardError end
}
rescue =>e
retry # <----
end
If you want skip an item, and continue to next item, catch the exception inside the loop.
a = [1,2,3,4,5]
a.each{|i|
begin
puts i
if i == 4 then raise StandardError end
rescue => e
end
}
Move your exception catching into the each block e.g:
a = [1,2,3,4,5]
a.each do |i|
puts i
begin
# Dummy exception case
if( i==4 ) then raise StandardError end
rescue =>e
# Do error handling here
end
end

How does one use rescue in Ruby without the begin and end block

I know of the standard technique of having a begin rescue end
How does one just use the rescue block on its own.
How does it work and how does it know which code is being monitored?
A method "def" can serve as a "begin" statement:
def foo
...
rescue
...
end
You can also rescue inline:
1 + "str" rescue "EXCEPTION!"
will print out "EXCEPTION!" since 'String can't be coerced into Fixnum'
I'm using the def / rescue combination a lot with ActiveRecord validations:
def create
#person = Person.new(params[:person])
#person.save!
redirect_to #person
rescue ActiveRecord::RecordInvalid
render :action => :new
end
I think this is very lean code!
Example:
begin
# something which might raise an exception
rescue SomeExceptionClass => some_variable
# code that deals with some exception
ensure
# ensure that this code always runs
end
Here, def as a begin statement:
def
# something which might raise an exception
rescue SomeExceptionClass => some_variable
# code that deals with some exception
ensure
# ensure that this code always runs
end
Bonus! You can also do this with other sorts of blocks. E.g.:
[1, 2, 3].each do |i|
if i == 2
raise
else
puts i
end
rescue
puts 'got an exception'
end
Outputs this in irb:
1
got an exception
3
=> [1, 2, 3]

Resources