Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How make a python windows service of a flask/gevent.socketio server?

I have a flask/gevent SocketIOServer and need to make it work as a service:

class TeleportService(win32serviceutil.ServiceFramework):
    _svc_name_ = "TeleportServer"
    _svc_display_name_ = "Teleport Database Backup Service"
    _svc_description_ = "More info at www.elmalabarista.com/teleport"

    def __init__(self, args):
        win32serviceutil.ServiceFramework.__init__(self, args)
        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)

    def SvcStop(self):
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.hWaitStop)

    def SvcDoRun(self):
        servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
                              servicemanager.PYS_SERVICE_STARTED, (self._svc_name_, ''))

        self.ReportServiceStatus(win32service.SERVICE_RUNNING)

        runServer()


@werkzeug.serving.run_with_reloader
def runServer():
    print 'Listening on %s...' % WEB_PORT
    ws = SocketIOServer(('0.0.0.0', WEB_PORT),
        SharedDataMiddleware(app, {}),
        resource="socket.io",
        policy_server=False)

    gevent.spawn(runTaskManager).link_exception(lambda *args: sys.exit("important_greenlet died"))

    ws.serve_forever()

However, I can't figure how stop it from SvcStop, and running it have the weird behaviour that the service parsing of command line parameters happend AFTER the runserver is killed. This mean the flask server run, I can acces from the web browser but the Service Manager listed it as "Not Started". For example, running in the command line:

C:\Proyectos\TeleportServer>python service.py uninstall <--BAD PARAM, TO MAKE IT OBVIOUS
2013-02-13 16:19:30,786 - DEBUG: Connecting to localhost:9097
 * Restarting with reloader
2013-02-13 16:19:32,650 - DEBUG: Connecting to localhost:9097
Listening on 5000...
Growl not available: Teleport Backup Server is started
KeyboardInterrupt <--- HERE I INTERRUPT WITH CTRL-C
Unknown command - 'uninstall'
Usage: 'service.py [options] install|update|remove|start [...]|stop|restart [...
]|debug [...]'
Options for 'install' and 'update' commands only:
 --username domain\username : The Username the service is to run under
 --password password : The password for the username
 --startup [manual|auto|disabled] : How the service starts, default = manual
 --interactive : Allow the service to interact with the desktop.
 --perfmonini file: .ini file to use for registering performance monitor data

With the suggestion of remove the live reloader, this is the code left. Still, same problem

def SvcDoRun(self): servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,servicemanager.PYS_SERVICE_STARTED,(self._svc_name_, ''))

#self.timeout = 640000    #640 seconds / 10 minutes (value is in milliseconds)
self.timeout = 6000     #120 seconds / 2 minutes
# This is how long the service will wait to run / refresh itself (see script below)
notify.debug("Starting service")

ws = getServer()

while 1:
    # Wait for service stop signal, if I timeout, loop again
    gevent.sleep(0)
    rc = win32event.WaitForSingleObject(self.hWaitStop, self.timeout)
    # Check to see if self.hWaitStop happened
    if rc == win32event.WAIT_OBJECT_0:
        # Stop signal encountered
        notify.debug("Stopping service")
        ws.kill()
        servicemanager.LogInfoMsg("TeleportService - STOPPED!")  #For Event Log
        break
    else:
        notify.debug("Starting web server")
        ws.serve_forever()
like image 549
mamcx Avatar asked Feb 13 '13 21:02

mamcx


1 Answers

To stop it from SvcStop you need to store a reference to "ws" in a global variable (that is, somewhere where it can be retrieved later on). AFAIK "ws.kill()" should then end the loop.

The run_with_reloader decorator appears to run the decorated function immediately, which would explain why the command-line is processed after running the web server. Do you need auto reloading, appearently the decorator only needed when you need reloading.

UPDATE: added example service code

In a project not using flask or gevent I use something like this (with lots of details removed):

class Service (win32serviceutil.ServiceFramework):

   def __init__(self, *args, **kwds):
       self._mainloop = None
       win32serviceutil.ServiceFramework.__init__(self, *args, **kwds)

   def SvcStop(self):
       self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)

       if self._mainloop is not None:
           self._mainloop.shutdown()


    def SvcStart(self):
        self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
        self._mainloop = ... .MainLoop()
        self.ReportServiceStatus(win32service.SERVICE_RUNNING)
        try:
            self._mainloop.run_forever()

        finally:
            self.ReportServiceStatus(win32service.SERVICE_STOPPED)

win32serviceutil.HandleCommandLine(Service)
like image 147
Ronald Oussoren Avatar answered Sep 23 '22 19:09

Ronald Oussoren