Задача Upstart зависает после успешного завершения

У меня есть задача Upstart, которая запускает несколько экземпляров службы на основе автоматического запуска нескольких экземпляров upstart и Перезапуск процессов экземпляра Upstart. Он работает и запускает все экземпляры, но после их успешного запуска просто зависает. Если я Ctrl-C выхожу, а затем проверяю экземпляры либо с помощью service status, либо с помощью ps, все они успешно запущены, поэтому я не знаю, что он делает, когда зависает.

Вот мой сценарий:

description "all-my-workers"

start on runlevel [2345]

task

console log

env NUM_INSTANCES=1
env STARTING_PORT=42002

pre-start script
  for i in `seq 1 $NUM_INSTANCES`;
  do
    start my-worker N=$i PORT=$(($STARTING_PORT + $i))
  done
end script

Когда я делаю service start all-my-workers, я получаю это:

vagrant@vagrant-service:/etc/init$ sudo service all-my-workers start

А потом просто висит там и больше не подсказывает. Как я уже сказал, я могу Ctrl-C выйти и увидеть бегущих рабочих:

vagrant@vagrant-service:/etc/init$ sudo service all-my-workers status
all-my-workers start/running
vagrant@vagrant-service:/etc/init$ sudo service my-worker status N=1
my-worker (1) start/running, process 21938

И в ps:

worker    21938  0.0  0.1   4392   612 ?        Ss   21:46   0:00 /bin/sh -e /proc/self/fd/9
worker    21941  0.2  7.3 174076 27616 ?        Sl   21:46   0:00 python /var/lib/my-system/script/start_worker.py

Я не думаю, что проблема в my-worker.conf, но на всякий случай:

description "my-worker"

stop on stopping all-my-workers

setuid worker
setgid worker

respawn

instance $N

console log

env SCRIPT_PATH="/var/lib/my-system/script/"

script
    export PROVIDER=vagrant
    export REGION=all
    export ENVIRONMENT=cert

    . /var/lib/my-system/.virtualenvs/my-system/bin/activate

    python $SCRIPT_PATH/start_worker.py

    END
end script

Огромное спасибо!


person JDiPierro    schedule 23.01.2015    source источник


Ответы (1)


Как это исправить?

Я предполагаю, что my-worker — это долгоживущий процесс, и вы хотите иметь какой-нибудь простой способ раскрутить и разорвать несколько параллельных экземпляров my-worker.

Если это так, вы, вероятно, не хотите, чтобы all-my-workers было task. Вместо этого вам нужно следующее:

description "all-my-workers"

start on runlevel [2345]

console log

env NUM_INSTANCES=1
env STARTING_PORT=42002

pre-start script
    for i in `seq 1 $NUM_INSTANCES`;
    do
        start my-worker N=$i PORT=$(($STARTING_PORT + $i))
    done
end script

pre-stop script
    for i in `seq 1 $NUM_INSTANCES`;
    do
        stop my-worker N=$i PORT=$(($STARTING_PORT + $i)) || true
    done
end script

Затем вы можете запустить start all-my-workers, чтобы запустить все экземпляры my-worker, а затем запустить stop all-my-workers, чтобы остановить их. По сути, all-my-workers становится родительским заданием, которое управляет запуском и остановкой своих дочерних заданий.

Почему?

Вы процитировали два ответа SO, демонстрирующие эту идею родительского задания, управляющего дочерними заданиями. Они показывают:

  1. задача со строфой script
  2. задание со строфой pre-start

Ваша родительская работа – это задача, состоящая из pre-start строфы, и именно поэтому вы сталкиваетесь с таким странным поведением.

сценарий против предварительного запуска

Из этого ответа Ask Ubuntu, в котором цитируется эта устаревшая документация содержит два очень важных утверждения (выделение добавлено):

Все файлы заданий должны иметь раздел exec или script. Это указывает, что будет выполняться для задания.

Дополнительный код оболочки может быть задан для запуска до или после двоичного файла или сценария, указанного с помощью exec или сценария. От них не ожидается, что они начнут процесс, на самом деле они не могут этого сделать. Они предназначены для подготовки среды и последующей очистки.

Таким образом, любые фоновые процессы, порожденные разделом pre-start, игнорируются (т. е. не отслеживаются) Upstart. Вместо этого вы должны использовать exec или script, чтобы запустить процесс, за которым будет следить Upstart.

Что произойдет, если вы пропустите строфу exec/script? Upstart будет сидеть и ждать запуска процесса. Таким образом, вы могли бы также написать цикл while-true:

script
    while true; do
        true
    done
end script

Единственное отличие состоит в том, что цикл while-true представляет собой активную блокировку, тогда как пустая строфа приводит к взаимоблокировке.

Работа против задач

Зная вышеизложенное, документация Upstart по задачам, наконец, приводит нас к тому, что происходит:

Без ключевого слова «задача» события, вызывающие запуск задания, будут разблокированы, как только задание будет запущено. Это означает, что задание сгенерировало событие start(7), запустило свой предварительный запуск, запустило свой сценарий/исполнение и пост-старт, а также сгенерировало свое событие start(7).

С задачей события, которые приводят к запуску этого задания, будут заблокированы до тех пор, пока задание полностью не перейдет обратно в состояние остановлено. Это означает, что задание дошло до ранее упомянутого события start(7), а также завершило свою пост-остановку и выдало свое событие stop(7).

(Некоторые особенности событий и состояний станут более понятными, если вы прочитаете документацию о запуск и остановка заданий).

Проще говоря:

  • Ожидается, что при обычном задании Upstart строфа exec/script будет блокироваться на неопределенный срок, поскольку она запускает долгоживущий процесс. Таким образом, Upstart прекращает блокировку после завершения строфы pre-start.
  • Ожидается, что с task строфа exec/script будет заблокирована на «конечный» период, потому что она запускает недолговечный процесс. Таким образом, Ubstart блокируется до после завершения строфы exec/script.

Но что произойдет, если нет exec/script строфы? Upstart сидит и бесконечно ждет, пока что-то будет запущено, но это никогда не произойдет.

  • В случае job это нормально, потому что Upstart не блокируется в ожидании запуска процесса, а вызова stop, по-видимому, достаточно, чтобы остановить ожидание.
  • Однако в случае с task Upstart просто будет сидеть и зависать вечно — или пока вы его не прервете. Однако, поскольку он до сих пор не нашел порожденный процесс, технически он все еще работает. Вот почему вы можете запросить статус после прерывания и увидеть all-my-workers start/running.

Ради интереса

Если по какой-то причине вы действительно хотите превратить родительское задание в задачу, вам фактически понадобятся две задачи: одна для запуска экземпляров my-worker и одна для их остановки. Вам также потребуется удалить строфу stop on stopping all-my-workers из my-worker.

запустить-все-мои-работники:

description "starts all-my-workers"

start on runlevel [2345]

task

console log

env NUM_INSTANCES=1
env STARTING_PORT=42002

script
    for i in `seq 1 $NUM_INSTANCES`;
    do
        start my-worker N=$i PORT=$(($STARTING_PORT + $i))
    done
end script

остановить-все-мои-рабочие:

description "stops all-my-workers"

start on runlevel [!2345]

task

console log

env NUM_INSTANCES=1
env STARTING_PORT=42002

script
    for i in `seq 1 $NUM_INSTANCES`;
    do
        stop my-worker N=$i PORT=$(($STARTING_PORT + $i)) || true
    done
end script
person cyfur01    schedule 30.01.2015
comment
Большое спасибо! Это, безусловно, лучший ответ, который я когда-либо получал на SO :) Очень информативно! - person JDiPierro; 02.02.2015