From 7d5cfdec26be380a2235b161ab2312063dd9f2d1 Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Tue, 7 Apr 2026 21:53:04 -0700 Subject: [PATCH] Add note about startup on windows --- README.md | 9 +++++++++ app/fanout/mqtt_base.py | 29 +++++++++++++++++++++++++++++ app/main.py | 38 +++++++++++++++++++++++++++++++++++++- 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b760f8..2512b29 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,15 @@ $env:MESHCORE_SERIAL_PORT="COM8" # or your COM port uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 ``` +> [!WARNING] +> **Windows + MQTT fanout:** Python's default Windows event loop (ProactorEventLoop) is not compatible with the MQTT libraries used by RemoteTerm. If you configure any MQTT integration, add `--loop none` to your uvicorn command: +> +> ```powershell +> uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --loop none +> ``` +> +> If you forget, the app will start normally but MQTT connections will fail and you'll see a toast in the UI with this same guidance. + If you enable Basic Auth, protect the app with HTTPS. HTTP Basic credentials are not safe on plain HTTP. Also note that the app's permissive CORS policy is a deliberate trusted-network tradeoff, so cross-origin browser JavaScript is not a reliable way to use that Basic Auth gate. ## Where To Go Next diff --git a/app/fanout/mqtt_base.py b/app/fanout/mqtt_base.py index 467b3b2..656bbf8 100644 --- a/app/fanout/mqtt_base.py +++ b/app/fanout/mqtt_base.py @@ -12,6 +12,7 @@ from __future__ import annotations import asyncio import json import logging +import sys import time from abc import ABC, abstractmethod from typing import Any @@ -252,6 +253,34 @@ class BaseMqttPublisher(ABC): self._client = None self._last_error = _format_error_detail(e) + # Windows ProactorEventLoop does not implement add_reader / + # add_writer, which paho-mqtt requires. The failure can + # surface as a direct NotImplementedError (add_writer in + # __aenter__) or as a generic timeout (add_reader fails + # inside an event-loop callback, so paho never hears back). + # Either way, if we're on Windows with Proactor the root + # cause is the same and retrying won't help. + _on_proactor = ( + sys.platform == "win32" + and type(asyncio.get_event_loop()).__name__ == "ProactorEventLoop" + ) + if _on_proactor: + broadcast_error( + "MQTT unavailable — Windows event loop incompatible", + "The default Windows event loop (ProactorEventLoop) does " + "not support MQTT. Add --loop none to your uvicorn " + "command and restart. See README.md for details.", + ) + _broadcast_health() + logger.error( + "%s cannot run: Windows ProactorEventLoop does not " + "implement add_reader/add_writer required by paho-mqtt. " + "Restart uvicorn with '--loop none' to use " + "SelectorEventLoop instead. Giving up (will not retry).", + self._integration_label(), + ) + return + title, detail = self._on_error() broadcast_error(title, detail) _broadcast_health() diff --git a/app/main.py b/app/main.py index f002fe0..51388a1 100644 --- a/app/main.py +++ b/app/main.py @@ -1,5 +1,41 @@ -import asyncio import logging +import sys + +# --------------------------------------------------------------------------- +# Windows event-loop advisory for MQTT fanout +# --------------------------------------------------------------------------- +# On Windows, uvicorn's default event loop (ProactorEventLoop) does not +# implement add_reader()/add_writer(), which paho-mqtt (via aiomqtt) requires. +# We cannot fix this from inside the app — the loop is already created by the +# time this module is imported. Log a prominent warning so Windows operators +# who want MQTT know to add ``--loop none`` to their uvicorn command. +# --------------------------------------------------------------------------- +if sys.platform == "win32": + import asyncio as _asyncio + + _loop = _asyncio.get_event_loop() + _is_proactor = type(_loop).__name__ == "ProactorEventLoop" + if _is_proactor: + print( + "\n" + "!" * 78 + "\n" + " NOTE FOR WINDOWS USERS\n" + "!" * 78 + "\n" + "\n" + " The running event loop is ProactorEventLoop, which is not\n" + " compatible with MQTT fanout (aiomqtt / paho-mqtt).\n" + "\n" + " If you use MQTT integrations, restart with --loop none:\n" + "\n" + " uv run uvicorn app.main:app \033[1m--loop none\033[0m" + " [... other options ...]\n" + "\n" + " Everything else works fine as-is.\n" + "\n" + "!" * 78 + "\n", + file=sys.stderr, + flush=True, + ) + del _loop, _is_proactor + +import asyncio from contextlib import asynccontextmanager from pathlib import Path