Package: xdaemonocle
daemonocle is a library for creating your own Unix-style daemons written in Python. It solves many
problems that other daemon libraries have and provides some really useful features you don’t often
see in other daemons.Table of ContentsInstallationBasic UsageRationaleThe ProblemThe SolutionOther Useful FeaturesThestatusActionSlightly SmarterrestartActionSelf-ReloadShutdown CallbackNon-Detached ModeFile Descriptor HandlingDetailed UsageActionsIntegration with mitsuhiko’s clickBugs, Requests, Questions, etc.InstallationTo install via pip:pip install daemonocleOr download the source code and install manually:git clone https://github.com/jnrbsn/daemonocle.git
cd daemonocle/
python setup.py installBasic UsageHere’s areally reallybasic example:importsysimporttimeimportdaemonocle# This is your daemon. It sleeps, and then sleeps again.defmain():whileTrue:time.sleep(10)if__name__=='__main__':daemon=daemonocle.Daemon(worker=main,pidfile='/var/run/daemonocle_example.pid',)daemon.do_action(sys.argv[1])And here’s the same example with logging and aShutdown Callback:importloggingimportsysimporttimeimportdaemonocledefcb_shutdown(message,code):logging.info('Daemon is stopping')logging.debug(message)defmain():logging.basicConfig(filename='/var/log/daemonocle_example.log',level=logging.DEBUG,format='%(asctime)s[%(levelname)s]%(message)s',)logging.info('Daemon is starting')whileTrue:logging.debug('Still running')time.sleep(10)if__name__=='__main__':daemon=daemonocle.Daemon(worker=main,shutdown_callback=cb_shutdown,pidfile='/var/run/daemonocle_example.pid',)daemon.do_action(sys.argv[1])And here’s what it looks like when you run it:user@host:~$ python example.py start
Starting example.py ... OK
user@host:~$ python example.py status
example.py -- pid: 1234, status: running, uptime: 1m, %cpu: 0.0, %mem: 0.0
user@host:~$ python example.py stop
Stopping example.py ... OK
user@host:~$ cat /var/log/daemonocle_example.log
2014-05-04 12:39:21,090 [INFO] Daemon is starting
2014-05-04 12:39:21,091 [DEBUG] Still running
2014-05-04 12:39:31,091 [DEBUG] Still running
2014-05-04 12:39:41,091 [DEBUG] Still running
2014-05-04 12:39:51,093 [DEBUG] Still running
2014-05-04 12:40:01,094 [DEBUG] Still running
2014-05-04 12:40:07,113 [INFO] Daemon is stopping
2014-05-04 12:40:07,114 [DEBUG] Terminated by SIGTERM (15)For more details, see theDetailed Usagesection below.RationaleIf you think about it, a lot of Unix daemons don’t really know what the hell they’re doing. Have you
ever found yourself in a situation that looked something like this?user@host:~$ sudo example start
starting example ... ok
user@host:~$ ps aux | grep example
user      1234  0.0  0.0   1234  1234 pts/1    S+   12:34   0:00 grep example
user@host:~$ sudo example start
starting example ... ok
user@host:~$ echo $?
0
user@host:~$ tail -f /var/log/example.log
...Or something like this?user@host:~$ sudo example stop
stopping example ... ok
user@host:~$ ps aux | grep example
user       123  0.0  0.0   1234  1234 ?        Ss   00:00   0:00 /usr/local/bin/example
user      1234  0.0  0.0   1234  1234 pts/1    S+   12:34   0:00 grep example
user@host:~$ sudo example stop
stopping example ... ok
user@host:~$ ps aux | grep example
user       123  0.0  0.0   1234  1234 ?        Ss   00:00   0:00 /usr/local/bin/example
user      1240  0.0  0.0   1234  1234 pts/1    S+   12:34   0:00 grep example
user@host:~$ sudo kill -9 123
...Or something like this?user@host:~$ sudo example status
Usage: example {start|stop|restart}
user@host:~$ ps aux | grep example
...These are just a few examples of unnecessarily common problems. It doesn’t have to be this way.Note:You might be thinking, “Why not just write a smarter start/stop shell script wrapper
for your daemon that checks whether or not it actually started, actually stopped, etc.?”
Seriously?It doesn’t have to be this way.I believe daemons should be more self-aware. They
should handle their own problems most of the time, and your start/stop script should only be a
very thin wrapper around your daemon or simply a symlink to your daemon.The ProblemIf you’ve ever dug deep into the nitty-gritty details of how daemonization works, you’re probably
familiar with thestandard “double fork” paradigmfirst introduced
by W. Richard Stevens in the bookAdvanced Programming in the UNIX Environment. One of the problems with the standard way to implement this is that
if the final child dies immediately when it gets around to doing real work, the original parent
process (the one that actually had control of your terminal) is long gone. So all you know is that
the process got forked, but you have no idea if it actually kept running for more than a fraction of
a second. And let’s face it, one of the most likely times for a daemon to die is immediately after
it starts (due to bad configuration, permissions, etc.).The next problem mentioned in the section above is when you try to stop a daemon, it doesn’t
actually stop, and you have no idea that it didn’t actually stop. This happens when a process
doesn’t respond properly to aSIGTERMsignal. It happens more often than it should. The problem
is not necessarily the fact that it didn’t stop. It’s the fact that you didn’tknowthat it didn’t
stop. The start/stop script knows that it successfully sent the signal and so it assumes success.
This also becomes a problem when yourrestartcommand blindly callsstopand thenstart,
because it will try to start a new instance of the daemon before the previous one has exited.These are the biggest problems most daemons have in my opinion. daemonocle solves these problems and
provides many other “fancy” features.The SolutionThe problem with the daemon immediately dying on startup and you not knowing about it is solved by
having the first child (the immediate parent of the final child) sleep for one second and then callos.waitpid(pid, os.WNOHANG)to see if the process is still running. This is what daemonocle
does. So if you’re daemon dies within one second of starting, you’ll know about it.This problem with the daemon not stopping and you not knowing about it is solved by simply waiting
for the process to finish (with a timeout). This is what daemonocle does. (Note: When a timeout
occurs, it doesn’t try to send aSIGKILL. This is not always what you’d want and often not a
good idea.)Other Useful FeaturesBelow are some other useful features that daemononcle provides that you might not find elsewhere.ThestatusActionThere is astatusaction that not only displays whether or not the daemon is running and its
PID, but also the uptime of the daemon and the % CPU and % memory usage of all the processes in the
same process group as the daemon (which are probably its children). So if you have a daemon that
launches mulitple worker processes, thestatusaction will show the % CPU and % memory usage of
all the workers combined.It might look something like this:user@host:~$ python example.py status
example.py -- pid: 1234, status: running, uptime: 12d 3h 4m, %cpu: 12.4, %mem: 4.5Slightly SmarterrestartActionHave you ever tried to restart a daemon only to realize that it’s not actually running? Let me
guess: it just gave you an error and didn’t start the daemon. A lot of the time this is not a
problem, but if you’re trying to restart the daemon in an automated way, it’s more annoying to have
to check if it’s running and do either astartorrestartaccordingly. With daemonocle, if
you try to restart a daemon that’s not running, it will give you a warning saying that it wasn’t
running and then start the daemon. This is often what people expect.Self-ReloadDaemons that use daemonocle have the ability to reload themselves by simply callingdaemon.reload()wheredaemonis yourdaemonocle.Daemoninstance. The execution of the
current daemon halts whereverdaemon.reload()was called, and a new daemon is started up to
replace the current one. From your code’s perspective, it’s pretty much the same as a doing arestartexcept that it’s initiated from within the daemon itself and there’s no signal handling
involved. Here’s a basic example of a daemon that watches a config file and reloads itself when the
config file changes:importosimportsysimporttimeimportdaemonocleclassFileWatcher(object):def__init__(self,filename,daemon):self._filename=filenameself._daemon=daemonself._file_mtime=os.stat(self._filename).st_mtimedeffile_has_changed(self):current_mtime=os.stat(self._filename).st_mtimeifcurrent_mtime!=self._file_mtime:self._file_mtime=current_mtimereturnTruereturnFalsedefwatch(self):whileTrue:ifself.file_has_changed():self._daemon.reload()time.sleep(1)if__name__=='__main__':daemon=daemonocle.Daemon(pidfile='/var/run/daemonocle_example.pid')fw=FileWatcher(filename='/etc/daemonocle_example.conf',daemon=daemon)daemon.worker=fw.watchdaemon.do_action(sys.argv[1])Shutdown CallbackYou may have noticed from theBasic Usagesection above that ashutdown_callbackwas defined.
This function gets called whenever the daemon is shutting down in a catchable way, which should be
most of the time except for aSIGKILLor if your server crashes unexpectedly or loses power or
something like that. This function can be used for doing any sort of cleanup that your daemon needs
to do. Also, if you want to log (to the logger of your choice) the reason for the shutdown and the
intended exit code, you can use themessageandcodearguments that will be passed to your
callback (your callback must take these two arguments).Non-Detached ModeThis is not particularly interesting per se, but it’s worth noting that in non-detached mode, your
daemon will do everything else you’ve configured it to do (i.e.setuid,setgid,chroot,
etc.) except actually detaching from your terminal. So while you’re testing, you can get an
extremely accurate view of how your daemon will behave in the wild. It’s also worth noting that
self-reloading works in non-detached mode, which was a little tricky to figure out initially.File Descriptor HandlingOne of the things that daemons typically do is close all open file descriptors and establish new
ones forSTDIN,STDOUT,STDERRthat just point to/dev/null. This is fine most of
the time, but if your worker is an instance method of a class that opens files in its__init__()method, then you’ll run into problems if you’re not careful. This is also a problem if you’re
importing a module that leaves open files behind. For example, importing therandomstandard library module in Python 3
results in an open file descriptor for/dev/urandom.Since this “feature” of daemons often causes more problems than it solves, and the problems it
causes sometimes have strange side-effects that make it very difficult to troubleshoot, this feature
is optional and disabled by default in daemonocle via theclose_open_filesoption.Detailed UsageThedaemonocle.Daemonclass is the main class for creating a daemon using daemonocle. Here’s the
constructor signature for the class:classdaemonocle.Daemon(worker=None,shutdown_callback=None,prog=None,pidfile=None,detach=True,uid=None,gid=None,workdir='/',chrootdir=None,umask=022,stop_timeout=10,close_open_files=False)And here are descriptions of all the arguments:workerThe function that does all the work for your daemon.shutdown_callbackThis will get called anytime the daemon is shutting down. It should take amessageand acodeargument. The message is a human readable message that explains why the daemon is
shutting down. It might useful to log this message. The code is the exit code with which it
intends to exit. SeeShutdown Callbackfor more details.progThe name of your program to use in output messages. Default:os.path.basename(sys.argv[0])pidfileThe path to a PID file to use. It’s not required to use a PID file, but if you don’t, you won’t
be able to use all the features you might expect. Make sure the user your daemon is running as
has permission to write to the directory this file is in.detachWhether or not to detach from the terminal and go into the background. SeeNon-Detached Modefor more details. Default:TrueuidThe user ID to switch to when the daemon starts. The default is not to switch users.gidThe group ID to switch to when the daemon starts. The default is not to switch groups.workdirThe path to a directory to change to when the daemon starts. Note that a file system cannot be
unmounted if a process has its working directory on that file system. So if you change the
default, be careful about what you change it to. Default:"/"chrootdirThe path to a directory to set as the effective root directory when the daemon starts. The
default is not to do anything.umaskThe file creation mask (“umask”) for the process. Default:022stop_timeoutNumber of seconds to wait for the daemon to stop before throwing an error. Default:10close_open_filesWhether or not to close all open files when the daemon detaches. Default:FalseActionsThe default actions arestart,stop,restart, andstatus. You can get a list of
available actions using thedaemonocle.Daemon.list_actions()method. The recommended way to call
an action is using thedaemonocle.Daemon.do_action(action)method. The string name of an action
is the same as the method name except with dashes in place of underscores.If you want to create your own actions, simply subclassdaemonocle.Daemonand add the@daemonocle.expose_actiondecorator to your action method, and that’s it.Here’s an example:importdaemonocleclassMyDaemon(daemonocle.Daemon):@daemonocle.expose_actiondeffull_status(self):"""Get more detailed status of the daemon."""passThen, if you did the basicdaemon.do_action(sys.argv[1])like in all the examples above, you can
call your action with a command likepython example.pyfull-status.Integration with mitsuhiko’s clickdaemonocle also provides an integration withclick, the “composable
command line utility”. The integration is in the form of a custom command classdaemonocle.cli.DaemonCLIthat you can use in conjunction with the@click.command()decorator
to automatically generate a command line interface with subcommands for all your actions. It also
automatically daemonizes the decorated function. The decorated function becomes the worker, and the
actions are automatically mapped from click to daemonocle.Here’s an example:importtimeimportclickfromdaemonocle.cliimportDaemonCLI@click.command(cls=DaemonCLI,daemon_params={'pidfile':'/var/run/example.pid'})defmain():"""This is my awesome daemon. It pretends to do work in the background."""whileTrue:time.sleep(10)if__name__=='__main__':main()Running this example would look something like this:user@host:~$ python example.py --help
Usage: example.py [<options>] <command> [<args>]...

  This is my awesome daemon. It pretends to do work in the background.

Options:
  --help  Show this message and exit.

Commands:
  start    Start the daemon.
  stop     Stop the daemon.
  restart  Stop then start the daemon.
  status   Get the status of the daemon.
user@host:~$ python example.py start --help
Usage: example.py start [<options>]

  Start the daemon.

Options:
  --debug  Do NOT detach and run in the background.
  --help   Show this message and exit.Thedaemonocle.cli.DaemonCLIclass also accepts adaemon_classargument that can be a
subclass ofdaemonocle.Daemon. It will use your custom class, automatically create subcommands
for any custom actions you’ve defined, and use the docstrings of the action methods as the help text
just like click usually does.This integration is entirely optional. daemonocle doesn’t enforce any sort of argument parsing. You
can use argparse, optparse, or just plainsys.argvif you want.Bugs, Requests, Questions, etc.Please create anissue on GitHub.