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

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

У меня есть оболочный скрипт, который запускает python.

start.sh

# Настройка окружения
cd ~/foobar/ && \
    python3 -m venv env && \
    source env/bin/activate && \
    pip install -q -r requirements.txt && \
    find . -maxdepth 1 -type f -name "*.py" | xargs pylint

# Запуск скриптов
python ~/foobar/main.py

Если я нажимаю ctrl+c в терминале, python завершается корректно. Однако если я использую pkill -9 start.sh, появится сообщение Killed, но python всё ещё будет работать. Есть ли способ завершить скрипт python, когда вызывающий скрипт прекращает работу?

Интересный вопрос: что должно завершить скрипт Python? Если вы уничтожите процесс bash с помощью SIGKILL, то он определенно ничего не сделает с скриптом Python.

Вы можете запустить фоновый процесс, который регулярно проверяет, существует ли основной процесс bash, и завершает скрипт Python, если его больше нет.

Если вы можете изменить скрипт Python, он может регулярно проверять, ушел ли его родитель (легче всего: проверьте, если PID родителя равен 1) и завершить себя. Таким образом, случайное завершение проверяющего не приведет к проблемам…

Лучшее решение может быть вне обоих скриптов. Вы можете запустить оболочный скрипт как сервис SystemD (обычный или временный, см. man systemd-run) и использовать systemctl для завершения скрипта (т.е. завершения всей cgroup).

Используйте только скрипт python и управляйте им с помощью unit systemd (как сервис)

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

Как завершить процесс Python, запущенный скриптом оболочки, при его завершении?

Если вы столкнулись с проблемой завершения процесса Python, который запущен через скрипт оболочки, и этот процесс продолжает исполняться, даже если сам скрипт был убит (например, с использованием pkill -9 start.sh), то вам стоит рассмотреть несколько методов для решения этой задачи. Ниже приведены рекомендации, которые помогут корректно завершаать процесс Python вместе с его родительским скриптом.

1. Использование сигналов и возможностей оболочки

Когда pkill -9 выполняется, сигнал SIGKILL отправляется, что приводит к немедленному завершению процесса оболочки. Однако при этом дочерние процессы не могут быть завершены. Чтобы гарантировать завершение дочернего процесса Python, необходимо использовать более контролируемые сигналы.

Использование trap:
Вы можете использовать команду trap в вашем start.sh для обработки сигналов и завершения дочерних процессов. Вот пример:

#!/bin/bash

# Установка обработки сигналов
trap 'kill $(jobs -p)' EXIT

# Настройка окружения
cd ~/foobar/ && \
    python3 -m venv env && \
    source env/bin/activate && \
    pip install -q -r requirements.txt && \
    find . -maxdepth 1 -type f -name "*.py" | xargs pylint

# Запуск скрипта
python ~/foobar/main.py &
wait

В этом примере trap 'kill $(jobs -p)' EXIT ловит выходные сигналы и завершает все дочерние процессы (включая Python), когда скрипт оболочки завершает свою работу.

2. Создание функции для мониторинга родительского процесса

Если вы можете отредактировать свой Python-скрипт, то добавьте функцию, которая будет проверять, существует ли родительский процесс. Если он не существует, процесс Python завершает свою работу:

import os
import sys
import time

def parent_alive():
    try:
        while True:
            parent_pid = os.getppid()
            if parent_pid == 1:  # Если PID родителя стал 1, значит, процесс завершен
                print("Parent process terminated. Exiting...")
                sys.exit()
            time.sleep(1)  # Проверяем каждый 1 секунду
    except KeyboardInterrupt:
        print("Terminating...")

if __name__ == "__main__":
    # Запуск функции в отдельном потоке
    import threading
    threading.Thread(target=parent_alive, daemon=True).start()

    # Основная логика вашего скрипта
    print("Running main process...")
    # Например, имитация длинной задачи
    while True:
        time.sleep(1)

3. Использование systemd для управления процессами

Если вы ориентированы на производственную среду, рассмотрите возможность управления вашим скриптом через systemd. Это позволит вам запускать ваш скрипт как службу, что обеспечит облегчённое управление процессами. В случае завершения родительского процесса (службы), все дочерние процессы также будут автоматически завершены.

Создайте файл конфигурации для systemd (например, /etc/systemd/system/my-python-script.service):

[Unit]
Description=My Python Script

[Service]
ExecStart=/path/to/start.sh
WorkingDirectory=/home/user/foobar/
StandardOutput=journal
StandardError=journal
Restart=on-failure

[Install]
WantedBy=multi-user.target

Затем активируйте и запустите службу:

sudo systemctl enable my-python-script.service
sudo systemctl start my-python-script.service

Заключение

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

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

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