我已经成功地拼凑了一个在 Pylons waitress WSGI 服务器内运行 Flask 的 pywin32 Windows 服务的工作演示(如下)。一个很好的独立解决方案是这个想法......
我花了几个小时审查和测试让女服务员干净地退出的方法(比如this和this),但到目前为止我能做的最好的就是一种自杀式的SIGINT,它让Windows抱怨“管道已结束”当通过服务控制面板停止时,但至少它停止了:-/我猜pywin32启动的pythonservice.exe不应该终止,只是女服务员在踩?
说实话,我仍然不确定这是一个关于 waitress、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()
我找到了一个使用似乎有效的子线程的解决方案。我不太确定这是否会产生意想不到的后果......
我相信下面的更新版本,将 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()