Can ruby exceptions be handled asynchronously outside of a Thread::handle_interrupt block?
Ruby provides for this with the ruby Queue
object (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).
I'd like to raise (ha-ha!) a pragmatic workaround. Here be dragons. I'm assuming you're building an application and not a library to be redistributed, if not then don't use this.
You can patch Thread#raise
, specifically on your session thread instance.
module AsynchronousExceptions @exception_queue = Queue.new class << self attr_reader :exception_queue end def raise(*args) # We do this dance to capture an actual error instance, because # raise may be called with no arguments, a string, 3 arguments, # an error, or any object really. We want an actual error. # NOTE: This might need to be adjusted for proper stack traces. error = begin Kernel.raise(*args) rescue => error error end AsynchronousExceptions.exception_queue.push(error) endendsession_thread = Thread.currentsession_thread.singleton_class.prepend(AsynchronousExceptions)
Bear in mind that exception_queue
is essentially a global. We also patch for everybody, not just the reader loop. Luckily there are few legitimate reasons to do Thread.raise
, so you might just get away with this safely.