Skip to content

Instantly share code, notes, and snippets.

@costastf
Created April 14, 2018 11:11
Show Gist options
  • Save costastf/497d22b1729cd6cee01991d1fa48b268 to your computer and use it in GitHub Desktop.
Save costastf/497d22b1729cd6cee01991d1fa48b268 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python2.7
# -*- coding: UTF-8 -*-
# File: daemonlib.py
"""An example of a Linux daemon written in Python.
Based on http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
The changes are:
1 - Uses file open context managers instead of calls to file().
2 - Forces stdin to /dev/null. stdout and stderr go to log files.
3 - Uses print instead of sys.stdout.write prior to pointing stdout to the log file.
4 - Omits try/excepts if they only wrap one error message w/ another.
i - http://stackoverflow.com/questions/3263672/python-the-difference-between-sys-stdout-write-and-print
"""
import atexit
import abc
import os
import signal
import sys
import time
# import logging
__author__ = '''Costas Tyfoxylos <costas.tyf@gmail.com>'''
__docformat__ = 'plaintext'
__date__ = '''2015-04-03'''
# This is the main prefix used for logging
# LOGGER_BASENAME = '''daemonlib'''
# LOGGER = logging.getLogger(LOGGER_BASENAME)
# LOGGER.setLevel(logging.DEBUG)
# LOGGER.addHandler(logging.NullHandler())
class Daemon(object):
__metaclass__ = abc.ABCMeta
"""Instantiating the daemon"""
def __init__(self, pid_file=None, stdout=None, stderr=None):
self.stdout = stdout or './daemon_out.log'
self.stderr = stderr or './daemon_err.log'
self.pid_file = pid_file or './daemon.pid'
def _remove_pid(self):
"""Delete the pid file."""
os.remove(self.pid_file)
def _daemonize(self):
"""Double forking of the process"""
# fork 1 to spin off the child that will spawn the deamon.
if os.fork():
sys.exit()
# This is the child.
# 1. clear the session id to clear the controlling TTY.
# 2. set the umask so we have access to all files created by the daemon.
os.setsid()
os.umask(0)
# fork 2 ensures we can't get a controlling ttd.
if os.fork():
sys.exit()
# This is a child that can't ever have a controlling TTY.
# Now we shut down stdin and point stdout/stderr at log files.
# stdin
with open('/dev/null', 'r') as dev_null:
os.dup2(dev_null.fileno(), sys.stdin.fileno())
# stderr - do this before stdout so that errors about setting stdout write to the log file.
#
# Exceptions raised after this point will be written to the log file.
sys.stderr.flush()
with open(self.stderr, 'a+', 0) as stderr:
os.dup2(stderr.fileno(), sys.stderr.fileno())
# stdout
#
# Print statements after this step will not work. Use sys.stdout
# instead.
sys.stdout.flush()
with open(self.stdout, 'a+', 0) as stdout:
os.dup2(stdout.fileno(), sys.stdout.fileno())
# Write pid file
# Before file creation, make sure we'll delete the pid file on exit!
atexit.register(self._remove_pid)
pid = str(os.getpid())
with open(self.pid_file, 'w+') as pid_file:
pid_file.write('{0}'.format(pid))
@property
def pid(self):
"""Return the pid read from the pid file."""
try:
with open(self.pid_file, 'r') as pid_file:
pid = int(pid_file.read().strip())
return pid
except IOError:
return
def start(self):
"""Start the daemon."""
print('Starting...')
if self.pid:
print(('PID file {0} exists. '
'Is the deamon already running?').format(self.pid_file))
sys.exit(1)
self._daemonize()
self.execute()
def stop(self):
"""Stop the daemon."""
print('Stopping...')
if not self.pid:
print(("PID file {0} doesn't exist. "
"Is the daemon not running?").format(self.pid_file))
return
try:
while 1:
os.kill(self.pid, signal.SIGTERM)
time.sleep(0.1)
except OSError as err:
if 'No such process' in err.strerror and \
os.path.exists(self.pid_file):
os.remove(self.pid_file)
else:
print(err)
sys.exit(1)
def restart(self):
"""Restart the deamon."""
self.stop()
self.start()
@abc.abstractmethod
def execute(self):
"""The main loop of the daemon."""
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment