Используя pywin32 и gevent, я создаю службу Windows, которая выполняет две функции:
- Он запускает веб-сервер для простого веб-приложения (используя адаптер сервера gevent для бутылки, который запускает serve_forever() WSGIServer).
- Он прослушивает входящие SIP-вызовы (используя SIP-клиент на основе gevent) и запускает простой код для ответа на вызовы.
Я хочу, чтобы служба просто поддерживала работу веб-сервера и SIP-клиента вечно, но но останавливалась немедленно и корректно, если я попытаюсь остановить службу Windows. Кажется, это должно быть довольно просто.
То, что я сейчас делаю для запуска приложения, в основном запускает веб-сервер и SIP-клиент каждый в гринлете и запускает kill на обоих гринлетах, когда я хочу остановить приложение (упрощенный макет):
from bottle import run
from mysipclient import SipClient
import gevent
def sip_listen():
client = SipClient()
try:
client.wait() # This method blocks on a gevent queue.get call
finally:
client.close() # This does some cleanup, like deregistering from the SIP server, that I really want to run when the service stops!
class App(object):
def start(self):
self.stop_event = gevent.event.Event()
self.server_greenlet = gevent.spawn(run, server='gevent', host='0.0.0.0', port=8080)
self.sip_greenlet = gevent.spawn(sip_listen)
gevent.joinall([self.server_greenlet, self.sip_greenlet])
def stop(self):
self.server_greenlet.kill()
self.sip_greenlet.kill()
if __name__ == "__main__":
app = App()
gevent.spawn_later(10, app.stop)
app.start()
Если я запускаю это из командной строки, оно отлично работает: оно запускает приложение с обоими работающими гринлетами, затем через десять секунд оно выключается, запуская код очистки для SIP-клиента и все такое.
Однако теперь я пытаюсь превратить это в службу Windows, используя win32serviceutil pywin32:
import win32serviceutil
import win32service
import win32event
import servicemanager
from app import App
class TestService(win32serviceutil.ServiceFramework):
_svc_name_ = 'TestService'
_svc_display_name_ = 'TestService'
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.service_obj = App()
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
self.service_obj.stop()
def SvcDoRun(self):
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED, (self._svc_name_, ''))
self.service_obj.start()
if __name__ == '__main__':
if len(sys.argv) == 1:
servicemanager.Initialize()
servicemanager.PrepareToHostSingle(TestService)
servicemanager.StartServiceCtrlDispatcher()
else:
win32serviceutil.HandleCommandLine(TestService)
Когда я устанавливаю это как службу и запускаю ее, я получаю исключение при попытке убить гринлеты: LoopExit: This operation would block forever
. Затем служба не останавливается, и мне приходится убивать ее вручную. (Я могу избежать этого, перехватив исключение, используя событие вместо присоединения к гринлетам и установив событие на остановку, но это означает, что очистка вообще не выполняется.)
Я новичок как в gevent, так и в работе со службами Windows, и Google не очень помог. Я подумал, может быть, разница была в том, как с версией командной строки я запускал стоп в другом гринлете, поэтому я попытался заменить self.service_obj.stop()
в SvcStop
на gevent.spawn(self.service_obj.stop).join()
, но таким образом он даже не выдает исключение и просто полностью зависает, пока я не убью процесс.
Что тут происходит? Я делаю что-то принципиально неправильное? Как изящно остановить гринлеты на SvcStop?