Created
September 3, 2016 06:19
-
-
Save fundor333/aaa0b1b46d1e961d6df4828eb82ec3f6 to your computer and use it in GitHub Desktop.
Python Deamon + Service classes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import atexit | |
import os | |
import signal | |
import sys | |
import time | |
__author__ = 'fundor333' | |
class Daemon: | |
"""A generic daemon class. | |
Usage: subclass the daemon class and override the run() method.""" | |
def __init__(self, pidfile): | |
self.pidfile = pidfile | |
def daemonize(self): | |
try: | |
pid = os.fork() | |
if pid > 0: | |
# exit first parent | |
sys.exit(0) | |
except OSError as err: | |
sys.stderr.write('fork #1 failed: {0}\n'.format(err)) | |
sys.exit(1) | |
# decouple from parent environment | |
os.chdir('/') | |
os.setsid() | |
os.umask(0) | |
# do second fork | |
try: | |
pid = os.fork() | |
if pid > 0: | |
# exit from second parent | |
sys.exit(0) | |
except OSError as err: | |
sys.stderr.write('fork #2 failed: {0}\n'.format(err)) | |
sys.exit(1) | |
# 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()) | |
# write pidfile | |
atexit.register(self.delpid) | |
pid = str(os.getpid()) | |
with open(self.pidfile, 'w+') as f: | |
f.write(pid + '\n') | |
def delpid(self): | |
os.remove(self.pidfile) | |
def start(self): | |
"""Start the daemon.""" | |
# Check for a pidfile to see if the daemon already runs | |
try: | |
with open(self.pidfile, 'r') as pf: | |
pid = int(pf.read().strip()) | |
except IOError: | |
pid = None | |
if pid: | |
message = "pidfile {0} already exist. " + \ | |
"Daemon already running?\n" | |
sys.stderr.write(message.format(self.pidfile)) | |
sys.exit(1) | |
# Start the daemon | |
self.daemonize() | |
self.run() | |
def stop(self): | |
"""Stop the daemon.""" | |
# Get the pid from the pidfile | |
try: | |
with open(self.pidfile, 'r') as pf: | |
pid = int(pf.read().strip()) | |
except IOError: | |
pid = None | |
if not pid: | |
message = "pidfile {0} does not exist. " + \ | |
"Daemon not running?\n" | |
sys.stderr.write(message.format(self.pidfile)) | |
return # not an error in a restart | |
# 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: | |
if os.path.exists(self.pidfile): | |
os.remove(self.pidfile) | |
else: | |
print(str(err.args)) | |
sys.exit(1) | |
def restart(self): | |
"""Restart the daemon.""" | |
self.stop() | |
self.start() | |
def run(self): | |
raise NotImplemented |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from os.path import splitext, abspath | |
from sys import modules | |
import win32api | |
import win32event | |
import win32service | |
import win32serviceutil | |
class Service(win32serviceutil.ServiceFramework): | |
_svc_name_ = '_unNamed' | |
_svc_display_name_ = '_Service Template' | |
def __init__(self, *args): | |
win32serviceutil.ServiceFramework.__init__(self, *args) | |
self.log('init') | |
self.stop_event = win32event.CreateEvent(None, 0, 0, None) | |
def log(self, msg): | |
import servicemanager | |
servicemanager.LogInfoMsg(str(msg)) | |
def sleep(self, sec): | |
win32api.Sleep(sec * 1000, True) | |
def SvcDoRun(self): | |
self.ReportServiceStatus(win32service.SERVICE_START_PENDING) | |
try: | |
self.ReportServiceStatus(win32service.SERVICE_RUNNING) | |
self.log('start') | |
self.start() | |
self.log('wait') | |
win32event.WaitForSingleObject(self.stop_event, win32event.INFINITE) | |
self.log('done') | |
except Exception as x: | |
self.log('Exception : %s' % x) | |
self.SvcStop() | |
def SvcStop(self): | |
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) | |
self.log('stopping') | |
self.stop() | |
self.log('stopped') | |
win32event.SetEvent(self.stop_event) | |
self.ReportServiceStatus(win32service.SERVICE_STOPPED) | |
# to be overridden | |
def start(self): | |
pass | |
# to be overridden | |
def stop(self): | |
pass | |
def run(self, name, display_name=None, stay_alive=True): | |
""" Install and Start (auto) a Service | |
cls : the class (derived from Service) that implement the Service | |
name : Service name | |
display_name : the name displayed in the service manager | |
stay_alive : Service will stop on logout if False | |
""" | |
self._svc_name_ = name | |
self._svc_display_name_ = display_name or name | |
try: | |
module_path = modules[self.__module__].__file__ | |
except AttributeError: | |
# maybe py2exe went by | |
from sys import executable | |
module_path = executable | |
module_file = splitext(abspath(module_path))[0] | |
self._svc_reg_class_ = '%s.%s' % (module_file, self.__name__) | |
if stay_alive: win32api.SetConsoleCtrlHandler(lambda x: True, True) | |
try: | |
win32serviceutil.InstallService( | |
self._svc_reg_class_, | |
self._svc_name_, | |
self._svc_display_name_, | |
startType=win32service.SERVICE_AUTO_START | |
) | |
print('Install ok') | |
win32serviceutil.StartService( | |
self._svc_name_ | |
) | |
print('Start ok') | |
except Exception as x: | |
print(str(x)) | |
from winservice import Service | |
class Test(Service): | |
def start(self): | |
self.runflag = True | |
while self.runflag: | |
self.sleep(10) | |
self.log("I'm alive ...") | |
def stop(self): | |
self.runflag = False | |
self.log("I'm done") | |
startservice(Test, 'aTest', 'Python Service Test') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment