Как мне дождаться программы, запущенной в другой оболочке?

Вопрос или проблема

У меня есть программа, которая выполняет большое количество работы (занимает около 4-5 часов), и она запускается с помощью cron, когда становятся доступны все данные, с которыми она работает. Иногда, когда я жду, пока она завершится, я хотел бы, чтобы другая (интерактивная) программа запускалась, когда она завершится. Вызов ожидания выглядит многообещающе, но будет ждать только дочерние процессы.

Я определенно предпочитаю решение ИЗМЕНЕНИЕ #3 (см. ниже).

Если это не в том же терминале, используйте while цикл с условием на ps -p, возвращающим true. Поместите sleep в цикл, чтобы снизить загрузку процессора.

while ps -p <pid> >/dev/null 2>&1
do
   sleep 10
done 

или если ваш UNIX поддерживает /proc (например, HP-UX все еще не поддерживает).

while [[ -d /proc/<pid> ]]
do 
    sleep 10
done

Если вы хотите тайм-аут

timeout=6  # тайм-аут через 1 минуту  
while ((timeout > 0)) && ps -p <pid> >/dev/null 2>&1
do
   sleep 10
   ((timeout -= 1))
done 

ИЗМЕНЕНИЕ #1

Есть другой способ: не используйте cron. Используйте команду batch для накопления ваших задач.

Например, вы можете ежедневно накапливать все ваши задачи. Batch можно настроить для разрешения некоторого параллелизма, так что заблокированная задача не остановит весь стек (это зависит от операционной системы).

ИЗМЕНЕНИЕ #2

Создайте fifo в вашей домашней директории:

$ mkfifo ~/tata

в конце вашей задачи:

echo "сделано" > ~/tata

в начале другой задачи (той, которая ждет):

cat ~/tata 

Это не опрос, это старое доброе блокирующее I/O.

ИЗМЕНЕНИЕ #3

Используя сигналы:

В начале сценария(ев), который(е) ждут:

echo $$ >>~/WeAreStopped
kill -STOP $$

в конце вашей долгой задачи:

if [[ -f ~/WeAreStopped ]] ; then
    xargs kill -CONT < ~/WeAreStopped
    rm ~/WeAreStopped
fi

Вы можете изменить вашу задачу cron, чтобы использовать некоторый флаг.

Вместо

2  2 * * *           /path/my_binary

Вы можете использовать

2  2 * * *           touch /tmp/i_m_running; /path/my_binary; rm /tmp/i_m_running

И просто мониторьте этот файл в сценарии или даже вручную. Если он существует, значит, ваша программа работает; в противном случае можете делать, что хотите.

Пример сценария:

while [[ -f /tmp/i_m_running ]] ; do
   sleep 10 ;
done
launch_whatever_you_want

В случае если вам не нравится использовать sleep, вы можете изменить сценарий и запускать его через cron раз в X минут.

В этом случае пример сценария будет:

[[ -f /tmp/i_m_running ]] && { echo "Слишком рано" ; exit ; }
launch_whatever_you_want

Этот способ немного проще, так как вам не нужно искать PID вашего процесса cron.

Нет возможности для процесса ожидать завершения другого процесса, кроме как родительскому ждать завершения одного из своих дочерних процессов. Если можете, запустите программу через сценарий:

do_large_amount_of_work
start_interactive_program

Если вы не можете этого сделать, например, перед тем, как хотите запустить большое количество работы из задания cron, но интерактивную программу из контекста вашей сессии, тогда сделайте это

do_large_amount_of_work
notify_completion

Существует несколько способов реализовать notify_completion. Некоторые рабочие окружения предоставляют механизм уведомления (Открыть окно на удаленном X дисплее (почему “Невозможно открыть дисплей”)? может быть полезным). Вы также можете создать его, используя уведомления об изменении файла. В Linux механизм уведомления об изменении файла – это inotify.

do_large_amount_of_work
echo $? >/path/to/finished.stamp

Чтобы отреагировать на создание /path/to/finished.stamp:

inotifywait -e close_write -q /path/to/finished.stamp
start_interactive_program

Если вы не можете изменить способ вызова do_large_amount_of_work, но знаете, какой файл он в последний раз изменяет, вы можете использовать тот же механизм, чтобы отреагировать, когда этот файл будет закрыт. Вы также можете реагировать на другие события, такие как переименование файла (см. inotifywait мануал для списка возможностей).

