Kodi Community Forum

Full Version: Service addons, background tasks in "sync" or "async" context.
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Hello all,

Trying to get this right, but it seems I got something wrong. Any help appreciated! Smile

Background task in "sync" context
Wanting to make a HTTP call every 2 minutes in a Client with urllib3:
import xbmc
import xbmcaddon

from lib.client import Client


SERVICE_FN='API Service'


class Service(xbmc.Monitor):
    def __init__(self):
        super().__init__()
        self.ADDON = xbmcaddon.Addon(id='service.myservice.api-key')
        self._api_key = self.ADDON.getSetting(id='api_key')

    def run(self):
        xbmc.log(f'{SERVICE_FN}: Running, {self._api_key}', xbmc.LOGINFO)
        while not self.abortRequested():
            client = Client("api.somewhere.net")
            resp = client.register(self._api_key)
            if resp:
                xbmc.log('{SERVICE_FN}: Registration OK', xbmc.LOGINFO)
            xbmc.log('{SERVICE_FN}: Sleeping', xbmc.LOGINFO)
            xbmc.sleep(120000)


if __name__ == '__main__':
    xbmc.sleep(2000)
    service = Service()
    try:
        service.run()
    except Exception as e:
        xbmc.log(f'{SERVICE_FN}: Exception {str(e)}', xbmc.LOGFATAL)
    xbmc.sleep(250)
    service.waitForAbort(timeout=2)

My Client in lib.client is not using any asyncio, and is only using xbmc.log() for logging.
What am I missing here? Disabling or stopping the service locks up the GUI probably caused by xbmc.sleep(120000).

I made some tests with an asyncio implementation too, but ran into the same problem.

/x-stride
(2022-03-27, 14:41)x-stride Wrote: [ -> ]Trying to get this right, but it seems I got something wrong. Any help appreciated! Smile

Made an similar asyncio websockets echo client: A bit overkill in some places maybe Smile
import asyncio
import websockets

import xbmc
import xbmcgui


SERVICE_FN = 'Asyncio WS Test'


class Client:
    def __init__(self, url):
        self.url = url
        self.websocket = None
        self.dialog = None
        self.seq = 0

    async def echo(self, message: str = ''):
        self.seq += 1
        xbmc.log(f'{SERVICE_FN}: {self.seq}, Snd: {message}')
        try:
            await self.websocket.send(message)
            self.dialog.notification(f'{SERVICE_FN}: {self.seq}', f'Snd: {message}')
            result = await self.websocket.recv()
            xbmc.log(f'{SERVICE_FN}: {self.seq}, Rec: {result}')
            self.dialog.notification(f'{SERVICE_FN}: {self.seq}', f'Rec: {result}')
        except Exception as e:
            xbmc.log(f'{SERVICE_FN}: Exception {str(e)}')
            self.dialog.notification(f'{SERVICE_FN}: Exception {str(e)}', xbmc.LOGERROR)

    async def __aenter__(self):
        xbmc.log(f'{SERVICE_FN}: Starting')
        self.dialog = xbmcgui.Dialog()
        self.websocket = await websockets.connect(self.url)
        xbmc.log(f'{SERVICE_FN}: Connected')
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.websocket.close()
        del self.dialog
        xbmc.log(f'{SERVICE_FN}: Stopped')


class Service(xbmc.Monitor):
    async def run(self):
        xbmc.log('AVController: run')
        try:
            async with Client("ws://echo.websocket.events/") as client:
                while True:
                    await client.echo('Is there anybody out there?')
                    timer = 10
                    while timer and not self.abortRequested():
                        xbmc.sleep(1000)
                        timer -= 1
                        await asyncio.sleep(0)
        except Exception as e:
            xbmc.log(f'AVController: Exception {str(e)}')
            xbmcgui.Dialog().notification('Heading', str(e))
            await asyncio.sleep(0)
            raise


if __name__ == '__main__':
    xbmc.sleep(2000)
    asyncio.run(
        Service().run()
    )

The async part behaves as expected. Same behaviour when it comes to killing it, updating it, GUI becoming locked.

Needed asyncio websockets so added it.
https://github.com/x-stride/script.module.websockets
https://github.com/x-stride/service.websockets.echo


/x-stride

/x-string
asyncio is currently cannot be used in Kodi due to underlying Python limitation regarding asyncio support in sub-interpreters. Basically, only one addon can run asyncio event loop, other will fail when trying to do this.
See my comment below.
Currently in Kodi you can only use a pure-Python asyncio implementation because of the underlying CPython bug. To be able to use asyncio in Kodi addons you need to apply the following workaround at the beginning of your Python module that uses asyncio:
import sys
sys.modules['_asyncio'] = None