4

I need to immediately catch exceptions in threads and stop all threads, so I'm using abort_on_exception in my script. Unfortunately this means the exception is not raised to the parent thread - perhaps this is because the exception ends up happening in a global scope??

Anyways, here's an example showing the issue:

Thread.abort_on_exception = true

begin
  t = Thread.new {
    puts "Start thread"
    raise saveMe
    puts "Never here.."
  } 
  t.join
rescue => e
  puts "RESCUE: #{e}"
ensure
  puts "ENSURE"
end

How can I rescue that exception that's raised in the thread when using abort_on_exception?

Here's a new example that shows something even more boggling. The thread is able to kill execution inside the begin block, but it does it without raising any exceptions??

Thread.abort_on_exception = true
begin
  t = Thread.new { raise saveMe }                     
  sleep 1
  puts "This doesn't execute"
rescue => e 
  puts "This also doesn't execute"
ensure
  puts "But this does??"
end   
  • It is not due to being global in scope for the exceptions. You will need to The parent process will need to 'babysit' the child processes. Check the thread documentation for this. – vgoff Nov 02 '12 at 21:31
  • From the [Pickaxe](http://www.rubycentral.com/pickaxe/tut_threads.html) (May be somewhat out of date, but should lead you in the right direction.) – vgoff Nov 02 '12 at 21:38
  • vgoff: The Pickaxe manual didn't have any information except to say what abort_on_exception does, which I already knew. I don't know what you mean by 'babysit' - can you elaborate? I've read the thread docs, and have used threads quite a bit, but I don't understand how to raise the error to outside the thread in this case. – David Ljung Madison Stellar Nov 02 '12 at 22:56
  • From the link that I pasted, "In addition to join, there are a few other handy routines that are used to manipulate threads. First of all, the current thread is always accessible using Thread.current . You can obtain a list of all threads using Thread.list , which returns a list of all Thread objects that are runnable or stopped. To determine the status of a particular thread, you can use Thread#status and Thread#alive? ." But there is a little bit more to it than that. I will have to further research, I just read it this last week (Jesse Storimers book, I believe). – vgoff Nov 02 '12 at 23:04
  • 1
    Kernel#abort provides a generic way to exit a process unsuccessfully.will set the exit code to 1 for the current process. Kernel#abort. This is why I say you need to babysit the child processes. If they come back with status 1, something went wrong. Exceptions should be handled before this point to exit gracefully. Kernel#raise has a different behavior, but still you want to watch for child processes with a status of 1.. There is a global variable for "last exception" which holds the stack as well. – vgoff Nov 02 '12 at 23:32
  • This is not just the thread that's getting killed, the parent code (not in a thread) is being killed, but no exception is raised in the parent code. See the new example I just posted in the original question. – David Ljung Madison Stellar Nov 03 '12 at 00:15
  • I don't know why you're surprised that `ensure` always runs - that's what it's supposed to do. Or that you're surprised if you're going to raise exceptions in other threads that the parent process doesn't pick them up. Surprise seems totally inappropriate! – ian Nov 03 '12 at 00:18
  • That is because exceptions are not passed up. The parent needs to see the return of (1) from the child process. The global $! holds the latest error message (though I am not sure it will survive a child process, this definitely should be thread safe, right?). At that point, I might consider checking it, but also handling and raising an error with the report that the child process was abnormally terminated. Better yet, have the child process rescue and log, and then exit with error code 1 like it wanted to do. You will need to handle some of this, more than you would without threads. – vgoff Nov 03 '12 at 00:20
  • @Iain, I am surprised that the parent process doesn't pickup the exception in the rescue, as it *does* when you don't have abort_on_exception set. So the problem seems to be that the exception in the thread is causing the parent to abort, and on an abort the ensure is called, but the rescue is not. It's especially strange since the value of $! is a SystemExit, and we should be able to rescue that, but it doesn't. So, still surprised. Isn't there a way to catch the abort? I can't 'babysit' the threads because I can't join or check status since the main block is aborted. – David Ljung Madison Stellar Nov 03 '12 at 05:39

1 Answers1

5

Ah - I figured it out.

The abort_on_exception send, obviously, an abort. The thread is irrelevant, our rescue won't see a basic abort either:

begin
  abort
  puts "This doesn't execute"
rescue => e
  puts "This also doesn't execute"
ensure
  puts "But this does??  #{$!}"
end   

The solution is to use a 'rescue Exception' which also catches the abort.

begin
  abort
  puts "This doesn't execute"
rescue Exception => e
  puts "Now we're executed!"
end   
  • 1
    I am disappointed I did not notice the lack of Exception for the rescue. :( – vgoff Nov 03 '12 at 11:16
  • 4
    Specifically, you can `rescue SystemExit`, since it's generally a good idea to rescue a more specific exception than `Exception`. – lukeasrodgers Dec 22 '14 at 23:10
  • This code works, but abort_on_exception is still not working. This code doesn't print 'yeah': ```begin Thread.abort_on_exception = true a = Thread.new { sleep(1); raise StandardError.new } a.abort_on_exception = true rescue Exception => e p 'yeah' end ``` – Andres Espinosa Nov 03 '20 at 14:53