fix(meshcore): run BLE scan in dedicated thread to avoid gevent conflict

asyncio.run() called from a gevent-patched Flask thread fails under
gunicorn+gevent. Run the one-shot scan in a ThreadPoolExecutor thread
with its own event loop, matching how AsyncWorker handles it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
James Smith
2026-05-13 20:52:44 +01:00
parent e778efa5b6
commit bf50cb4acd
+18 -6
View File
@@ -416,16 +416,28 @@ class MeshcoreClient:
"""Scan for BLE MeshCore devices; works with or without an active connection."""
if self._worker:
return self._worker.scan_ble_sync()
# No worker yet — run a one-shot scan directly
# No worker — spin up a dedicated thread with its own event loop to avoid
# conflicts with gevent's monkey-patched event loop in the Flask thread.
import asyncio
import concurrent.futures
from utils.meshcore_client import _scan_ble
try:
return asyncio.run(_scan_ble())
except Exception as exc:
logger.warning("BLE scan failed: %s", exc)
return []
def _run() -> list[dict]:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
return loop.run_until_complete(_scan_ble())
finally:
loop.close()
asyncio.set_event_loop(None)
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
try:
return executor.submit(_run).result(timeout=12)
except Exception as exc:
logger.warning("BLE scan failed: %s", exc)
return []
_client: MeshcoreClient | None = None