Ruby Net::HTTP 执行已过期

Ruby Net::HTTP execution expired

使用 Net::HTTP,我定期发现下面的代码使用 "execution expired" 消息从 StandardError 中拯救出来,尽管来自访问 URL 的 Web 服务器日志显示相应的回复很快就发出来了。当 Web 服务器日志显示响应时间超过 5 秒时,我通常会看到来自 Timeout::Error.

的代码救援

什么情况下会导致下面的代码使用 "execution expired" 而不是 从 Timeout::Error 中拯救 StandardError?

这段代码是 运行 在一个相对古老的 Ruby 1.9.3 上的多线程程序中,在一个不支持较新版本 Ruby 的平台上。虽然该程序是多线程的,但显示的代码仅在单线程上运行。

begin
  connection = Net::HTTP.new(uri.host, uri.port)
  connection.open_timeout = 5
  connection.read_timeout = 5
  connection.start do |http|
    request = Net::HTTP::Post.new("/reader_events")
    request.body = body
    response = http.request(request)
  end
rescue StandardError => std_error
  log "error sending event to server: #{std_error}"
rescue Timeout::Error => error
  log "timeout sending event to server"
end

这是因为 rescue 的工作原理。查看 Exception class 的文档页面。基本上你可以创建许多继承自单个异常的异常,并使用父 class:

的 rescue 处理所有异常
begin
  ...
rescue Exception => exception
  ...
end

此代码将拯救所有类型的异常,因为 Exception 是根(其他异常从它继承)。在你的情况下 Timeout::Error 继承自 RuntimeError 继承自 StandardError:

Timeout::Error.ancestors
  => [Timeout::Error, RuntimeError, StandardError, Exception, Object, PP::ObjectMixin, Kernel, BasicObject]

结果有点像 Exception:

Timeout::Error.new.is_a?(StandardError)
  => true

你的情况的另一件事是解释器将从上到下检查每个 rescue 语句。这意味着首先它会检查 exception 是否属于 StandardError 类型,然后它会移动到下面的 rescue 块。您应该始终列出 rescue 个块,从最具体到最一般。

更改 rescue 个块的顺序以修复代码。