How to exit cleanly from flask and waitress running as a windows pywin32 service

后端 未结 1 1826
北海茫月
北海茫月 2020-12-11 10:52

I have managed to cobble together a working demo of a pywin32 windows service running flask inside the pylons waitress wsgi server (below). A niece self contained solution i

相关标签:
1条回答
  • 2020-12-11 11:14

    I have found a solution using a sub-thread that seems to work. Not quite sure if this may have possible unintended consequences yet..

    --

    update:

    I believe the updated version below, "injecting" a SystemExit into the waitress thread is as good as it gets. I think thee original kills the thread hard, but this one prints "thread done" indicating a graceful shutdown.

    Corrections or improvements welcome!

    import ctypes
    import os
    import random
    import socket
    import threading
    
    from flask import Flask, escape, request
    
    import servicemanager
    import win32event
    import win32service
    import win32serviceutil
    from waitress import serve
    
    app = Flask(__name__)
    
    # waitress thread exit based on:
    # https://www.geeksforgeeks.org/python-different-ways-to-kill-a-thread/
    
    @app.route('/')
    def hello():
        random.seed()
        x = random.randint(1, 1000000)
        name = request.args.get("name", "World")
        return 'Hello, %s! - %s - %s' % (escape(name), x, os.getpid())
    
    
    class ServerThread(threading.Thread):
    
        def __init__(self):
            threading.Thread.__init__(self)
    
        def run(self):
            print('thread start\n')
            serve(app, listen='*:5000')  # blocking
            print('thread done\n')
    
        def get_id(self):
            # returns id of the respective thread
            if hasattr(self, '_thread_id'):
                return self._thread_id
            for id, thread in threading._active.items():
                if thread is self:
                    return id
    
        def exit(self):
            thread_id = self.get_id()
            res = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, ctypes.py_object(SystemExit))
            if res > 1:
                ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
                print('Exception raise failure')
    
    
    class SMWinservice(win32serviceutil.ServiceFramework):
        _svc_name_ = 'WaitressService'
        _svc_display_name_ = 'Waitress server'
        _svc_description_ = 'Python waitress WSGI service'
    
        @classmethod
        def parse_command_line(cls):
            win32serviceutil.HandleCommandLine(cls)
    
        def __init__(self, args):
            win32serviceutil.ServiceFramework.__init__(self, args)
            self.stopEvt = win32event.CreateEvent(None, 0, 0, None)
            socket.setdefaulttimeout(60)
    
        def SvcStop(self):
            servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
                                servicemanager.PYS_SERVICE_STOPPED,
                                (self._svc_name_, ''))
            self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
            win32event.SetEvent(self.stopEvt)
    
        def SvcDoRun(self):
            servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
                                servicemanager.PYS_SERVICE_STARTED,
                                (self._svc_name_, ''))
            self.main()
    
        def main(self):
            print('main start')
            self.server = ServerThread()
            self.server.start()
            print('waiting on win32event')
            win32event.WaitForSingleObject(self.stopEvt, win32event.INFINITE)
            self.server.exit()  # raise SystemExit in inner thread
            print('waiting on thread')
            self.server.join()
            print('main done')
    
    
    if __name__ == '__main__':
        SMWinservice.parse_command_line()
    
    0 讨论(0)
提交回复
热议问题