python-daemon pidfile is not created

121 Views Asked by At

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?

1

There are 1 best solutions below

0
Tobias Leupold On BEST ANSWER

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.