@@ -233,6 +233,35 @@ def _parse_via_mermaid_py(diagram_content: str) -> str:
233233 return str (e )
234234
235235
236+ def _run_in_daemon_thread (fn , * args ) -> "asyncio.Future" :
237+ """Run fn in a daemon thread and return an awaitable for its result.
238+
239+ Unlike asyncio.to_thread, a thread left hanging after wait_for times out
240+ (mermaid-py's requests.get has no timeout) does not block interpreter exit.
241+ """
242+ import threading
243+
244+ loop = asyncio .get_running_loop ()
245+ fut = loop .create_future ()
246+
247+ def _set (setter , value ):
248+ if not fut .done ():
249+ setter (value )
250+
251+ def _worker ():
252+ try :
253+ setter , value = fut .set_result , fn (* args )
254+ except BaseException as e :
255+ setter , value = fut .set_exception , e
256+ try :
257+ loop .call_soon_threadsafe (_set , setter , value )
258+ except RuntimeError :
259+ pass # loop already closed; nobody is waiting any more
260+
261+ threading .Thread (target = _worker , daemon = True ).start ()
262+ return fut
263+
264+
236265async def validate_single_diagram (diagram_content : str , diagram_num : int , line_start : int ) -> str :
237266 """
238267 Validate a single mermaid diagram.
@@ -258,7 +287,7 @@ async def validate_single_diagram(diagram_content: str, diagram_num: int, line_s
258287 return ""
259288 try :
260289 core_error = await asyncio .wait_for (
261- asyncio . to_thread (_parse_via_mermaid_py , diagram_content ),
290+ _run_in_daemon_thread (_parse_via_mermaid_py , diagram_content ),
262291 timeout = 15.0 ,
263292 )
264293 except asyncio .TimeoutError :
0 commit comments