首页 > 解决方案 > 如何从作为 Windows pywin32 服务运行的烧瓶和女服务员中干净地退出

问题描述

我已经设法拼凑了一个 pywin32 Windows 服务在 pylons 服务员 wsgi 服务器(下)内运行烧瓶的工作演示。一个侄女自给自足的解决方案就是这个想法。

我花了几个小时来审查和测试让女服务员干净地退出的方法(比如这个这个),但到目前为止我能做的最好的是一种自杀式的 SIGINT,它让 Windows 在通过服务控制停止时抱怨“管道已结束”面板,但至少它停止了:-/我猜 pywin32 启动的 pythonservice.exe 不应该终止,只是女服务员踩踏?

老实说,我仍然不确定这是关于女服务员、pywin32 还是只是普通的 python 的问题。我确实感觉答案就在我面前,但现在我完全被难住了。

欢迎任何建议!

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

标签: pythonflaskservicepywin32waitress

解决方案


我找到了一个使用似乎可行的子线程的解决方案。不太确定这是否可能会产生意想不到的后果..

--

更新:

我相信下面的更新版本,将 SystemExit “注入”到女服务员线程中是最好的。我认为你原来很难杀死线程,但是这个打印“线程完成”表示正常关闭。

欢迎指正或改进!

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

推荐阅读