"rescue Exception" 没有在 net_http 营救 Timeout::Error

"rescue Exception" not rescuing Timeout::Error in net_http

我们似乎遇到了 rescue Exception 没有捕捉到特定异常的情况。

我正在尝试发送有关发生的任何异常的电子邮件警报,然后继续处理。我们已经对有意退出进行了必要的处理。我们希望循环在提醒我们之后继续进行其他任何事情。

根据堆栈跟踪,未被捕获的异常表面上是 Timeout::Error

这是堆栈跟踪,删除了对我的中间代码的引用(我的代码的最后一行是 request.rb:93):

/opt/ruby-enterprise/lib/ruby/1.8/timeout.rb:64:in `rbuf_fill': execution expired (Timeout::Error)
    from /opt/ruby-enterprise/lib/ruby/1.8/net/protocol.rb:134:in `rbuf_fill'
    from /opt/ruby-enterprise/lib/ruby/1.8/net/protocol.rb:116:in `readuntil'
    from /opt/ruby-enterprise/lib/ruby/1.8/net/protocol.rb:126:in `readline'
    from /opt/ruby-enterprise/lib/ruby/1.8/net/http.rb:2028:in `read_status_line'
    from /opt/ruby-enterprise/lib/ruby/1.8/net/http.rb:2017:in `read_new'
    from /opt/ruby-enterprise/lib/ruby/1.8/net/http.rb:1051:in `__request__'
    from /mnt/data/blueleaf/releases/20150211222522/vendor/bundle/ruby/1.8/gems/rest-client-1.6.7/lib/restclient/net_http_ext.rb:51:in `request'
    from /opt/ruby-enterprise/lib/ruby/1.8/net/http.rb:1037:in `__request__'
    from /opt/ruby-enterprise/lib/ruby/1.8/net/http.rb:543:in `start'
    from /opt/ruby-enterprise/lib/ruby/1.8/net/http.rb:1035:in `__request__'
    from /mnt/data/blueleaf/releases/20150211222522/vendor/bundle/ruby/1.8/gems/rest-client-1.6.7/lib/restclient/net_http_ext.rb:51:in `request'
    from /mnt/data/blueleaf/releases/20150211222522/app/models/dst/request.rb:93:in `send'
    [intermediate code removed]
    from script/dst_daemon.rb:49
    from script/dst_daemon.rb:46:in `each'
    from script/dst_daemon.rb:46
    from /opt/ruby-enterprise/lib/ruby/1.8/benchmark.rb:293:in `measure'
    from script/dst_daemon.rb:45
    from script/dst_daemon.rb:24:in `loop'
    from script/dst_daemon.rb:24
    from script/runner:3:in `eval'
    from /mnt/data/blueleaf/releases/20150211222522/vendor/bundle/ruby/1.8/gems/rails-2.3.14/lib/commands/runner.rb:46
    from script/runner:3:in `require'

这里是 request.rb#send,第 93 行带有注释:

  def send
    build

    uri = URI.parse([DST::Request.configuration[:prefix], @path].join('/'))
    https = Net::HTTP.new(uri.host, uri.port)
    https.use_ssl = true
    https.verify_mode = OpenSSL::SSL::VERIFY_NONE
    https_request = Net::HTTP::Post.new(uri.request_uri.tap{|e| debug_puts "\nURL: #{e}, host:#{uri.host}"})
    # line 93:
    https_request.body = request 
    response = https.request(https_request)
    # the rest should be irrelevant

这里是dst_daemon.rb;第 49 行用注释表示,应该捕获除故意中断以外的任何内容的 rescue Exception 接近尾声:

DST::Request.environment = :production
class DST::Request::RequestFailed < Exception; end

Thread.abort_on_exception = true
SEMAPHORE = 'import/dst/start.txt' unless defined?(SEMAPHORE)
DEBUG_DST = 'import/dst/debug.txt' unless defined?(DEBUG_DST)
DEBUG_LOG = 'import/dst/debug.log' unless defined?(DEBUG_LOG)

def debug_dst(*args)
  File.open(DEBUG_LOG, 'a') do |f|
    f.print "#{Time.now.localtime}: "
    f.puts(*args)
  end if debug_dst?
end

def debug_dst?
  File.exist?(DEBUG_DST)
end

dst_ids = [Institution::BAA_DST_WS_CLIENT_ID, Institution::BAA_DST_WS_DEALER_ID]
institutions = Institution.find_all_by_baa_api_financial_institution_id(dst_ids)
DST::Collector.prime_key!

loop do
  begin
    if File.exist?(SEMAPHORE)
      debug_dst 'waking up...'

      custodians = InstitutionAccount.acts_as_baa_custodian.
        find_all_by_institution_id(institutions).select(&:direct?)
      good,bad = custodians.partition do |c|
        c.custodian_users.map{|e2|e2.custodian_passwords.count(:conditions => ['expired is not true']) == 1}.all?
      end
      if bad.present?
        msg = "  skipping: \n"
        bad.each do |c|
          msg += "    #{c.user.full_name_or_email}, custodian id #{c.id}: "
          c.custodian_users.each{|cu| msg += "#{cu.username}:#{cu.custodian_passwords.count(:conditions => ['expired is not true'])}; "}
          msg += "\n"
        end
        AdminSimpleMailer.deliver_generic_mail("DST Daemon skipping #{bad.size} connections", msg)
        debug_dst msg
      end

      Benchmark.measure do
        good.each do |custodian|
          begin
            debug_dst "  collecting for: #{custodian.name}, #{custodian.subtitle}, (#{custodian.id.inspect})"
            # line 49:
            DST::Collector.new(custodian, 0).collect!
          rescue DST::Request::PasswordFailed, DST::Request::RequestFailed  => e
            message = e.message + "\n\n" + e.backtrace.join("\n")
            AdminSimpleMailer.deliver_generic_mail("DST Daemon Connection Failed #{e.class.name}", message)
            debug_dst "  skipping, #{e.class}"
          end
        end
      end.tap{|duration| debug_dst "collection done, duration #{duration.real.to_f/60} minutes. importing" }

      DST::Strategy.new(Date.yesterday, :recompute => true).import!
      debug_dst 'import done.'

      rm SEMAPHORE, :verbose => debug_dst?
    else
      debug_dst 'sleeping.' if Time.now.strftime("%M").to_i % 5 == 0
    end
  rescue SystemExit, Interrupt
    raise
  rescue Exception => e
    message = e.message + "\n\n" + e.backtrace.join("\n")
    AdminSimpleMailer.deliver_generic_mail("DST Daemon Exception #{e.class.name}", message)
  ensure
    sleep 60
  end
end

除了从 SystemExit 或 Interrupt 之外,这个循环不应该以堆栈跟踪退出吗?

您可能已经知道,在救援块内调用 raise 会向调用方引发异常。 由于 Timeout::Error 是 ruby 1.8* 中的 Interrupt,net_http 引发的超时异常在 rescue SystemExit, Interrupt 块中处理,而不是在以下 [=14] =].

要验证 Timeout::Error 是一个中断,只需评估 Timeout::Error.ancestors。您从中得到的是 类 Timeout::Error 继承自的层次结构。

*ruby1.9 中不再是这种情况。