Search code examples
pythonpidpython-daemonlockfile

python-daemon pidfile is not created


I implement a Python daemon that uses python-daemon to daemonize.

A stripped down minimal example would be:

import daemon
import daemon.pidfile
import threading
import syslog
import signal

class Runner:
    def run(self):
        syslog.syslog("Running something")

    def scheduleNextRun(self):
        self.run()
        self.timer = threading.Timer(3, self.scheduleNextRun)
        self.timer.start()

    def terminate(self, signum, frame):
        syslog.syslog("Received {}".format(signal.Signals(signum).name))
        if self.timer:
            syslog.syslog("Stopping the timer")
            self.timer.cancel()
        syslog.syslog("Will now terminate")

def setup():
    runner = Runner()
    signal.signal(signal.SIGTERM, runner.terminate)
    signal.signal(signal.SIGINT, runner.terminate)
    runner.scheduleNextRun()

with daemon.DaemonContext(pidfile = daemon.pidfile.PIDLockFile("/var/run/test.pid")):
    setup()

The daemon starts up and writes to syslog, and it also shuts down when receiving SIGTERM. However, no pidfile is created.

I tried different ways to invoke the DaemonContext whilst searching for a solution, but neither lead to a pidfile being created:

Both

...
import lockfile
...
with daemon.DaemonContext(pidfile = lockfile.FileLock("/var/run/test.pid")):
...

and (using pidfile.py from https://github.com/bmhatfield/python-pidfile)

...
from pidfile import PidFile
...
with daemon.DaemonContext(pidfile = PidFile("/var/run/test.pid")):
...

do work, but I never get a pidfile.

What's the correct way to get a pidfile a well-behaved daemon has to create?


Solution

  • Okay, now I know what happens ;-)

    It's as simple as the DaemonContext goes out of scope. The pidfile actually is created, but it's removed again at once.

    I solved this by using a threading.Event:

    The Runner adds an Event in it's __init__ function:

    self.finished = threading.Event()
    

    and sets it in terminate:

    self.finished.set()
    

    and the DaemonContext waits for it:

    runner = Runner()
    
    def setup():
        signal.signal(signal.SIGTERM, runner.terminate)
        signal.signal(signal.SIGINT, runner.terminate)
        runner.scheduleNextRun()
    
    with daemon.DaemonContext(pidfile = daemon.pidfile.PIDLockFile("/var/run/test.pid")):
        setup()
        runner.finished.wait()
    

    This way, the main program stays inside the DaemonContext until the Runner terminates, and the pidfile is there.