Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper way to clean up a Python service -- atexit, signal, try/finally

Tags:

python

daemon

I have a single threaded Python 2.7 daemon/service running on Debian. I have some critical cleanup code that disables certain hardware features.

import sys
import atexit
import signal
from my_job import give_high_fives
from my_cleanup import prevent_the_apocalypse

atexit.register(prevent_the_apocalypse)
signal.signal(signal.SIGTERM, prevent_the_apocalypse)
signal.signal(signal.SIGHUP, prevent_the_apocalypse)
try:
    while True:
        give_high_fives()
finally:
    prevent_the_apocalypse()

This looks paranoid, and I also don't like calling the cleanup code so many times. Right now it looks like cleanup is called 3 or 4 times on a SIGTERM.

Is there one single way to prevent_the_apocalypse exactly once in all possible exit conditions?

like image 967
Cuadue Avatar asked Oct 09 '13 17:10

Cuadue


1 Answers

Writing a correct daemon in Python is hard. In fact, it's hard in any language. PEP 3143 explains the issues.

The daemon module wraps up most of the details so you don't have to get them right. If you use that, it becomes very easy to add cleanup code.

One option is to just subclass daemon.DaemonContext and put it there. For example:

class MyDaemonContext(daemon.DaemonContext):
    def close(self):
        if not self.is_open:
            return
        prevent_the_apocalypse()
        super(MyDaemonContext, self).close()

with MyDaemonContext():
    while True:
        give_high_fives()

The daemon module already sets up the signal handlers so that they do what you've configured them to do, but without skipping the close method. (The close will be run exactly once—in the context's __exit__, in an atexit method, or possibly elsewhere, as appropriate.)

If you want something more complicated, where some signals skip close and others don't, instead of subclassing, just set its signal_map appropriately.

like image 162
abarnert Avatar answered Sep 25 '22 00:09

abarnert