16

Heroku ワーカーが (コマンドまたはデプロイの結果として) 再起動されると、HerokuSIGTERMはワーカー プロセスに送信します。の場合delayed_jobSIGTERM シグナルがキャッチされ、現在のジョブ (存在する場合) が停止した後にワーカーが実行を停止します。

ワーカーが完了するまでに時間がかかる場合、Heroku は を送信しますSIGKILL。の場合delayed_job、これによりデータベース内にロックされたジョブが残り、別のワーカーが取得することはありません。

ジョブが最終的に終了することを確認したいと思います (エラーがない限り)。それを考えると、これにアプローチする最良の方法は何ですか?

2 つの選択肢があります。しかし、私は他の入力を取得したいと思います:

  1. . delayed_job_ SIGTERM_
  2. 孤立したロックされたジョブを検出してロックを解除する (プログラムによる) 方法を考え出します。

何かご意見は?

4

6 に答える 6

32

SIGTERM で正常にジョブを中止する

はるかに優れたソリューションがdelayed_jobに組み込まれました。この設定を使用して、初期化子にこれを追加することにより、TERM シグナルで例外をスローします。

Delayed::Worker.raise_signal_exceptions = :term

この設定では、heroku が非協調プロセス向けの最終的な KILL シグナルを発行する前に、ジョブが適切にクリーンアップされて終了します。

SIGTERM シグナルで例外を発生させる必要がある場合があります。Delayed::Worker.raise_signal_exceptions = :term により、ワーカーは SignalException を発生させ、実行中のジョブを中止してロックを解除し、他のワーカーがジョブを利用できるようにします。このオプションのデフォルトは false です。

可能な値は次のraise_signal_exceptionsとおりです。

  • false- 例外は発生しません(デフォルト)
  • :term- TERM シグナルでのみ例外を発生させますが、INT は現在のジョブが終了するのを待ちます。
  • true- TERM と INT で例外を発生させます

バージョン 3.0.5 以降で利用できます。

導入されたこのコミットを参照してください。

于 2013-05-29T10:47:16.253 に答える
12

TLDR:

これをジョブメソッドの先頭に置きます:

begin
  term_now = false
  old_term_handler = trap 'TERM' do
    term_now = true
    old_term_handler.call
  end

これが少なくとも 10 秒に 1 回呼び出されることを確認します。

  if term_now
    puts 'told to terminate'
    return true
  end

メソッドの最後に、これを入れます:

ensure
  trap 'TERM', old_term_handler
end

説明:

私は同じ問題を抱えていて、この Heroku articleに出会いました。

ジョブには外側のループが含まれていたので、記事に従って と を追加しましtrap('TERM')exit。ただしdelayed_job 、それをピックアップしfailed with SystemExit、タスクを失敗としてマークします。

SIGTERMトラップされるとtrap 、ワーカーのハンドラーは呼び出されず、代わりにすぐにジョブを再開しSIGKILL、数秒後に取得します。振り出しに戻って。

私はいくつかの代替案を試しましたexit

  • Areturn trueはジョブを成功としてマークします (そしてキューから削除します) が、キューで待機している別のジョブがある場合、同じ問題に悩まされます。

  • を呼び出すexit!と、ジョブとワーカーは正常に終了しますが、ワーカージョブをキューから削除することはできないため、「孤立したロックされたジョブ」の問題が引き続き発生します。

私の最終的な解決策は、回答の上部にあるもので、次の 3 つの部分で構成されています。

  1. 'TERM'潜在的に長いジョブを開始する前に、trap(Heroku の記事で説明されているように) を実行して新しい割り込みハンドラーを追加し、それを使用して を設定しterm_now = trueます。

    しかしold_term_handler遅延ジョブ ワーカー コードセット ( によって返されるtrap)も取得して覚えておく必要がcallあります。

  2. Delayed:Job:Worker制御をクリーンアップしてシャットダウンするのに十分な時間でに戻す必要があるためterm_now、少なくとも 10 秒ごとreturntrue.

    ジョブが成功したと見なされるかどうかに応じて、またはどちらreturn trueかを選択できます。return false

  3. 最後に、ハンドラーを削除し、Delayed:Job:Worker終了したらハンドラーを再度インストールすることを忘れないでください。これを行わないと、追加した参照へのダングリング参照が保持され、その上に別の参照を追加するとメモリ リークが発生する可能性があります (たとえば、ワーカーがこのジョブを再度開始した場合)。

于 2012-09-26T17:00:37.767 に答える
5

サイトが初めてなので、デイブの投稿にコメントすることはできず、新しい回答を追加する必要があります。

デイブのアプローチで私が抱えている問題は、私のタスクが長く(数分から最大8時間)、まったく繰り返されないことです。10秒ごとに「確実に電話をかける」ことはできません。また、私はDaveの答えを試しましたが、返される内容(trueまたはfalse)に関係なく、ジョブは常にキューから削除されます。仕事を待ち行列に入れておく方法がわかりません。

このプルリクエストを参照してください。これは私にとってはうまくいくかもしれないと思います。遠慮なくコメントして、プルリクエストをサポートしてください。

私は現在、トラップを試し、出口信号を救出しています...これまでのところ運がありません。

于 2012-10-04T04:49:28.480 に答える
4

つまり、ジョブがロックされてからmax_run_timemax_run_time経過すると、他のプロセスがロックを取得できるようになります。

Google グループからこのディスカッションを参照してください

于 2012-09-26T01:17:38.810 に答える
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
于 2012-12-12T23:48:43.260 に答える
1

ステート マシンを使用してジョブの進行状況を追跡し、プロセスを冪等にすることで、特定のジョブ/オブジェクトに対して perform を複数回呼び出すことができ、破壊的なアクションを再適用しないと確信できます。次に、rake task/delayed_job を更新して、TERM のログを解放します。

プロセスが再起動すると、意図したとおりに続行されます。

于 2012-05-04T08:35:03.557 に答える