กระบวนการ Python หยุดตอบสนองต่อ SIGTERM / SIGINT หลังจากรีสตาร์ท

ฉันมีปัญหาแปลก ๆ กับกระบวนการหลามบางกระบวนการที่ทำงานโดยใช้กระบวนการเฝ้าระวัง

กระบวนการเฝ้าระวังเขียนด้วยภาษาไพธอนและเป็นพาเรนต์ และมีฟังก์ชันชื่อ start_child(name) ซึ่งใช้ subprocess.Popen เพื่อเปิดกระบวนการลูก ออบเจ็กต์ Popen จะถูกบันทึกเพื่อให้ผู้เฝ้าระวังสามารถตรวจสอบกระบวนการโดยใช้ poll() และท้ายที่สุดจะสิ้นสุดด้วย terminate() เมื่อจำเป็น หากเด็กเสียชีวิตอย่างกะทันหัน สุนัขเฝ้าบ้านจะเรียก start_child(name) อีกครั้งและบันทึกวัตถุ Popen ใหม่

มีกระบวนการย่อยทั้งหมด 7 กระบวนการ ซึ่งทั้งหมดเป็นกระบวนการแบบหลามเช่นกัน หากฉันเรียกใช้รายการย่อยด้วยตนเอง ฉันสามารถส่ง SIGTERM หรือ SIGINT โดยใช้ kill และรับผลลัพธ์ที่ฉันคาดหวัง (กระบวนการสิ้นสุด)

อย่างไรก็ตาม เมื่อเรียกใช้จากกระบวนการเฝ้าระวัง เด็กจะสิ้นสุดหลังจากสัญญาณ FIRST เท่านั้น เมื่อโปรแกรมเฝ้าระวังรีสตาร์ทเด็ก กระบวนการลูกใหม่จะไม่ตอบสนองต่อ SIGTERM หรือ SIGINT อีกต่อไป ฉันไม่รู้ว่าอะไรเป็นสาเหตุของสิ่งนี้

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)

สิ่งที่เกิดขึ้นคือ watchdog.start() เรียกใช้กระบวนการทั้ง 7 กระบวนการ และหากฉันส่งกระบวนการใดๆ SIGTERM กระบวนการนั้นจะสิ้นสุดลง และเธรดของจอภาพจะเริ่มใหม่อีกครั้ง อย่างไรก็ตาม หากฉันส่งกระบวนการใหม่ SIGTERM ก็จะไม่สนใจกระบวนการนั้น

ฉันควรจะสามารถส่ง kill -15 ไปยังกระบวนการที่รีสตาร์ทซ้ำแล้วซ้ำเล่าได้ ทำไมพวกเขาถึงเพิกเฉยหลังจากรีสตาร์ทแล้ว?


person gdm    schedule 15.07.2009    source แหล่งที่มา
comment
ดูเหมือนว่าสิ่งนี้เกิดจากการเปิดกระบวนการภายในเธรดหลาม ตาม blogs.gentoo.org/agaffney/2005/03/18/python_sucks , Python ตั้งค่ามาสก์สัญญาณเพื่อบล็อกสัญญาณทั้งหมดในกระบวนการที่เริ่มต้นจากเธรด อะไรวะ ไพธอน? ตอนนี้ฉันกำลังพยายามใช้ ctypes เพื่อเรียก sigprocmask() และรีเซ็ต signal mask เพื่อไม่ให้บล็อก   -  person gdm    schedule 16.07.2009


คำตอบ (2)


ตามที่อธิบายไว้ที่นี่: http://blogs.gentoo.org/agaffney/2005/03/18/python_sucks เมื่อ Python สร้างเธรดใหม่ มันจะบล็อกสัญญาณทั้งหมดสำหรับเธรดนั้น (และสำหรับกระบวนการใด ๆ ที่เธรดวางไข่)

ฉันแก้ไขสิ่งนี้โดยใช้ sigprocmask เรียกผ่าน ctypes นี่อาจเป็นหรือไม่ใช่วิธีที่ "ถูกต้อง" ในการทำ แต่ก็ใช้ได้ผล

ในกระบวนการลูก ระหว่าง __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
การผสม fork/exec เธรด และสัญญาณใดๆ เข้าด้วยกันเป็นเรื่องยากที่จะแก้ไขให้ถูกต้อง การผสมทั้งสามอย่างเข้าด้วยกันเป็นสูตรสำเร็จของหายนะ - person Miles; 16.07.2009
comment
ฉันเคยบอกไปแล้วหรือเปล่าว่ากระบวนการเฝ้าระวังนั้นเป็นกระบวนการภูตซึ่งแยกหลายครั้งเพื่อแยกตัวมันเอง หายนะ อร่อย - person gdm; 16.07.2009
comment
ขณะนี้ sigprocmask() มีกำหนดการสำหรับ Python 3.2: ‹bugs.python.org/issue8407 - person Martin Carpenter; 25.06.2010
comment
ฉันกำลังเขียนชุดทดสอบที่รันเซิร์ฟเวอร์หลายอินสแตนซ์ (ด้วยกระบวนการย่อยของตัวเอง) และทดสอบการโต้ตอบของพวกเขา คำตอบของคุณช่วยให้ฉันสามารถทำได้ในหลามบริสุทธิ์ - person nflacco; 27.06.2013

จะดีกว่าไหมถ้าคืนค่าตัวจัดการสัญญาณเริ่มต้นภายใน Python แทนที่จะใช้ผ่าน ctypes ในกระบวนการลูกของคุณ ให้ใช้โมดูลสัญญาณ:

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

RuntimeError เกิดขึ้นเมื่อพยายามตั้งค่าสัญญาณ เช่น SIGKILL ซึ่งตรวจไม่พบ

person mhawke    schedule 16.07.2009
comment
การดำเนินการนี้ใช้ไม่ได้เนื่องจากสัญญาณทั้งหมดถูกปกปิด ไม่ว่าคุณจะทำอะไรกับ signal.signal() กระบวนการนี้จะไม่มีวันรับสัญญาณเลย จริงๆ แล้วฉันใช้ signal.signal() เพื่อตั้งค่าตัวจัดการของฉันสำหรับ SIGTERM (เพื่อให้ฉันสามารถล้างข้อมูลได้เมื่อออก) แต่คุณยังต้องใช้ sigprocmask เพื่อให้กระบวนการเห็น SIGTERM - person gdm; 16.07.2009
comment
@gdm: ขออภัยด้วย ฉันไม่รู้วิธีการทำเช่นนี้ใน Python ดังนั้นการโทรออกผ่าน ctype อาจเป็นวิธีเดียว - person mhawke; 17.07.2009