Handling exceptions raised in a Ruby thread

前端 未结 4 1609
陌清茗
陌清茗 2020-12-09 14:49

I am looking for a solution of classic problem of exception handling. Consider following piece of code:

def foo(n)
  puts \" for #{n}\"
  sleep n
  raise \"a         


        
相关标签:
4条回答
  • 2020-12-09 15:27

    This will wait for the first thread to either raise or return (and re-raise):

    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
    
    0 讨论(0)
  • 2020-12-09 15:28

    Postponed exceptions processing (Inspired by @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'
    
    0 讨论(0)
  • 2020-12-09 15:30
    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
    
    0 讨论(0)
  • 2020-12-09 15:50

    If you want any unhandled exception in any thread to cause the interpreter to exit, you need to set Thread::abort_on_exception= to true. Unhandled exception cause the thread to stop running. If you don't set this variable to true, exception will only be raised when you call Thread#join or Thread#value for the thread. If set to true it will be raised when it occurs and will propagate to the main thread.

    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
    

    Output:

     for 5
     for 20
     for 3
     for 15
    EXCEPTION: #<RuntimeError: after 3>
    MESSAGE: after 3
    

    Note: but if you want any particular thread instance to raise exception this way there are similar abort_on_exception= Thread instance method:

    t = Thread.new {
       # do something and raise exception
    }
    t.abort_on_exception = true
    
    0 讨论(0)
提交回复
热议问题