RogerB
RogerB

Reputation: 92

Why is pywin32 service not stopping correctly?

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?

The copied code for the base class (service_base.py):

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

My modified Service creator (create_service.py):

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

The websocket client (client.py):

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

Answers (1)

user4815162342
user4815162342

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:

  • modify main to assign self.loop = asyncio.get_event_loop(), and
  • modify stop to call self.loop.call_soon_threadsafe(self.loop.stop).

Upvotes: 2

Related Questions