当重新启动Heroku工作程序时(无论是在命令上还是作为部署的结果),Heroku都会向工作进程发送SIGTERM
。在delayed_job
的情况下,SIGTERM
signal is caught然后工作程序在当前作业(如果有)停止后停止执行。
如果工人需要很长时间才能完成,那么Heroku将发送SIGKILL
。在delayed_job
的情况下,这会在数据库中留下一个锁定的作业,不会被另一个工作人员接收。
我想确保工作最终完成(除非出现错误)。鉴于此,最好的方法是什么?
我看到两个选项。但我想得到其他意见:
delayed_job
以在收到SIGTERM
时停止处理当前作业(并解除锁定)。有什么想法吗?
答案 0 :(得分:27)
现在更好的解决方案内置于delayed_job中。使用此设置通过在初始化程序中添加此项来在TERM信号上抛出异常:
Delayed::Worker.raise_signal_exceptions = :term
使用该设置,作业将在heroku发出针对非合作进程的最终KILL信号之前正确清理并退出:
您可能需要在SIGTERM信号上引发异常,Delayed :: Worker.raise_signal_exceptions =:term将导致worker引发SignalException,导致正在运行的作业中止并被解锁,这使得该作业可供其他工作人员使用。此选项的默认值为false。
raise_signal_exceptions
的可能值为:
false
- 不会引发任何例外(默认) :term
- 仅在TERM信号上引发异常,但INT将等待当前作业完成。true
- 将在TERM和INT上引发异常自版本3.0.5起可用。
请参阅this commit介绍它的位置。
答案 1 :(得分:12)
<强> TLDR:强>
将此放在工作方法的顶部:
begin
term_now = false
old_term_handler = trap 'TERM' do
term_now = true
old_term_handler.call
end
和强>
确保每十秒至少调用一次:
if term_now
puts 'told to terminate'
return true
end
和强>
在你的方法结束时,把它放在:
ensure
trap 'TERM', old_term_handler
end
<强>解释强>
我遇到了同样的问题并且遇到了this Heroku article。
作业包含外部循环,因此我按照文章添加了trap('TERM')
和exit
。但是delayed_job
选择failed with SystemExit
并将任务标记为失败。
SIGTERM
现在被我们的trap
the worker's handler isn't called所困,而是立即重新启动作业,然后在几秒钟后获得SIGKILL
。回到原点。
我尝试了exit
的一些替代方案:
return true
将作业标记为成功(并将其从队列中删除),但如果队列中有另一个作业正在等待则会遇到同样的问题。
调用exit!
将成功退出作业和但它不允许工作人员从队列中删除作业,因此您仍然拥有'孤儿锁定工作的问题。
我的最终解决方案是在我的答案顶部给出的解决方案,它由三部分组成:
在我们开始这个可能很长的工作之前,我们通过执行'TERM'
(如Heroku文章中所述)为trap
添加一个新的中断处理程序,我们用它来设置{{ 1}}。
但是我们还必须抓住term_now = true
设置old_term_handler
设置(trap
)和的call
Delayed:Job:Worker
它。
我们仍然必须确保我们将控制权返回term_now
并有足够的时间进行清理和关闭,因此我们应该每隔十秒检查return
至少(刚刚下) true
如果是return true
。
您可以return false
或Delayed:Job:Worker
,具体取决于您是否希望将该作业视为成功。
最后,记住删除处理程序并在完成后重新安装{{1}},这是至关重要。如果你没有这样做,你将保留对我们添加的那个的悬空引用,如果你在其上添加另一个(例如,当工人再次启动这个工作时),可能会导致内存泄漏。
答案 2 :(得分:5)
网站新手,所以无法对Dave的帖子发表评论,需要添加新的答案。
我与戴夫的方法有关的问题是我的任务很长(分钟长达8小时),根本不重复。我不能每隔10秒“确保打电话”。 此外,我尝试过戴夫的答案,无论我返回什么,无论是真还是假,工作总是从队列中删除。我不清楚如何将工作留在队列中。
见this pull request。我认为这对我有用。请随时评论并支持拉取请求。
我正在尝试陷阱,然后拯救退出信号......到目前为止没有运气。
答案 3 :(得分:4)
这就是max_run_time
的用途:从作业被锁定之后经过max_run_time
之后,其他进程将能够获得锁定。
答案 4 :(得分:2)
我最终不得不在几个地方做这个,所以我创建了一个模块,我坚持使用lib /,然后从我的延迟作业执行块中运行ExitOnTermSignal.execute {long_running_task}。
# Exits whatever is currently running when a SIGTERM is received. Needed since
# Delayed::Job traps TERM, so it does not clean up a job properly if the
# process receives a SIGTERM then SIGKILL, as happens on Heroku.
module ExitOnTermSignal
def self.execute(&block)
original_term_handler = Signal.trap 'TERM' do
original_term_handler.call
# Easiest way to kill job immediately and having DJ mark it as failed:
exit
end
begin
yield
ensure
Signal.trap 'TERM', original_term_handler
end
end
end
答案 5 :(得分:1)
我使用状态机来跟踪作业的进度,并使进程具有幂等性,以便我可以多次调用给定作业/对象上的执行,并确信它不会重新应用破坏性操作。然后更新rake task / delayed_job以释放TERM上的日志。
当流程重新启动时,它将按预期继续。