Search code examples
pythonpython-multithreading

Python trouble killing main thread from daemons


I have a simple Python class. It starts two daemon threads and under certain conditions one of the daemon threads tries to stop all processes, by signaling the main thread using:

_thread.interrupt_main(SIGKILL)

and

_thread.interrupt_main(SIGKILL)

but I get an exception.

If I use SIGSTOP or SIGKILL, I get these errors respectively:

OSError: Signal 17 ignored due to race condition OSError: Signal 9 ignored due to race condition

If I use SIGINT, I do not get an error. The entire application shuts down, which is what I want but it takes 4-5 seconds. I'd rather it happen immediately, but also I want to learn what is going on here.


Solution

  • This is a simulated signal. When you register a signal handler with python, it is stored in a table. Python has a low level C signal function that is registered with the system and then forwards the signal to the python function via this table after obtaining the GIL.

    _thread.interrupt_main() uses this table directly. It does not actually send the signal to the process or try to call a raw C signal handler. You get this message if a handler has not been registered with python. The odd message is because low level signal handlers can be registered and changed at will, not under python's control and could change while python is figuring its own stuff out. That's the source of the "race condition".

    More fundamentally, SIGSTOP and SIGKILL are not under the program's control. Python's signal subsystem doesn't like uncontrolled termination as a design philosophy. So its not going work in a simulated interrupt scenario either.

    Instead, you could use os.getpid() and os.kill() to have the operating system send the kill signal.

    import threading
    import time
    import os
    import signal
    
    def worker(delay):
        print("sleep", delay)
        time.sleep(delay)
        print("kill after", delay)
        os.kill(os.getpid(), signal.SIGKILL)
    
    workers = 2
    threads = [threading.Thread(target=worker, args=(i,), daemon=True)
            for i in range(1, workers+1)]
    for t in threads:
        t.start()
        
    time.sleep(workers + 3)
    print("oops, didn't work")