在进行数据库调用的线程中使用 ruby 超时
我正在使用 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
知道为什么会发生这种情况以及我能做些什么吗?
I am using Ruby 1.9.2.
I have a thread running which makes periodic calls to a database. The calls can be quite long, and sometimes (for various reasons) the DB connection disappears. If it does disappear, the thread just silently hangs there forever.
So, I want to wrap it all in a timeout to handle this. The problem is, on the second time through when a timeout should be called (always second), it still simply hangs. The timeout never takes effect. I know this problem existed in 1.8, but I was lead to believe timeout.rb worked in 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
Any idea why this is happening and what I can do about it?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
一种可能性是您的线程没有挂起,它实际上死亡。您应该执行以下操作来弄清楚发生了什么。在创建工作线程之前添加此内容:
当线程内引发从未捕获的异常时,整个进程将终止,您可以看到引发了哪个异常。否则(这是默认情况),您的线程将被终止。
如果这不是问题所在,请继续阅读……
Ruby 的超时实现非常幼稚。它设置一个单独的线程,休眠 n 秒,然后盲目地在原始线程内引发超时异常。
现在,原始代码实际上可能位于
rescue
或ensure
块的中间。在这样的块中引发异常将默默地中止任何类型的清理代码。这可能会使超时的代码处于不正确的状态。很难判断这是否正是您的问题,但是看看数据库处理程序如何执行相当多的锁定和异常处理,这可能是很有可能的。 这里有一篇文章更深入地解释了这个问题。
有什么方法可以使用数据库的内置超时处理吗?它可能在较低级别上实现,而不是使用 Ruby 的超时实现。
一个简单的替代方案是在单独的进程中安排数据库调用。每次进行繁重的数据库提升时,您都可以分叉主进程。或者您可以设置一个简单的 cronjob 来执行执行它的脚本。如果您需要与主线程通信,这会稍微困难一些。如果您需要有关哪个选项适合您的需求的建议,请留下更多详细信息。
根据您的评论,该线程即将消失。这可能是库或应用程序代码中的错误,您可能无法修复该错误。如果您希望捕获数据库处理代码生成的任意错误并随后重试,可以尝试如下操作:
您还可以在
rescueretry
关键字code> 阻止立即重试,但您可能应该保留一个计数器,以确保当不可恢复的错误不断发生时,您不会意外地无限期地重试。One possibility is that your thread does not hang, it actually dies. Here's what you should do to figure out what's going on. Add this before you create your worker thread:
When an exception is raised inside your thread that is never caught, your whole process is terminated, and you can see which exception was raised. Otherwise (and this is the default), your thread is killed.
If this turns out not to be the problem, read on...
Ruby's implementation of timeouts is pretty naive. It sets up a separate thread that sleeps for n seconds, then blindly raises a Timeout exception inside the original thread.
Now, the original code might actually be in the middle of a
rescue
orensure
block. Raising an exception in such a block will silently abort any kind of cleanup code. This might leave the code that times out in an improper state.It's quite difficult to tell if this is your problem exactly, but seeing how database handlers might do a fair bit of locking and exception handling, it might be very likely. Here's an article that explains the issue in more depth.
Is there any way you can use your database library's built-in timeout handling? It might be implemented on a lower level, not using Ruby's timeout implementation.
A simple alternative is to schedule the database calls in a separate process. You can fork the main process each time you do the heavy database-lifting. Or you could set up a simple cronjob to execute a script that executes it. This will be slightly more difficult if you need to communicate with your main thread. Please leave some more details if you want any advice on which option might suit your needs.
Based on your comments, the thread is dying. This might be a fault in libraries or application code that you may or may not be able to fix. If you wish to trap any arbitrary error that is generated by the database handling code and subsequently retry, you can try something like the following:
You can also use the
retry
keyword in therescue
block to retry immediately, but you probably should keep a counter to make sure you're not accidentally retrying indefinitely when an unrecoverable error keeps occurring.