views:

224

answers:

3

I am writing a daemon program using python 2.5. In the main process an exit handler is registered with atexit module, it seems that the handler gets called when each child process ends, which is not I expected.

I noticed this behavior isn't mentioned in python atexit doc, anybody knows the issue? If this is how it should behave, how can I unregister the exit handler in children processes? There is a atexit.unregister in version 3.0, but I am using 2.5.

+1  A: 

atexit.register() basically registers your function in atexit._exithandlers, which is a module private list of functions called by sys.exitfunc(). You can set exitfunc() to your custom made exit handler function, which then checks for child status or simply unregisters it. What about just copying the 3.0 atexit.py to your local source tree and using that instead?

EDIT: I copied the atexit.py from my 2.6 version and extended it by

def unregister(func, *targs, **kargs):
    _exithandlers.remove((func, targs, kargs))

If you take that instead of your original version it should work. I have not tested it with subprocesses, though.

wr
There is no atext.py in 3.0, atexit module in 3.0 is written in C.
btw0
Right, but you can also take atexit.py from the 2.x versions
wr
+2  A: 

When you fork to make a child process, that child is an exact copy of the parent -- including of course registered exit functions as well as all other code and data structures. I believe that's the issue you're observing -- of course it's not mentioned in each and every module, because it necessarily applies to every single one.

Alex Martelli
Absolutely, I tend to forget basic CS facts in practice, thank you for reminding of this.
btw0
+1  A: 

There isn't an API to do it in Python 2.5, but you can just:

import atexit
atexit._exithandlers = []

in your child processes - if you know you only have one exit handler installed, and that no other handlers are installed. However, be aware that some parts of the stdlib (e.g. logging) register atexit handlers. To avoid trampling on them, you could try:

my_handler_entries = [e for e in atexit._exithandlers if e[0] == my_handler_func]
for e in my_handler_entries:
    atexit._exithandlers.remove(e)

where my_handler_func is the atexit handler you registered, and this should remove your entry without removing the others.

Vinay Sajip
+1: Better solution than mine, since we really get access to _exithandlers. Forgot that __all__ is only used when "from atexit import *".
wr