mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-05-07 22:05:14 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| abe8390237 |
@@ -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,26 @@ 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. Give a specific,
|
||||
# actionable toast instead of the generic connection error.
|
||||
if isinstance(e, NotImplementedError) and sys.platform == "win32":
|
||||
broadcast_error(
|
||||
"MQTT unavailable — Windows event loop incompatible",
|
||||
"The default Windows event loop (ProactorEventLoop) does "
|
||||
"not support MQTT. Restart with: uv run uvicorn "
|
||||
"app.main:app --loop none",
|
||||
)
|
||||
_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()
|
||||
|
||||
+117
@@ -1,3 +1,120 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Windows event-loop fix for MQTT fanout (aiomqtt / paho-mqtt) compatibility
|
||||
# ---------------------------------------------------------------------------
|
||||
# On Windows, uvicorn's default "auto" loop explicitly creates ProactorEventLoop,
|
||||
# which does NOT implement add_reader()/add_writer() — calls that paho-mqtt
|
||||
# requires internally. Setting the event loop *policy* alone is not enough
|
||||
# because uvicorn's "auto" factory bypasses it.
|
||||
#
|
||||
# The fix: re-exec the current process with "--loop none", which tells uvicorn
|
||||
# to let asyncio.run() create the loop through the standard policy (where we
|
||||
# have just installed WindowsSelectorEventLoopPolicy).
|
||||
#
|
||||
# Guards:
|
||||
# - "--loop" already in argv → we (or the operator) already handled it
|
||||
# - MESHCORE_NO_AUTO_LOOP_ON_WIN32=true → operator opt-out for custom
|
||||
# runners, test harnesses, or other non-uvicorn invocations
|
||||
# ---------------------------------------------------------------------------
|
||||
_win32_needs_reexec = (
|
||||
sys.platform == "win32"
|
||||
and os.environ.get("MESHCORE_NO_AUTO_LOOP_ON_WIN32", "").lower() not in ("true", "1")
|
||||
and "--loop" not in sys.argv
|
||||
)
|
||||
# Skip re-exec when --reload is active: on Windows os.execv spawns a new
|
||||
# process and exits, so the reloader's child dies and a fresh uvicorn
|
||||
# (with its own reloader) starts — creating doubled watchers or a loop.
|
||||
# Also skip if sys.executable is missing (embedded / frozen Python).
|
||||
if _win32_needs_reexec and "--reload" in sys.argv:
|
||||
print(
|
||||
"\n" + "!" * 78 + "\n"
|
||||
" WINDOWS + --reload DETECTED\n" + "!" * 78 + "\n"
|
||||
"\n"
|
||||
" We can't auto-fix the event loop when --reload is active because\n"
|
||||
" the re-exec would fight with uvicorn's reloader process.\n"
|
||||
"\n"
|
||||
" If you need MQTT fanout, add --loop none to your command:\n"
|
||||
"\n"
|
||||
" uv run uvicorn app.main:app --reload \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,
|
||||
)
|
||||
_win32_needs_reexec = False
|
||||
|
||||
if _win32_needs_reexec and not sys.executable:
|
||||
# Embedded or frozen Python — can't re-exec, just warn.
|
||||
_win32_needs_reexec = False
|
||||
|
||||
if _win32_needs_reexec:
|
||||
import asyncio as _asyncio
|
||||
|
||||
_asyncio.set_event_loop_policy(
|
||||
_asyncio.WindowsSelectorEventLoopPolicy() # type: ignore[attr-defined]
|
||||
)
|
||||
|
||||
print(
|
||||
"\n" + "=" * 78 + "\n"
|
||||
" HALLO FRIEND WINDOWS USER <3 WE GOTTA ADJUST THINGS BEFORE YOU STARTUP\n"
|
||||
+ "="
|
||||
* 78
|
||||
+ "\n"
|
||||
"\n"
|
||||
" uvicorn's default event loop on Windows (ProactorEventLoop) is not\n"
|
||||
" compatible with aiomqtt/paho-mqtt, which require add_reader() /\n"
|
||||
" add_writer(). Re-executing with '--loop none' so uvicorn honours\n"
|
||||
" WindowsSelectorEventLoopPolicy and MQTT fanout can function.\n"
|
||||
""
|
||||
" In English: The code we use for MQTT is fussy. We're restarting\n"
|
||||
" the server with the right settings for MQTT to work.\n"
|
||||
"\n"
|
||||
" This may or may not work :) If the app starts up after this without a warning, you're good to go.\n"
|
||||
"\n" + "=" * 78 + "\n",
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
)
|
||||
|
||||
# sys.argv[0] on Windows is typically a .exe console-script launcher
|
||||
# (e.g. .venv\Scripts\uvicorn.exe) which Python can't open as a script.
|
||||
# use "python -m uvicorn" instead, forwarding the original arguments.
|
||||
# yes, this is brittle as all hell.
|
||||
try:
|
||||
os.execv(
|
||||
sys.executable,
|
||||
[sys.executable, "-m", "uvicorn"] + sys.argv[1:] + ["--loop", "none"],
|
||||
)
|
||||
except Exception:
|
||||
# execv failed — fall through and let the app start normally.
|
||||
# MQTT fanout will not work, but everything else will.
|
||||
print(
|
||||
"\n" + "!" * 78 + "\n"
|
||||
" AUTO-RESTART FAILED :<\n" + "!" * 78 + "\n"
|
||||
"\n"
|
||||
" We tried to restart uvicorn with the necessary settings\n"
|
||||
" automatically, but there was a problem with the invocation\n"
|
||||
" (not shocking; this is a fragile system).\n"
|
||||
"\n"
|
||||
" Please rerun RemoteTerm with a command like:\n"
|
||||
"\n"
|
||||
" uv run uvicorn app.main:app \033[1m--loop none\033[0m [... other options ...]\n"
|
||||
"\n"
|
||||
" Setting '--loop none' on uvicorn startup will put you in a good\n"
|
||||
" state for MQTT and bypass this self-repair.\n"
|
||||
"\n"
|
||||
" The server is starting anyway -- everything except MQTT fanout\n"
|
||||
" will work normally. If you want to suppress this attempt, \n"
|
||||
" set the env var MESHCORE_NO_AUTO_LOOP_ON_WIN32=true\n"
|
||||
"\n" + "!" * 78 + "\n",
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
@@ -53,6 +53,9 @@ ignore = [
|
||||
"SIM117", # nested with statements - can be clearer in tests
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"app/main.py" = ["E402"] # imports after Windows event-loop re-exec block
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
known-first-party = ["app"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user