[Tutor] UPDATE: Is there a 'hook' to capture all exits from a python program?

Peter Otten __peter__ at web.de
Sat Mar 21 09:19:42 CET 2015


Alan Gauld wrote:

> On 20/03/15 09:37, Peter Otten wrote:
> 
>>> def close_relay(e=None,v=None,t=None):
>>>      try:
>>>         if not relay_closed()
>>>            really_close_relay()
>>>      except:
>>>         really_close_relay()
> 
> The purpose of the if clause is to ensure that
> if the function is called many times you only
> close the relay once (I surmised that more than
> once could be harmful?)
> 
>>> import sys, atexit
>>> atexit.register(close_relay)
>>> sys.excepthook = close_relay
> 
> atexit should be overkill, but it might be needed
> if for some reason the interpreter dies while
> performing the usual cleanup.
> 
> excepthook replaces the usual exception mechanism
> with a clean up. This is needed for cases where an
> exception occurs before getting to the finally. We
> want to close the relay ASAP, not waiting till
> the interpreter decides to call the finally.
> 
>>> try:
>>>      main program here
>>> finally:
>>>      close_relay()
> 
> This is the happy path where everything shuts down as expected.
> 
>> That reeks of cargo cult. Are there actual scenarios for each of the
>> three mechanisms where it is the only one that works?
> 
> In real-time you never trust anything.
> Always cover your back.
> 
>> I would expect that
>>
>> try:
>>      main program here
>> finally:
>>      close_relay()
>>
>> provides the same level of confidence,
> 
> Only if the interpreter is behaving as normal.
> The hooks are to try (we hope) to catch cases where
> the interpreter has broken its normal flow.
> 
> So the scenarios are:
> 
> 1) an unexpected exception occurs - close the relay ASAP.
>     - Use excepthook
> 
> 2) The interpreter gets sent a kill or similar unexpected
> termination - use atexit because finally may not get
> called. (I'm not sure, so belt n' braces here)
> (BTW Does anyone know what the interpreter does when
> suspending - Ctrl-Z in Unix land?)
> 
> 3) Normal program exit. Use the finally clause.
> 
> But its only ever going to be a best endeavour, that's
> why Python is not suitable for true real-time/critical apps...
> But I'd never trust any environment to its usual behaviour
> if there is a possibility of something being broken.
> In this case the relay and its battery pack.
> 
>> the program closes normally or the main code raises an exception, but not
>> if the process is killed.
> 
> What's not clear in the Python  documentation is how Python responds
> to a kill(or suspend). I'd hope the atexit got called even in a kill.
> I would not expect the finally to be executed.
> 
> Of course, if its a seg fault you are probably stuffed either way...

I ran a few experiments:

$ cat bnb.py 
import atexit
import os
import signal
import sys
import time

def handle_except(*args):
    print("except", args, flush=True)

def handle_exit():
    print("exit", flush=True)

def register_signalhandler(sig):
    def handler(*args):
        print("receiving signal", sig, args, flush=True)
    signal.signal(sig, handler)

def main():
    print("Hello from", os.getpid())
    while True:
        print(".", flush=True, end="")
        time.sleep(1)

sys.excepthook = handle_except
atexit.register(handle_exit)

for sig in sys.argv[1:]:
    register_signalhandler(getattr(signal, sig))

try:
    main()
finally:
    print("finally", flush=True)
$ python3 bnb.py 
Hello from 32578
....^Cfinally
except (<class 'KeyboardInterrupt'>, KeyboardInterrupt(), <traceback object 
at 0x7ff97b001bc8>)
exit

When there is no signal handler all three mechanisms work, in the order

- finally
- except hook
- exit handler

Now let's kill:

$ python3 bnb.py 
Hello from 32584
.............Terminated

None of the three are invoked. Let's install a signal handler for SIGTERM:

$ python3 bnb.py SIGTERM
Hello from 32593
.................receiving signal 15 (15, <frame object at 0x7f818e0bb648>)
...............^Cfinally
except (<class 'KeyboardInterrupt'>, KeyboardInterrupt(), <traceback object 
at 0x7f818cdc6bc8>)
exit

The signal is intercepted (and ignored by the no-op handler thus the 
additional Ctrl-C). If we raise a SystemExit in the handler

- finally
- exit handler

will be invoked, but not the except hook.

$ kill -9

of course cannot be intercepted.

My conclusions: 
- If finally does not work nothing does.
- Signal handlers increase safety

Bonus:

$ python3 bnb.py SIGTSTP
Hello from 32614
........^Zreceiving signal 20 (20, <frame object at 0x7f2f8a897648>)
........^Cfinally
except (<class 'KeyboardInterrupt'>, KeyboardInterrupt(), <traceback object 
at 0x7f2f895a2bc8>)
exit

So Ctrl-Z can be intercepted. The program could put the relay into a safe 
state before it suspends.



More information about the Tutor mailing list