Długo działające opóźnione zadania pozostają zablokowane po ponownym uruchomieniu na Heroku

Po ponownym uruchomieniu Heroku worker (na polecenie lub w wyniku wdrożenia), Heroku wysyła SIGTERM do procesu worker. W przypadku delayed_job,SIGTERM sygnał jest przechwytywany, a następnie worker przestaje wykonywać po zatrzymaniu bieżącego zadania (jeśli takie istnieje).

Jeśli robotnik zajmie dużo czasu, to Heroku wyśle SIGKILL. W przypadku delayed_job, pozostawia to zablokowane zadanie w bazie danych, które nie zostanie odebrane przez innego pracownika.

Chciałbym zapewnić, że zadania ostatecznie kończą się (chyba że jest błąd). Biorąc to pod uwagę, jaki jest najlepszy sposób, aby podejść do tego?

Widzę dwie opcje. Ale chciałbym dostać inny wkład:
  1. zmodyfikuj delayed_job, aby przestać pracować nad bieżącym zadaniem (i zwolnić blokadę), gdy otrzyma SIGTERM.
  2. wymyśl (programowy) sposób wykrywania osieroconych zablokowanych zadań, a następnie odblokuj je.
Jakieś pomysły?
Author: dukedave, 2012-05-03

6 answers

TLDR:

Umieść to na górze swojej metody pracy:

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

I

Upewnij się, że jest wywoływany co najmniej raz na dziesięć sekund:

  if term_now
    puts 'told to terminate'
    return true
  end

I

Na koniec swojej metody, umieść to:

ensure
  trap 'TERM', old_term_handler
end

Explanation:

Miałem ten sam problem i natknąłem się na Ten artykuł Heroku .

Praca zawierała zewnętrzną pętlę, więc podążałem za artykułem i dodałem trap('TERM') i exit. Jednak delayed_job określa to jako failed with SystemExit i oznacza, że zadanie nie powiodło się.

With the SIGTERM now trapped by our trap funkcja obsługi pracownika nie jest wywoływana, a zamiast tego natychmiast uruchamia ponownie zadanie, a następnie otrzymuje SIGKILL kilka sekund później. Wracamy do punktu wyjścia.

Wypróbowałem kilka alternatyw dla exit:

  • A return true oznacza zadanie jako pomyślne (i usuwa je z kolejki), ale cierpi na ten sam problem, jeśli jest inne zadanie czekam w kolejce.

  • Wywołanie {[12] } zakończy zadanie i workera, ale nie pozwala robotnikowi usunąć zadania z kolejki, więc nadal masz problem z "osieroconymi zablokowanymi zadaniami".

Moje ostateczne rozwiązanie było podane na górze mojej odpowiedzi, składa się z trzech części:]}
  1. Przed rozpoczęciem potencjalnie długiego zadania dodajemy nowy handler przerwań dla 'TERM' wykonując trap (zgodnie z opisem w artykule Heroku) i używamy go do Ustawienia term_now = true.

    Ale musimy również pobrać old_term_handler, który opóźniony kod pracownika pracy ustawiony (który jest zwracany przez trap) i pamiętaj, aby call to.

  2. Nadal musimy zapewnić powrót kontroli do Delayed:Job:Worker z wystarczającym czasem na jej wyczyszczenie i wyłączenie, więc powinniśmy sprawdzać term_now co najmniej (tuż poniżej) co dziesięć sekund i return jeśli jest to true.

    Możesz albo return true albo return false w zależności od tego, czy chcesz, aby praca była uznana za udaną, czy nie.

  3. Na koniec jest Ważne , aby pamiętać o usunięciu obsługi i zainstalowaniu z powrotem Delayed:Job:Worker Po zakończeniu. Jeśli tego nie zrobisz, zachowasz zwisające odniesienie do dodanego przez nas, co może skutkować wyciekiem pamięci, jeśli dodasz jeszcze jedną (na przykład, gdy pracownik ponownie rozpocznie tę pracę).

 12
Author: dukedave,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-03 16:57:07

Przerwij zadanie czysto na SIGTERM

Znacznie lepsze rozwiązanie jest teraz wbudowane w delayed_job. Użyj tego ustawienia, aby rzucić wyjątek dla sygnałów TERM, dodając go do inicjalizatora:

Delayed::Worker.raise_signal_exceptions = :term

Z tym ustawieniem, zadanie zostanie prawidłowo wyczyszczone i zakończone przed heroku wydaniem ostatecznego sygnału KILL przeznaczonego dla procesów niewspółpracujących:

Może być konieczne zgłaszanie WYJĄTKÓW na sygnałach SIGTERM, Delayed:: Worker.raise_signal_exceptions =: term spowoduje, że pracownik podnieść Sygnalexception powodując uruchomione zadanie do przerwania i być odblokowane, co sprawia, że zadanie dostępne dla innych pracowników. Domyślną wartością tej opcji jest false.

Możliwe wartości dla raise_signal_exceptions to:

  • false - żadne wyjątki nie zostaną podniesione (domyślnie)
  • :term - wywoła wyjątek tylko na sygnałach TERM, ale INT będzie czekał na zakończenie bieżącego zadania.
  • true - wywoła wyjątek w terminie i INT

Dostępny od wersji 3.0.5.

Zobacz: https://github.com/collectiveidea/delayed_job/commit/90579c3047099b6a58595d4025ab0f4b7f0aa67a

 25
Author: Alex Neth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-19 16:55:51

Do tego właśnie służy max_run_time: po upłynięciu max_run_time od czasu zablokowania zadania, inne procesy będą mogły uzyskać blokadę.

Zobacztę dyskusję z google groups

 4
Author: klochner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-26 01:17:38

Nowy na stronie, więc nie mogę skomentować Posta Dave ' a i muszę dodać nową odpowiedź.

Problem z podejściem Dave ' a polega na tym, że moje zadania są długie (minuty do 8 godzin) i wcale nie są powtarzalne. Nie mogę "zapewnić, aby dzwonić" co 10 sekund. Poza tym, próbowałem odpowiedzi Dave ' a, i zadanie jest zawsze usuwane z kolejki, niezależnie od tego, co zwracam-prawda czy fałsz. Nie wiem, jak utrzymać pracę w kolejce.

Zobacz to to żądanie pull . Myślę, że to może mi się udać. Prosimy o komentarz i wsparcie pull request.

Obecnie eksperymentuję z pułapką, a następnie ratuję sygnał wyjścia... Na razie bez powodzenia.

 4
Author: Istvan Jonyer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-30 00:09:17

Skończyło się na tym, że musiałem to zrobić w kilku miejscach, więc stworzyłem moduł, który trzymam w lib/, a następnie uruchamiam ExitOnTermSignal.wykonaj { long_running_task } z bloku wykonaj mojego opóźnionego zadania.

# 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
 2
Author: Ari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-12 23:48:43

Używam maszyny stanowej do śledzenia postępu zadań i sprawiam, że proces jest idempotentny, dzięki czemu mogę wielokrotnie wywoływać perform na danym zadaniu/obiekcie i mieć pewność, że nie zastosuje on ponownie destrukcyjnej akcji. Następnie zaktualizuj zadanie rake / delayed_job, aby zwolnić termin logowania.

Po ponownym uruchomieniu proces będzie kontynuowany zgodnie z przeznaczeniem.

 1
Author: Glenn Gillen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-04 08:35:03