From bed22edcc1a9b53fae78c1da1e041d14cde26516 Mon Sep 17 00:00:00 2001 From: Harsh Pandhe Date: Wed, 20 May 2026 10:50:57 +0530 Subject: [PATCH] fix(ui): force stdlib asyncio policy so 3-D Twin tab works under uvloop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `make ui` crashed every time it tried to render the 3-D Twin tab: Process Process-1: ... ValueError: Can't patch loop of type uvicorn ships uvloop as a transitive dependency of Streamlit. If the parent Streamlit process's event-loop policy is uvloop's, `multiprocessing.Process` (forked by stpyvista's trame backend to render the 3-D scene) inherits it. The subprocess then calls `asyncio.run` → `nest_asyncio2._patch_loop`, which only knows how to patch stdlib loops, and bails on `uvloop.Loop`. Fix: - Reset `asyncio.set_event_loop_policy(asyncio.DefaultEventLoopPolicy())` at app import time. Streamlit's tornado server is unaffected; the child process now spawns a stdlib loop that nest_asyncio2 can patch. - Wrap the `stpyvista(...)` call in try/except so any future backend crash degrades to a clear warning + an `st.exception` panel rather than a silent subprocess traceback, and the rest of the tab (sliders, compliance badges) stays interactive. No other code paths touched; tests unaffected. Co-Authored-By: Claude Opus 4.7 --- app/streamlit_app.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/app/streamlit_app.py b/app/streamlit_app.py index e2fff89..e4ed6bb 100644 --- a/app/streamlit_app.py +++ b/app/streamlit_app.py @@ -10,8 +10,18 @@ from __future__ import annotations +import asyncio from pathlib import Path +# Force stdlib asyncio policy before any subprocess (stpyvista's trame +# export runs in multiprocessing.Process and inherits the parent's +# policy). uvicorn ships uvloop as a transitive of Streamlit; if the +# parent's policy is uvloop, the 3-D Twin tab's subprocess hits +# `nest_asyncio2._patch_loop` which raises `ValueError: Can't patch loop +# of type `. The fix is to reset the policy at +# import time — Streamlit's tornado server is unaffected. +asyncio.set_event_loop_policy(asyncio.DefaultEventLoopPolicy()) + import numpy as np import streamlit as st @@ -368,7 +378,23 @@ def _badge(ok: bool, label: str, detail: str): patch, edited_alignment, segments, z_exaggeration=float(z_exag), off_screen=True, ) - stpyvista(plotter, key="ropeway_twin") + # Graceful fallback: stpyvista spawns a multiprocessing.Process for + # trame's HTML export. On systems where uvloop is loaded that + # subprocess can still fail (the asyncio policy reset at import + # time covers the common case but not e.g. pre-imported uvloop in + # the parent). Catch any subprocess crash, log it, and offer the + # alignment plot as a fallback so the rest of the tab stays usable. + try: + stpyvista(plotter, key="ropeway_twin") + except Exception as exc: # noqa: BLE001 — surface any backend crash + st.warning( + "3-D Twin renderer crashed in its subprocess " + f"(`{type(exc).__name__}`). Falling back to the 2-D " + "alignment plot. Run `ropeway view3d --dem ... --out twin.html` " + "from a shell to get an interactive 3-D HTML you can open " + "directly in a browser." + ) + st.exception(exc) # Persist edited layout into session for the Projects tab st.session_state["edited_alignment"] = edited_alignment