Ruby: how to handler thread exception? - ruby

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

Related

How exactly exception unlocks mutex?

This simple test mostly results as
(1) rescue break, m.locked?: false
but sometimes I can see
(1) rescue break, m.locked?: true
m = Mutex.new
6.times do
Thread.new do
begin
m.synchronize do
puts 't1 action'
3.times do
puts '.'
sleep 0.5
end
raise 'Break'
end
rescue
puts "(1) rescue break, m.locked?: #{m.locked?}"
m.synchronize do
sleep 0.1
end
puts '(2) after m {sleep}'
sleep 0.1
puts 'rescue break 2'
end
end
sleep 0.1
t2 = Thread.new do
puts 't2 waiting for mutex'
m.synchronize do
puts '(3) t2 action'
end
end
t2.join
sleep 0.2
puts;puts;
end
I expected that inside the rescue block mutex will be always unlocked.
Environment:
Ruby v2.6.3.62 (2019-04-16) [x64-mingw32]
Nobody promised that the processor would stop the world, waiting for your action :) That said, between
raise 'Break'
and
puts "(1) rescue break, m.locked?: #{m.locked?}"
there is another thread that might get the execution time and in turn lock the mutex.
Please also note, that
raise 'Break'
end
rescue
puts "(1) rescue break, m.locked?: #{m.locked?}"
is effectively the same as
end
puts "(1) rescue break, m.locked?: #{m.locked?}"
In the latter snippet, it should be clear that m might be either locked by another thread, or not; we have it just released, so no promise.

How to protect some code from SIG terminating with threads/mutex?

I make some important calculations in endless loop and don't want this calculation interrupts with SIGINT signal (e.g. ctrl-c). So I place loop in thread with protecting important calculation with mutex:
mutex = Mutex.new
trap('INT') do
Thread.new do
puts 'Terminating..'
exit(0)
end.join
end
Thread.new do
loop do
mutex.synchronize do
puts 'Some important computation is started.'
sleep(5)
puts 'Some important computation is done.'
end
sleep(30)
end
end.join
I add another thread inside trap block, so I expect this thread will be executed only when mutex will be unlocked.
But in fact, this second thread starts immediately after receiving SIGINT signal:
Some important computation is started.
^CTerminating..
What am I missed/doing wrong?
You must synchronize the trap thread with the computation:
trap('INT') do
Thread.new do
mutex.synchronize do
puts 'Terminating..'
exit(0)
end
end.join
end
But perhaps it is easier if you set a boolean var in your trap function, and you use it to break the loop.
mustexit = false
trap('INT') do
mustexit= true
end
Thread.new do
loop do
puts 'Some important computation is started.'
sleep(5)
puts 'Some important computation is done.'
if mustexit then
break
end
end
sleep(30)
end.join

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

Handling exceptions raised in a Ruby thread

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

Read from stdin or file in single stagment in ruby

I have this code
if filename
begin
if filename == '-'
ARGF.each{|url|
begin
check(url)
rescue Timeout::Error, Errno::ETIMEDOUT
puts "Timeout Error, try again"
redo
end
}
else
File.open(filename) {|file|
file.each{|url|
begin
check(url)
rescue Timeout::Error, Errno::ETIMEDOUT
puts "Timeout Error, try again"
redo
end
}
}
end
rescue Interrupt, Errno::EINTR
exit(1)
end
end
But I don't want repeated code for stdin and file, how can I rewrite it?
You can pull out your repeated code into a method and call that on ARGF or file as they respond to the same methods.
def do_check(to_check)
to_check.each do |url|
begin
check(url)
rescue Timeout::Error, Errno::ETIMEDOUT
puts "Timeout Error, try again"
redo
end
end
end
Then your example becomes:
if filename
begin
if filename == '-'
do_check(ARGF)
else
File.open(filename) do |file|
do_check(file)
end
end
rescue Interrupt, Errno::EINTR
exit(1)
end
end
I've used do ... end rather than {} simply because I find it easier to read.
Try using $< special variable: http://ruby.wikia.com/wiki/Special_variable

Resources