empicano / aiomqtt

The idiomatic asyncio MQTT client
https://aiomqtt.bo3hm.com
BSD 3-Clause "New" or "Revised" License
427 stars 75 forks source link

Issues with uvicorn on Windows 10 #290

Closed realchandan closed 7 months ago

realchandan commented 7 months ago

I've written a very simple, reproducible script that demonstrates the error I'm facing. If I name the file main.py, I can run the file using python main.py on Windows 10, and no error is thrown. However, if I try running it with uvicorn main:app, it doesn't work.

I'm not sure whether this repository is the right place, or if the uvicorn repo is where I should raise this issue. Can anyone give me any hints on how to get it to work with uvicorn main:app?

import os
import sys

if sys.platform.lower() == "win32" or os.name.lower() == "nt":
    from asyncio import WindowsSelectorEventLoopPolicy, set_event_loop_policy

    set_event_loop_policy(WindowsSelectorEventLoopPolicy())

from aiomqtt import Client
from fastapi import FastAPI

app = FastAPI()

@app.on_event("startup")
async def startup_event():
    async with Client("test.mosquitto.org") as client:
        await client.subscribe("humidity/#")
        async for message in client.messages:
            print(message.payload)

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app=app)
empicano commented 7 months ago

Hi there! Did you already find the section of how to run aiomqtt alongside FastAPI in our docs? 🙂

In your example, the code loops infinitely through the async for message in client.messages: part, which means that FastAPI can't start because the startup never finishes.

realchandan commented 7 months ago

I'm afraid that's not the issue, because If I use create_task, still it throws the same error. I have modified the code -

import os
import sys

if sys.platform.lower() == "win32" or os.name.lower() == "nt":
    from asyncio import WindowsSelectorEventLoopPolicy, set_event_loop_policy

    set_event_loop_policy(WindowsSelectorEventLoopPolicy())

import asyncio
import contextlib

import aiomqtt
import fastapi

async def listen(client):
    async for message in client.messages:
        print(message.payload)

client = None

@contextlib.asynccontextmanager
async def lifespan(app):
    global client
    async with aiomqtt.Client("test.mosquitto.org") as c:
        # Make client globally available
        client = c
        # Listen for MQTT messages in (unawaited) asyncio task
        await client.subscribe("humidity/#")
        loop = asyncio.get_event_loop()
        task = loop.create_task(listen(client))
        yield
        # Cancel the task
        task.cancel()
        # Wait for the task to be cancelled
        try:
            await task
        except asyncio.CancelledError:
            pass

app = fastapi.FastAPI(lifespan=lifespan)

@app.get("/")
async def publish():
    await client.publish("humidity/outside", 0.38)

And when I do uvicorn main:app, I get -

Exception in callback AbstractEventLoop.add_reader(800, <function Cli...002F5A9ECBD80>)
handle: <Handle AbstractEventLoop.add_reader(800, <function Cli...002F5A9ECBD80>)>
Traceback (most recent call last):
  File "C:\Users\Chandan\AppData\Local\Programs\Python\Python311\Lib\asyncio\events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "C:\Users\Chandan\AppData\Local\Programs\Python\Python311\Lib\asyncio\events.py", line 530, in add_reader
    raise NotImplementedError
NotImplementedError
Exception in callback AbstractEventLoop.add_writer(<socket.socke...93.94', 1883)>, <function Cli...002F5A9F398A0>)
handle: <Handle AbstractEventLoop.add_writer(<socket.socke...93.94', 1883)>, <function Cli...002F5A9F398A0>)>
Traceback (most recent call last):
  File "C:\Users\Chandan\AppData\Local\Programs\Python\Python311\Lib\asyncio\events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "C:\Users\Chandan\AppData\Local\Programs\Python\Python311\Lib\asyncio\events.py", line 536, in add_writer
    raise NotImplementedError

Which is weird because I have set the event loop to WindowsSelectorEventLoopPolicy

empicano commented 7 months ago

Ah, I think I understand what you mean now 👍

I haven't tested it, but I suspect what's happening is that in the case where you run uvicorn from the command line, uvicorn creates an event loop before the Windows-specific policy is set. aiomqtt then takes the already running event loop via get_event_loop (where in the case that one doesn't already exists, it would create a new one) and thus ends up with a Windows-incompatible loop.

On the other hand, if you run python main.py the policy is set before uvicorn.run() is executed.

I know that you can pass an existing event loop to uvicorn programmatically, but I'm less sure that that's possible when you invoke it via the command line. If there's no option for that I fear there's no way around running it via python main.py.

Let me know if that helps 🙂

realchandan commented 7 months ago

Ah, Okay! I guess you are right, It has something to do with how uvicorn works, than this library, Anyways I have moved to using WSL now.

empicano commented 7 months ago

I'll close this then 🙂