python 2.6.x theading / signals /atexit fail on some versions?

妖精的绣舞 提交于 2019-12-03 08:03:47

The root difference here is actually unrelated to both signals and atexit, but rather a change in the behavior of sys.exit.

Before around 2.6.5, sys.exit (more accurately, SystemExit being caught at the top level) would cause the interpreter to exit; if threads were still running, they'd be terminated, just as with POSIX threads.

Around 2.6.5, the behavior changed: the effect of sys.exit is now essentially the same as returning from the main function of the program. When you do that--in both versions--the interpreter waits for all threads to be joined before exiting.

The relevant change is that Py_Finalize now calls wait_for_thread_shutdown() near the top, where it didn't before.

This behavioral change seems incorrect, primarily because it no longer functions as documented, which is simply: "Exit from Python." The practical effect is no longer to exit from Python, but simply to exit the thread. (As a side note, sys.exit has never exited Python when called from another thread, but that obscure divergance from documented behavior doesn't justify a much bigger one.)

I can see the appeal of the new behavior: rather than two ways to exit the main thread ("exit and wait for threads" and "exit immediately"), there's only one, as sys.exit is essentially identical to simply returning from the top function. However, it's a breaking change and diverges from documented behavior, which far outweighs that.

Because of this change, after sys.exit from the signal handler above, the interpreter sits around waiting for threads to exit and then runs atexit handlers after they do. Since it's the handler itself that tells the threads to exit, the result is a deadlock.

Exiting due to a signal is not the same as exiting from within a signal handler. Catching a signal and exiting with sys.exit is a clean exit, not an exit due to a signal handler. So, yes, I agree that it should run atexit handlers here--at least in principle.

However, there's something tricky about signal handlers: they're completely asynchronous. They can interrupt the program flow at any time, between any VM opcode. Take this code, for example. (Treat this as the same form as your code above; I've omitted code for brevity.)

import threading
lock = threading.Lock()
def test_loop():
    while not terminate:
        print('looping')
        with lock:
             print "Executing synchronized operation"
        time.sleep(1)
    print('stopping thread')

def run():
    while True:
        time.sleep(2)
        with lock:
             print "Executing another synchronized operation"
        print('main')

There's a serious problem here: a signal (eg. ^C) may be received while run() is holding lock. If that happens, your signal handler will be run with the lock still held. It'll then wait for test_loop to exit, and if that thread is waiting for the lock, you'll deadlock.

This is a whole category of problems, and it's why a lot of APIs say not to call them from within signal handlers. Instead, you should set a flag to tell the main thread to shut down at an appropriate time.

do_shutdown = False
def close_handler(signum, frame):
    global do_shutdown
    do_shutdown = True
    print('caught signal')

def run():
    while not do_shutdown:
        ...

My preference is to avoid exiting the program with sys.exit entirely and to explicitly do cleanup at the main exit point (eg. the end of run()), but you can use atexit here if you want.

I'm not sure if this was entirely changed, but this is how I have my atexit done in 2.6.5


atexit.register(goodbye)

def goodbye():
    print "\nStopping..."
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!