Proses Python berhenti merespons SIGTERM/SIGINT setelah dimulai ulang

Saya mengalami masalah aneh dengan beberapa proses python yang berjalan menggunakan proses pengawas.

Proses pengawas ditulis dengan python dan merupakan induknya, serta memiliki fungsi bernama start_child(name) yang menggunakan subprocess.Popen untuk membuka proses anak. Objek Popen dicatat sehingga pengawas dapat memantau proses menggunakan poll() dan akhirnya mengakhirinya dengan terminate() bila diperlukan. Jika anak tersebut meninggal secara tidak terduga, pengawas akan memanggil start_child(name) lagi dan mencatat objek Popen yang baru.

Ada 7 proses anak, semuanya juga python. Jika saya menjalankan salah satu turunan secara manual, saya dapat mengirim SIGTERM atau SIGINT menggunakan kill dan mendapatkan hasil yang saya harapkan (proses berakhir).

Namun, ketika dijalankan dari proses pengawas, anak hanya akan berakhir setelah sinyal FIRST. Ketika pengawas memulai ulang anak tersebut, proses anak baru tidak lagi merespons SIGTERM atau SIGINT. Saya tidak tahu apa yang menyebabkan ini.

watchdog.py

class watchdog:
    # <snip> various init stuff

    def start(self):
        self.running = true

        kids = ['app1', 'app2', 'app3', 'app4', 'app5', 'app6', 'app7']
        self.processes = {}

        for kid in kids:
            self.start_child(kid)

        self.thread = threading.Thread(target=self._monitor)
        self.thread.start()

        while self.running:
            time.sleep(10)

    def start_child(self, name):
        try:
            proc = subprocess.Popen(name)
            self.processes[name] = proc
        except:
            print "oh no"
        else:
            print "started child ok"

    def _monitor(self):
        while self.running:
            time.sleep(1)
            if self.running:
                for kid, proc in self.processes.iteritems():
                    if proc.poll() is not None: # process ended
                        self.start_child(kid)

Jadi yang terjadi adalah watchdog.start() meluncurkan semua 7 proses, dan jika saya mengirim proses apa pun SIGTERM, proses itu akan berakhir, dan thread monitor memulainya lagi. Namun, jika saya mengirim proses baru SIGTERM, itu mengabaikannya.

Saya harus dapat terus mengirimkan kill -15 ke proses yang dimulai ulang berulang kali. Mengapa mereka mengabaikannya setelah di-restart?


person gdm    schedule 15.07.2009    source sumber
comment
Jadi sepertinya hal ini disebabkan oleh proses Popening di dalam Thread python. Menurut blogs.gentoo.org/agaffney/2005/03/18/python_sucks , Python menyetel masker sinyal untuk memblokir semua sinyal pada proses yang dimulai dari thread. Apa-apaan ini, Piton? Saya sekarang mencoba menggunakan ctypes untuk memanggil sigprocmask() dan mengatur ulang topeng sinyal agar tidak memblokir.   -  person gdm    schedule 16.07.2009


Jawaban (2)


Seperti yang dijelaskan di sini: http://blogs.gentoo.org/agaffney/2005/03/18/python_sucks , ketika Python membuat thread baru, ia memblokir semua sinyal untuk thread tersebut (dan untuk proses apa pun yang memunculkan thread).

Saya memperbaikinya menggunakan sigprocmask, dipanggil melalui ctypes. Ini mungkin cara yang "benar" atau bukan, tetapi berhasil.

Dalam proses anak, selama __init__:

libc = ctypes.cdll.LoadLibrary("libc.so")
mask = '\x00' * 17 # 16 byte empty mask + null terminator 
libc.sigprocmask(3, mask, None) # '3' on FreeBSD is the value for SIG_SETMASK
person gdm    schedule 15.07.2009
comment
Mencampur dua fork/exec, thread, dan sinyal sulit dilakukan dengan benar. Mencampur ketiganya adalah resep bencana. - person Miles; 16.07.2009
comment
Apakah saya menyebutkan bahwa proses pengawas itu sendiri adalah proses daemon yang bercabang beberapa kali untuk melepaskan diri? Bencana yang lezat. - person gdm; 16.07.2009
comment
sigprocmask() kini dijadwalkan untuk Python 3.2: ‹bugs.python.org/issue8407 - person Martin Carpenter; 25.06.2010
comment
Saya sedang menulis test harness yang menjalankan beberapa contoh server (dengan subprosesnya sendiri) dan menguji interaksinya. Jawaban Anda menyelamatkan saya dari kemampuan melakukannya dengan python murni. - person nflacco; 27.06.2013

Bukankah lebih baik mengembalikan penangan sinyal default dalam Python daripada melalui ctypes? Dalam proses anak Anda, gunakan modul sinyal:

import signal
for sig in range(1, signal.NSIG):
    try:
        signal.signal(sig, signal.SIG_DFL)
    except RuntimeError:
        pass

RuntimeError dimunculkan ketika mencoba mengatur sinyal seperti SIGKILL yang tidak dapat ditangkap.

person mhawke    schedule 16.07.2009
comment
Ini tidak berhasil karena semua sinyal ditutupi. Terlepas dari apa yang Anda lakukan dengan signal.signal(), proses tidak akan pernah menerima sinyal. Saya sebenarnya menggunakan signal.signal() untuk menyetel penangan saya untuk SIGTERM (sehingga saya dapat membersihkannya saat berhenti), tetapi Anda masih perlu menggunakan sigprocmask agar proses dapat melihat SIGTERM. - person gdm; 16.07.2009
comment
@gdm: Maaf soal itu, saya tidak tahu cara melakukan ini dengan Python, jadi memanggil melalui ctype mungkin adalah satu-satunya cara. - person mhawke; 17.07.2009