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?
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.