MrDraw/src/octoprint/daemon.py

175 lines
3.8 KiB
Python

# coding=utf-8
"""
Generic linux daemon base class
Originally from http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/#c35
"""
from __future__ import (print_function, absolute_import)
import sys, os, time, signal
class Daemon:
"""
A generic daemon class.
Usage: subclass the daemon class and override the run() method.
If you want to log the output to someplace different that stdout and stderr,
also override the echo() and error() methods.
"""
def __init__(self, pidfile):
self.pidfile = pidfile
def _daemonize(self):
"""Daemonize class. UNIX double fork mechanism."""
self._double_fork()
self._redirect_io()
# write pidfile
pid = str(os.getpid())
self.set_pid(pid)
def _double_fork(self):
try:
pid = os.fork()
if pid > 0:
# exit first parent
sys.exit(0)
except OSError as err:
self.error("First fork failed: {}".format(str(err)))
sys.exit(1)
# decouple from parent environment
os.chdir('/')
os.setsid()
os.umask(002)
# do second fork
try:
pid = os.fork()
if pid > 0:
# exit from second parent
sys.exit(0)
except OSError as err:
self.error("Second fork failed: {}".format(str(err)))
sys.exit(1)
def _redirect_io(self):
# redirect standard file descriptors
sys.stdout.flush()
sys.stderr.flush()
si = open(os.devnull, 'r')
so = open(os.devnull, 'a+')
se = open(os.devnull, 'a+')
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
def terminated(self):
self.remove_pidfile()
def start(self):
"""Start the daemon."""
# Check for a pidfile to see if the daemon already runs
pid = self.get_pid()
if pid:
self.error("pidfile {} already exist. Is the daemon already running?".format(self.pidfile))
sys.exit(1)
self.echo("Starting daemon...")
# Start the daemon
self._daemonize()
self.run()
def stop(self, check_running=True):
"""Stop the daemon."""
pid = self.get_pid()
if not pid:
if not check_running:
return
self.error("pidfile {} does not exist. Is the daemon really running?".format(self.pidfile))
sys.exit(1)
self.echo("Stopping daemon...")
# Try killing the daemon process
try:
while 1:
os.kill(pid, signal.SIGTERM)
time.sleep(0.1)
except OSError as err:
e = str(err.args)
if e.find("No such process") > 0:
self.remove_pidfile()
else:
self.error(e)
sys.exit(1)
def restart(self):
"""Restart the daemon."""
self.stop(check_running=False)
self.start()
def status(self):
"""Prints the daemon status."""
if self.is_running():
self.echo("Daemon is running")
else:
self.echo("Daemon is not running")
def is_running(self):
"""Check if a process is running under the specified pid."""
pid = self.get_pid()
if pid is None:
return False
try:
os.kill(pid, 0)
except OSError:
try:
self.remove_pidfile()
except:
self.error("Daemon found not running, but could not remove stale pidfile")
return False
else:
return True
def get_pid(self):
"""Get the pid from the pidfile."""
try:
with open(self.pidfile,'r') as pf:
pid = int(pf.read().strip())
except (IOError, ValueError):
pid = None
return pid
def set_pid(self, pid):
"""Write the pid to the pidfile."""
with open(self.pidfile,'w+') as f:
f.write(str(pid) + '\n')
def remove_pidfile(self):
"""Removes the pidfile."""
if os.path.isfile(self.pidfile):
os.remove(self.pidfile)
def run(self):
"""You should override this method when you subclass Daemon.
It will be called after the process has been daemonized by
start() or restart()."""
raise NotImplementedError()
@classmethod
def echo(cls, line):
print(line)
@classmethod
def error(cls, line):
print(line, stream=sys.stderr)