Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
306 views
in Technique[技术] by (71.8m points)

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

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 is the idea..

I have spent hours reviewing and testing ways of making waitress exit cleanly (like this and this) but the best I can do so far is a kind of suicidal SIGINT which makes Windows complain "the pipe has been ended" when stopping through the Services control panel, but at least it stops :-/ I guess the pythonservice.exe which pywin32 starts, should not terminate, just the waitress treads?

To be honest I'm still uncertain if this is a question about waitress, pywin32, or maybe its just plain python. I do have the feeling the answer is right in front of me but right now I'm completely stumped.

Any suggestions welcome!

import os
import random
import signal
import socket

from flask import Flask, escape, request

import servicemanager
import win32event
import win32service
import win32serviceutil
from waitress import serve

app = Flask(__name__)


@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())


# based on https://www.thepythoncorner.com/2018/08/how-to-create-a-windows-service-in-python/

class SMWinservice(win32serviceutil.ServiceFramework):
    '''Base class to create winservice in Python'''

    _svc_name_ = 'WaitressService'
    _svc_display_name_ = 'Waitress server'
    _svc_description_ = 'Python waitress WSGI service'

    @classmethod
    def parse_command_line(cls):
        '''
        ClassMethod to parse the command line
        '''
        win32serviceutil.HandleCommandLine(cls)

    def __init__(self, args):
        '''
        Constructor of the winservice
        '''
        win32serviceutil.ServiceFramework.__init__(self, args)
        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
        socket.setdefaulttimeout(60)

    def SvcStop(self):
        '''
        Called when the service is asked to stop
        '''
        self.stop()
        servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
                            servicemanager.PYS_SERVICE_STOPPED,
                            (self._svc_name_, ''))
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.hWaitStop)

    def SvcDoRun(self):
        '''
        Called when the service is asked to start
        '''
        self.start()
        servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
                            servicemanager.PYS_SERVICE_STARTED,
                            (self._svc_name_, ''))
        self.main()

    def start(self):
        pass

    def stop(self):
        print 'sigint'
        os.kill(os.getpid(), signal.SIGINT)

    def main(self):
        print 'serve'
        serve(app, listen='*:5000')


if __name__ == '__main__':
    SMWinservice.parse_command_line()
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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
')
        serve(app, listen='*:5000')  # blocking
        print('thread done
')

    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()

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...