在进行数据库调用的线程中使用 ruby​​ 超时

Posted

技术标签:

【中文标题】在进行数据库调用的线程中使用 ruby​​ 超时【英文标题】:Using ruby timeout in a thread making a database call 【发布时间】:2011-11-30 21:24:52 【问题描述】:

我使用的是 Ruby 1.9.2。

我有一个线程正在运行,它定期调用数据库。调用可能会很长,有时(由于各种原因)数据库连接会消失。如果它真的消失了,线程就会永远挂在那里。

所以,我想把它全部包装在一个超时中来处理这个问题。问题是,在第二次应该调用超时(总是第二次)时,它仍然只是挂起。超时永远不会生效。我知道这个问题存在于 1.8 中,但我被引导相信 timeout.rb 在 1.9 中有效。

t = Thread.new do
  while true do
    sleep SLEEPTIME
    begin
      Timeout::timeout(TIMEOUTTIME) do
        puts "About to do DB stuff, it will hang here on the second timeout"
        db.do_db_stuff()
        process_db_stuff()
      end
    rescue Timeout::Error
      puts "Timed out"
      #handle stuff here
    end
  end
end

知道为什么会发生这种情况以及我能做些什么吗?

【问题讨论】:

所以它没有抛出 Timeout::Error?它会抛出任何错误吗? 正确,没有任何反应,它只是冻结在原处。至少,这就是我确定的,将过多的puts 放在任何地方以确定执行的位置/是否恢复。 为什么要投掷?超时的全部要点应该是,如果给定的块运行时间过长,它会引发错误。我已经直接尝试过 datamapper 和 mysql2,希望它们都能自动重新连接并以这种方式解决问题,但没有运气。如果细节有帮助,假设它是一个 mysql2 连接,我在其上执行.query(...)。无论如何,什么都不会从那里抛出,并且它之外的通用 rescue 子句无论如何都应该捕获它。 你说得对,对不起。在您的 Timeout::timeout 块中仅放置 sleep 30TIMEOUTTIME = 20 即可验证这一点。我不知道。我会再玩一些。您可能需要在救援块中添加break,这取决于您的处理方式? 我还不确定如何处理它 - 手动重置数据库连接将是我第一次尝试,但不管第一步我只是看到超时确实有效.我希望,无论我做什么或如何处理它,只要我继续回到那个超时块(我这样做),超时就会在那里狙击需要太长时间的事情,但它似乎只适用于一次。 【参考方案1】:

一种可能性是您的线程没有挂起,它实际上死亡。这是你应该做的事情来弄清楚发生了什么。在创建工作线程之前添加:

Thread.abort_on_exception = true

当您的线程中引发从未被捕获的异常时,您的整个进程将终止,您可以查看引发了哪个异常。否则(这是默认设置),您的线程将被终止。

如果事实证明不是是问题所在,请继续阅读...

Ruby 的超时实现非常幼稚。它设置了一个单独的线程,睡眠 n 秒,然后在原始线程内盲目地引发 Timeout 异常。

现在,原始代码实际上可能位于 rescueensure 块的中间。在这样的块中引发异常将静默中止任何类型的清理代码。这可能会使超时的代码处于不正确的状态。

很难判断这是否正是您的问题,但看看数据库处理程序如何进行相当多的锁定和异常处理,这很有可能。 Here's an article that explains the issue in more depth.

有什么方法可以使用数据库库的内置超时处理?它可能在较低级别上实现,而不是使用 Ruby 的超时实现。

一个简单的替代方法是将数据库调用安排在一个单独的进程中。每次执行繁重的数据库提升时,您都可以分叉主进程。或者您可以设置一个简单的 cronjob 来执行执行它的脚本。如果您需要与主线程通信,这将稍微困难一些。如果您想了解哪种选项可能适合您的需求,请留下更多详细信息。


根据您的 cmets,线程正在消亡。这可能是您可能无法修复的库或应用程序代码中的错误。如果您希望捕获由数据库处理代码生成的任意错误并随后重试,您可以尝试以下操作:

t = Thread.new do
  loop do
    sleep INTERVAL
    begin
      # Execute database queries and process data
    rescue StandardError
      # Log error or recover from error situation before retrying
    end
  end
end

您也可以在rescue 块中使用retry 关键字立即重试,但您可能应该保留一个计数器以确保在不断发生不可恢复的错误时不会意外地无限期重试。

【讨论】:

看来它确实快要死了,虽然现在我不明白我得到的错误:rescue in block in <top (required)>': uninitialized constant Object::Error (NameError) 看来它确实快要死了,尽管现在我不明白我得到的错误:rescue in block in <top (required)>': uninitialized constant Object::Error (NameError)。尽管如此,我该如何处理呢?我从数据库中获得的数据被合并并与其他东西合并,然后服务调用它,所以如果/当这个线程死亡时,我希望能够产生另一个相同的线程。由于它总是发生在第一次超时之后,如果我可以在其他地方克隆线程,我也可以主动终止超时处理程序中的线程。 很高兴知道!最好找出此异常的原因并修复它,但捕获这些异常并再次尝试可能就足够了。答案已更新以解释如何。

以上是关于在进行数据库调用的线程中使用 ruby​​ 超时的主要内容,如果未能解决你的问题,请参考以下文章

Ruby / Rails超时与线程 - 如何等待一段时间然后重新加入

如何在 Ruby 的 RestClient gem 中设置超时?

Tomcat最大线程数最大连接数超时时间及高效配置

Tomcat最大线程数、最大连接数、超时时间及高效配置

php进程超时接口返回504错误分析

通过 SO_RCVTIMEO 套接字选项在 Ruby 中设置套接字超时