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

3

9

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()
Damoiselle answered 13/2, 2013 at 21:22 Comment(0)
C
2

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)
Carlton answered 26/2, 2013 at 15:37 Comment(2)
I do that and still have the web server running before the service code. I remove the run_with_reloader and return ws.Damoiselle
I must note that despite this code is correct in a general case, not solve the question of make it work in the context of gevent-socketioDamoiselle
E
0

The method serve_forever comes from BaseServer.serve_forever. To stop it, you must call BaseServer.shutdown() or a derivative of it.

In short, you must declare ws in the global scope. Putting this code before your Service class definition is one way to do it.

ws = None

Then change your Service.SvcStop implementation to this :

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

    #Tell the serve_forever() loop to stop and wait until it does. 
    ws.shutdown()

Since ws.shutdown() already waits for the listener to stop, you can get rid of self.hWaitStop, unless you use it somewhere else in your code.

Requires Python 2.6+

Exclusive answered 27/2, 2013 at 21:12 Comment(1)
From where is self._mainloop ?Damoiselle
P
0

I can't access WSGIRequestHandler in Flask outside request, so I use Process.

import win32serviceutil
import win32service
import win32event
import servicemanager
from multiprocessing import Process

from app import app


class Service(win32serviceutil.ServiceFramework):
    _svc_name_ = "TestService"
    _svc_display_name_ = "Test Service"
    _svc_description_ = "Tests Python service framework by receiving and echoing messages over a named pipe"

    def __init__(self, *args):
        super().__init__(*args)

    def SvcStop(self):
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        self.process.terminate()
        self.ReportServiceStatus(win32service.SERVICE_STOPPED)

    def SvcDoRun(self):
        self.process = Process(target=self.main)
        self.process.start()
        self.process.run()

    def main(self):
        app.run()


if __name__ == '__main__':
    win32serviceutil.HandleCommandLine(Service)
Pelota answered 5/8, 2014 at 2:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.