Reputation: 92
I want to run a websocket server as windows service. I was able to create a simple echo server by copying and modifying code from the Python corner for creating the service:
https://www.thepythoncorner.com/2018/08/how-to-create-a-windows-service-in-python/
And I used code from here:
https://websockets.readthedocs.io/en/stable/intro.html
to create the websocket echo server.
I can install the service:
python create_service.py install
Start the service:
python create_service.py start
The socket server functions without problem. But when I try to stop the service like so:
python create_service.py stop
and:
python create_service.py remove
I get confirmation
stopping service
and
Service removed
in the console. But in the taskmanager under status, it sais: stop pending. Also the websocket is still responding to the client. So, obviously it hangs.
Eventually I can kill it using taskkill, but something must be wrong. I can see this behavior only with the websocket. Other code like in the original example starts and stopps just fine. So I'm guessing it is related to python's asyncio. Does anyone have experience with this issue or deeper inside as to what is happening here?
'''
SMWinservice
by Davide Mastromatteo
Base class to create winservice in Python
-----------------------------------------
Instructions:
1. Just create a new class that inherits from this base class
2. Define into the new class the variables
_svc_name_ = "nameOfWinservice"
_svc_display_name_ = "name of the Winservice that will be displayed in scm"
_svc_description_ = "description of the Winservice that will be displayed in scm"
3. Override the three main methods:
def start(self) : if you need to do something at the service initialization.
A good idea is to put here the inizialization of the running condition
def stop(self) : if you need to do something just before the service is stopped.
A good idea is to put here the invalidation of the running condition
def main(self) : your actual run loop. Just create a loop based on your running condition
4. Define the entry point of your module calling the method "parse_command_line" of the new class
5. Enjoy
'''
import socket
import win32serviceutil
import servicemanager
import win32event
import win32service
class SMWinservice(win32serviceutil.ServiceFramework):
'''Base class to create winservice in Python'''
_svc_name_ = 'pythonService'
_svc_display_name_ = 'Python Service'
_svc_description_ = 'Python Service Description'
@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()
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):
'''
Override to add logic before the start
eg. running condition
'''
pass
def stop(self):
'''
Override to add logic before the stop
eg. invalidating running condition
'''
pass
def main(self):
'''
Main class to be ovverridden to add logic
'''
pass
# entry point of the module: copy and paste into the new module
# ensuring you are calling the "parse_command_line" of the new created class
if __name__ == '__main__':
SMWinservice.parse_command_line()
import time
import random
from pathlib import Path
from service_base import SMWinservice
import asyncio
import websockets
async def hello(websocket, path):
name = await websocket.recv()
print(f"< {name}")
greeting = f"Hello {name}!"
await websocket.send(greeting)
print(f"> {greeting}")
class MyService(SMWinservice):
_svc_name_ = "_MyService"
_svc_display_name_ = "Winservice Example"
_svc_description_ = "Simple example for a service"
def start(self):
self.isrunning = True
def stop(self):
self.isrunning = False
def main(self):
start_server = websockets.serve(hello, "localhost", 8765)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
if __name__ == '__main__':
MyService.parse_command_line()
import asyncio
import websockets
async def hello():
uri = "ws://localhost:8765"
async with websockets.connect(uri) as websocket:
name = input("What's your name? ")
await websocket.send(name)
print(f"> {name}")
greeting = await websocket.recv()
print(f"< {greeting}")
asyncio.get_event_loop().run_until_complete(hello())
Upvotes: 2
Views: 3047
Reputation: 155226
Your implementation of the stop()
method doesn't do anything other than set a flag that the rest of your code is not checking in any way.
The implementation of stop()
should actually stop the service, in your case by stopping the event loop. This can be accomplished with a call to loop.stop()
, being careful to use the thread-safe API as stop()
is likely invoked from a different thread. For example, you can:
main
to assign self.loop = asyncio.get_event_loop()
, andstop
to call self.loop.call_soon_threadsafe(self.loop.stop)
.Upvotes: 2