mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-05-10 07:15:09 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| abe8390237 |
@@ -12,6 +12,7 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -252,6 +253,26 @@ class BaseMqttPublisher(ABC):
|
|||||||
self._client = None
|
self._client = None
|
||||||
self._last_error = _format_error_detail(e)
|
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()
|
title, detail = self._on_error()
|
||||||
broadcast_error(title, detail)
|
broadcast_error(title, detail)
|
||||||
_broadcast_health()
|
_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 asyncio
|
||||||
import logging
|
import logging
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ ignore = [
|
|||||||
"SIM117", # nested with statements - can be clearer in tests
|
"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]
|
[tool.ruff.lint.isort]
|
||||||
known-first-party = ["app"]
|
known-first-party = ["app"]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user