python - прекратява дъщерен процес, когато скриптът е извикан от bash

Имам скрипт на Python: zombie.py

from multiprocessing import Process
from time import sleep
import atexit

def foo():
    while True:
        sleep(10)

@atexit.register
def stop_foo():
    p.terminate()
    p.join()

if __name__ == '__main__':
    p = Process(target=foo)
    p.start()

    while True:
        sleep(10)

Когато стартирам това с python zombie.py & и убия родителския процес с kill -2, stop() се извиква правилно и двата процеса се прекратяват.

Сега да предположим, че имам bash скрипт zombie.sh:

#!/bin/sh

python zombie.py &

echo "done"

И стартирам ./zombie.sh от командния ред.

Сега stop() никога не се обажда, когато родителят бъде убит. Ако стартирам kill -2 на родителския процес, нищо не се случва. kill -15 или kill -9 и двете просто убиват родителския процес, но не и дъщерния:

[foo@bar ~]$ ./zombie.sh 
done
[foo@bar ~]$ ps -ef | grep zombie | grep -v grep
foo 27220     1  0 17:57 pts/3    00:00:00 python zombie.py
foo 27221 27220  0 17:57 pts/3    00:00:00 python zombie.py
[foo@bar ~]$ kill -2 27220
[foo@bar ~]$ ps -ef | grep zombie | grep -v grep
foo 27220     1  0 17:57 pts/3    00:00:00 python zombie.py
foo 27221 27220  0 17:57 pts/3    00:00:00 python zombie.py
[foo@bar ~]$ kill 27220
[foo@bar ~]$ ps -ef | grep zombie | grep -v grep
foo 27221     1  0 17:57 pts/3    00:00:00 python zombie.py

какво става тук Как мога да се уверя, че дъщерният процес умира заедно с родителя?


person user545424    schedule 28.02.2014    source източник


Отговори (2)


Нито atexit, нито p.daemon = True наистина ще гарантират, че дъщерният процес ще умре с бащата. Получаването на SIGTERM няма да задейства рутинните процедури atexit.

За да сте сигурни, че детето ще бъде убито след смъртта на баща си, ще трябва да инсталирате сигнален манипулатор в бащата. По този начин можете да реагирате на повечето сигнали (SIGQUIT, SIGINT, SIGHUP, SIGTERM, ...), но не и на SIGKILL; просто няма начин да се реагира на този сигнал от процеса, който го получава.

Инсталирайте манипулатор на сигнали за всички полезни сигнали и в този манипулатор убийте дъщерния процес.

person Alfe    schedule 28.02.2014
comment
Инсталирането на единични манипулатори също няма да го гарантира наистина. В Linux има prctl(), но би било прекалено много. - person jfs; 28.02.2014
comment
Какво имаш предвид под баща? Баш скриптът? Вече инсталирах atexit.register в скрипта на Python, който трябва да отговаря на SIGINT от kill -2. - person user545424; 28.02.2014
comment
Не, бащиният процес, за който споменах, е скриптът на Python, който стартирахте. С помощта на модула subprocess той създава дъщерен процес. - person Alfe; 01.03.2014
comment
И всъщност не, atexit реагира на контролирано излизане от скрипта на Python. SIGINT се улавя от интерпретатора и се превежда в KeyboardInterrupt изключение, което след това се разпространява подредено и води като резултат до контролирано прекратяване на скрипта; по този начин atexit се задейства. Това няма да функционира повече, ако някакъв друг сигнал (който интерпретаторът на Python не обработва това грациозно) прекрати процеса. - person Alfe; 01.03.2014

Актуализация: Това решение не работи за процеси, убити от сигнал.


Вашият дъщерен процес не е зомби. То е живо.

Ако искате дъщерният процес да бъде убит, когато неговият родител излезе нормално, тогава задайте p.daemon = True преди p.start(). От документите:

Когато даден процес излезе, той се опитва да прекрати всичките си демонични дъщерни процеси.

Разглеждайки изходния код, той е ясно, че multiprocessing използва atexit обратно извикване, за да убие своите демонични деца, т.е. няма да работи, ако родителят бъде убит от сигнал. Например:

#!/usr/bin/env python
import logging
import os
import signal
import sys
from multiprocessing import Process, log_to_stderr
from threading import Timer
from time import sleep

def foo():
    while True:
        sleep(1)

if __name__ == '__main__':
    log_to_stderr().setLevel(logging.DEBUG)
    p = Process(target=foo)
    p.daemon = True
    p.start()

    # either kill itself or exit normally in 5 seconds
    if '--kill' in sys.argv:
        Timer(5, os.kill, [os.getpid(), signal.SIGTERM]).start()
    else: # exit normally
        sleep(5)

Изход

$ python kill-orphan.py
[INFO/Process-1] child process calling self.run()
[INFO/MainProcess] process shutting down
[DEBUG/MainProcess] running all "atexit" finalizers with priority >= 0
[INFO/MainProcess] calling terminate() for daemon Process-1
[INFO/MainProcess] calling join() for process Process-1
[DEBUG/MainProcess] running the remaining "atexit" finalizers

Обърнете внимание на реда „извикване на terminate() за демон“.

Изход (с --kill)

$ python kill-orphan.py --kill
[INFO/Process-1] child process calling self.run()

Регистърът показва, че ако родителят бъде убит от сигнал, тогава обратното извикване "atexit" не се извиква (и ps показва, че детето е живо в този случай). Вижте също Multiprocess Daemon Not Terminating on Parent Exit.

person jfs    schedule 28.02.2014
comment
Това не работи на моята система. Пускам Scientific Linux release 6.5 (Carbon). - person user545424; 28.02.2014
comment
@user545424: Не успява и на моята система (Ubuntu). Очаквах да работи за сигнали като SIGTERM. Въпреки това работи за SIGINT (kill -2). - person jfs; 28.02.2014