ruby异常是否可以在Thread::handle_interrupt块之外异步处理



乍一看,我以为新的ruby 2.0Thread.handle_interrupt会解决我所有的异步中断问题,但除非我错了,否则我无法让它做我想做的事(我的问题在结尾和标题中)。

从文档中,我可以看到如何避免在某个块中接收中断,并将它们推迟到另一个块。下面是一个示例程序:

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

当使用参数2运行时,它输出如下内容:

--sending-
--received Ka-boom!

也就是说,主线程在两秒钟后向另一个线程发送RuntimeError,但该线程在进入内部Thread.handle_interrupt块之前不会处理它。

不幸的是,如果我不知道我的线程是在哪里创建的,我看不出这对我有什么帮助,因为我不能将它所做的一切都打包在一个块中。例如,在Rails中,我将用什么来包装Thread.handle_interruptbegin...rescue...end块?这是否会因运行的Web服务器而异?

我所希望的是一种注册处理程序的方法,就像Kernel.trap的工作方式一样。也就是说,我想指定与上下文无关的处理代码,它将处理特定类型的所有异常:

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

引发这个问题的是RabbitMQ gembunny如何向使用Thread#raise打开Bunny::Session的线程发送连接级别错误。这些异常可能会出现在任何地方,我只想记录它们,标记连接不可用,然后继续前进。

想法?

Ruby通过RubyQueue对象(不要与AMQP队列混淆)提供了这一点。如果Bunny要求您在打开Bunny::Session之前创建一个rubyQueue,并将Queue对象传递给它,它将向其发送连接级别错误,而不是使用Thread#raise将其发送回任何位置,那就太好了。然后,您可以简单地提供自己的Thread来通过队列消费消息。

查看RabbitMQ gem代码内部,看看是否可以做到这一点,或者询问该gem的维护人员

在Rails中,除非您能够从ruby队列中建立一个服务器范围的线程来使用,否则这不太可能奏效,当然,ruby队列是特定于web服务器的。我不知道如何在一个短暂的对象中做到这一点,例如Rails视图的代码,其中线程被重用,但Bunny并不知道(或关心)。

我想提出(哈哈!)一个实用的解决方法。这是龙。我假设你正在构建一个应用程序,而不是一个要重新分发的库,如果不是,那么就不要使用它。

您可以修补Thread#raise,特别是在您的会话线程实例上。

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)
end
end
session_thread = Thread.current
session_thread.singleton_class.prepend(AsynchronousExceptions)

请记住,exception_queue本质上是一个全球性的。我们还为每个人打补丁,而不仅仅是读者循环。幸运的是,做Thread.raise的正当理由很少,所以你可以安全地逃脱惩罚。

最新更新