Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variable in Async function not re-evaluated in while-True loop

I made a dummy server to test my websockets application against. It listens to subscription messages, then gives information about those subscriptions through the socket.

The class' subscriptions attribute is empty at initialisation and should fill up as the listen() function receives subscription messages. However, it seems as though self.subscriptions in talk() is never appended to, leaving it stuck in its infinite while-loop and never transmitting messages.

The problem is solved by adding a line await asyncio.sleep(1) after the for-loop. But why? Shouldn't self.subscriptions be re-evaluated every time the for-loop is started?

Code below:

class DummyServer:
    def __init__(self):
        self.subscriptions = []

    def start(self):
        return websockets.serve(self.handle, 'localhost', 8765)

    async def handle(self, websocket, path):
        self.ws = websocket
        listen_task = asyncio.ensure_future(self.listen())
        talk_task = asyncio.ensure_future(self.talk())

        done, pending = await asyncio.wait(
            [listen_task, talk_task],
            return_when=asyncio.FIRST_COMPLETED
        )

        for task in pending:
            task.cancel()

    async def listen(self):
        while True:
            try:
                msg = await self.ws.recv()
                msg = json.loads(msg)
                await self.triage(msg)  # handles subscriptions
            except Exception as e:
                await self.close()
                break

    async def talk(self):
        while True:
            for s in self.subscriptions:
                dummy_data = {
                    'product_id': s
                }
                try:
                    await self.send(json.dumps(dummy_data))
                except Exception as e:
                    await self.close()
                    break

            await asyncio.sleep(1)  # without this line, no message is ever sent
like image 682
bluppfisk Avatar asked Feb 17 '26 12:02

bluppfisk


1 Answers

At the start of your function, subscriptions is empty and the for body is not evaluated. As a result, your coroutine is virtually the same as:

async def talk(self):
    while True:
        pass

The while-loop does not contain a "context switching point", meaning that the asyncio event loop basically hangs there, forever executing the blocking while loop.

Adding await sleep() breaks the magic circle; even await sleep(0) could help.

Clever code should probably make use of asyncio.Condition in combination with self.subscriptions, but this matter goes beyond the scope of your original question.

like image 116
Andrew Svetlov Avatar answered Feb 20 '26 01:02

Andrew Svetlov



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!