Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can ruby exceptions be handled asynchronously outside of a Thread::handle_interrupt block?

At first glance, I thought the new ruby 2.0 Thread.handle_interrupt was going to solve all my asynchronous interrupt problems, but unless I'm mistaken I can't get it to do what I want (my question is at the end and in the title).

From the documentation, I can see how I can avoid receiving interrupts in a certain block, deferring them to another block. Here's an example program:

duration = ARGV.shift.to_i

t = Thread.new do
  Thread.handle_interrupt(RuntimeError => :never) do
    5.times { putc '-'; sleep 1 }
    Thread.handle_interrupt(RuntimeError => :immediate) do
      begin
        5.times { putc '+'; sleep 1}
      rescue
        puts "received #{$!}"
      end
    end
  end
end

sleep duration
puts "sending"
t.raise "Ka-boom!"

if t.join(20 + duration).nil?
  raise "thread failed to join"
end

When run with argument 2 it outputs something like this:

--sending-
--received Ka-boom!

That is, the main thread sends a RuntimeError to the other thread after two seconds, but that thread doesn't handle it until it gets into the inner Thread.handle_interrupt block.

Unfortunately, I don't see how this can help me if I don't know where my thread is getting created, because I can't wrap everything it does in a block. For example, in Rails, what would I wrap the Thread.handle_interrupt or begin...rescue...end blocks around? And wouldn't this differ depending on what webserver is running?

What I was hoping for is a way to register a handler, like the way Kernel.trap works. Namely, I'd like to specify handling code that's context-independent that will handle all exceptions of a certain type:

register_handler_for(SomeExceptionClass) do 
 ... # handle the exception
end

What precipitated this question was how the RabbitMQ gem, bunny sends connection-level errors to the thread that opened the Bunny::Session using Thread#raise. These exceptions could end up anywhere and all I want to do is log them, flag that the connection is unavailable, and continue on my way.

Ideas?

like image 395
Kevin Bullaughey Avatar asked Jan 15 '14 20:01

Kevin Bullaughey


1 Answers

Ruby provides for this with the ruby Queueobject (not to be confused with an AMQP queue). It would be nice if Bunny required you to create a ruby Queue before opening a Bunny::Session, and you passed it that Queue object, to which it would send connection-level errors instead of using Thread#raise to send it back to where ever. You could then simply provide your own Thread to consume messages through the Queue.

It might be worth looking inside the RabbitMQ gem code to see if you could do this, or asking the maintainers of that gem about it.

In Rails this is not likely to work unless you can establish a server-wide thread to consume from the ruby Queue, which of course would be web server specific. I don't see how you can do this from within a short-lived object, e.g. code for a Rails view, where the threads are reused but Bunny doesn't know that (or care).

like image 166
Tom Avatar answered Nov 05 '22 12:11

Tom