Manejo de excepciones planteadas en un hilo de Ruby.

Estoy buscando una solución al problema clásico de manejo de excepciones. Considere la siguiente pieza de código:

def foo(n) puts " for #{n}" sleep n raise "after #{n}" end begin threads = [] [5, 15, 20, 3].each do |i| threads < e puts "EXCEPTION: #{e.inspect}" puts "MESSAGE: #{e.message}" end 

Este código atrapa la excepción después de 5 segundos.

Pero si cambio la matriz como [15, 5, 20, 3] , el código anterior captura la excepción después de 15 segundos. En resumen, siempre captura la excepción planteada en el primer hilo.

Cualquier idea, ¿por qué? ¿Por qué no atrapa la excepción después de 3 segundos cada vez? ¿Cómo puedo capturar la primera excepción provocada por cualquier hilo?

Si desea que una excepción no controlada en cualquier subproceso haga que el intérprete salga, debe establecer Thread :: abort_on_exception = en true . Excepción no controlada hace que el hilo deje de ejecutarse. Si no establece esta variable en verdadero, la excepción solo se generará cuando llame a Thread#join o Thread#value para el hilo. Si se establece en verdadero, se generará cuando ocurra y se propagará al hilo principal.

 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 

Salida:

  for 5 for 20 for 3 for 15 EXCEPTION: # MESSAGE: after 3 

Nota: pero si desea que una instancia de subproceso particular genere una excepción de esta manera, hay un método abort_on_exception = instancia de subproceso similar:

 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 

Procesamiento de excepciones pospuesto (Inspirado por @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' 

Esto esperará a que el primer hilo suba o regrese (y vuelva a subir):

 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