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.
Related
I have some code that passes tests in rspec. It appears I don't need end with a double rescue in Ruby, or even begin for that matter. Essentially, I return 5 when a Geocoder error is encountered and 6 if a CustomError is encounted and 7 otherwise.
def get_stuff
puts "some code work"
rescue ::Geocoder::Error => e
puts "hello"
return 5
rescue CustomError => e
puts "world"
return 6
7
end
Indentation doesn't matter in Ruby. Technically the 7 will never be returned because you have it inside the rescue block after the return 6 line.
If you want to return 7 when no error is raised, place it on the last line before the rescue blocks:
def get_stuff
puts "some code work"
7 #success!
rescue ::Geocoder::Error => e
puts "hello"
return 5
rescue CustomError => e
puts "world"
return 6
end
A method is its own begin/end block, so you can just do rescue when you need it, and also ensure when you need it.
As you've done, you do need to do explicit return statements if the rescue or ensure block should return a value.
... although I'm surprised that you're getting 7 returned... that's part of your second rescue block and shouldn't return.
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
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
Is it possible, in Ruby, to raise an Exception that will also automatically abort the program, ignoring any enclosing begin/rescue blocks?
Unfortunately, none of these exit answers will work. exit raises SystemExit which can be caught. Observe:
begin
exit
rescue SystemExit
end
puts "Still here!"
As #dominikh says, you need to use exit! instead:
begin
exit!
rescue SystemExit
end
puts "Didn't make it here :("
Edu already asked: If you want to abort the program, why not going straight for it and use 'exit'
One Possibility:
You may define your own Exception and when the exception is called, the exception stopps the programm with exit:
class MyException < StandardError
#If this Exception is created, leave programm.
def initialize
exit 99
end
end
begin
raise MyException
rescue MyException
puts "You will never see meeeeeee!"
end
puts "I will never get called neither :("
Would this do what you want?
begin
puts Idontexist
rescue StandardError
exit
puts "You will never see meeeeeee!"
end
puts "I will never get called neither :("
My answer is similar to Maran's one, but slightly different:
begin
puts 'Hello'
# here, instead of raising an Exception, just exit.
exit
puts "You will never see meeeeeee!"
rescue # whatever Exception
# ...
end
puts "I will never get called neither :("
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