Пусть сценарий, вызываемый заданием cron, запускает пустой сценарий оболочки, в который вы можете вставить последующие задачи, если они вам нужны.

Это очень похоже на подход Жиля.

cronjob-task.sh содержит:

# do_large_amount_of_work

./post-execute.sh

где post-execute.sh обычно пуст, если вы не видите, что вам нужно запустить последующую задачу.

while [ ! -z `pgrep nameoflongprocess` ]
do
  sleep 1
done
start script

pgrep вернет пустую строку, если долгий процесс больше не работает. Предполагается, что в данный момент работает только один такой процесс.

Я использовал tail -f /dev/null --pid=<pid>, чтобы создать процесс в моей текущей оболочке, который ожидает завершения любого PID в системе. Это не доступно повсюду, но это очень удобно там, где оно доступно.

Ответ или решение

Вопрос о том, как дождаться завершения программы, запущенной в другой оболочке, является актуальным для многих ИТ-специалистов, особенно когда речь идет о программном обеспечении, работающем с большим объемом данных. В данной статье мы рассмотрим несколько подходов к решению этой задачи, уделяя особое внимание эффективности и простоте реализации.

Общая информация о проблеме

Если программа запущена через cron, она работает в фоновом режиме и не является дочерним процессом к вашему текущему терминалу. Из-за этого команды типа wait, которые ожидают завершения дочерних процессов, не подойдут. Следовательно, необходимо использовать другие методы, чтобы определить, завершилась ли работа фоновой программы.

Подходы к решению

  1. Использование команды ps
    Один из наиболее распространенных способов – использовать команду ps для проверки статуса процесса. Вы можете создать цикл, который будет периодически проверять, работает ли процесс.

    while ps -p <pid> > /dev/null 2>&1; do
       sleep 10
    done

    В данном фрагменте <pid> заменяется на идентификатор процесса. Этот метод позволяет снизить нагрузку на процессор за счет использования функции sleep.

  2. Проверка наличия каталога в /proc
    Если ваша система поддерживает файловую систему /proc, можно использовать следующий подход:

    while [[ -d /proc/<pid> ]]; do
       sleep 10
    done

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

  3. Использование FIFO (первого входа – первого выхода)
    Создайте именованный канал (FIFO), который будет использоваться для уведомления о завершении работы.

    mkfifo ~/tata

    В конце вашего долгого задания добавьте следующую команду:

    echo "it's done" > ~/tata

    Затем в начале интерактивной программы считывайте данные из FIFO:

    cat ~/tata
  4. Использование сигналов
    Этот метод состоит в том, чтобы останавливать и возобновлять выполнение вашего скрипта с помощью сигналов:

    В начале вашего скрипта добавьте:

    echo $$ >> ~/WeAreStopped
    kill -STOP $$

    В конце долгого задания добавьте:

    if [[ -f ~/WeAreStopped ]]; then
       xargs kill -CONT < ~/WeAreStopped
       rm ~/WeAreStopped
    fi
  5. Мониторинг состояния файла
    Добавьте в ваш cron-задание создание файла-флага, который будет указывать на выполнение программы:

    2 2 * * * touch /tmp/i_m_running; /path/my_binary; rm /tmp/i_m_running

    Затем используйте следующий скрипт для ожидания завершения:

    while [[ -f /tmp/i_m_running ]]; do
       sleep 10
    done
    launch_whatever_you_want
  6. Что делать, если вы не можете изменить запуск задачи?
    Если вы не можете изменить то, как запускается ваша программа, но знаете, какой файл она последней модифицирует, вы можете использовать механизм уведомлений, основываясь на изменениях файла с помощью программы inotify.

    inotifywait -e close_write -q /path/to/finished.stamp
    start_interactive_program

Заключение

Как видно, существует множество способов организовать процесс ожидания завершения фоновой программы. Выбор подхода зависит от ваших конкретных требований и условий работы системы. Самое главное – это обеспечить минимальную нагрузку на систему и обеспечить надежность процесса. Надеемся, что предложенные методы будут вам полезны в вашей работе.

Оцените материал
Добавить комментарий

Капча загружается...