@@ -202,6 +202,10 @@ def start_monitor():
202202 start_monitor ()
203203 try :
204204 import uvicorn
205+ import threading
206+ import time
207+ import socket
208+
205209 # Windows 下用 wsproto 处理 WebSocket:基于纯 asyncio 流,规避 ProactorEventLoop
206210 # 在 UDP/传输 socket 上的 WinError 64 异常(默认 websockets 库会冒泡到 asyncio 回调)
207211 run_kwargs = {}
@@ -211,13 +215,59 @@ def start_monitor():
211215 run_kwargs ["ws" ] = "wsproto"
212216 except Exception :
213217 pass
214- # 在 uvicorn 真正运行的 loop 上挂异常处理器(避免 get_event_loop 拿到旧 loop 而失效)
218+
219+ def make_config ():
220+ cfg = uvicorn .Config (app , host = HOST , port = PORT , ** run_kwargs )
221+ return cfg
222+
223+ # 看门狗:监听 socket 因网络变动(WinError 64 等)失效后,uvicorn 不会自动重绑,
224+ # 表现为「端口占用但不再服务」。这里周期性探测本地连通性,发现监听死亡即让当前
225+ # server 退出,由外层循环重建并重新绑定端口,实现自愈。
226+ current_server = {"ref" : None }
227+
228+ def _watchdog ():
229+ while True :
230+ time .sleep (5 )
231+ srv = current_server ["ref" ]
232+ if srv is None :
233+ continue
234+ alive = False
235+ try :
236+ # uvicorn 内部维护的监听 server 列表非空,且本地 TCP 实际可连,才算活着
237+ if getattr (srv , "servers" , None ):
238+ with socket .create_connection (("127.0.0.1" , PORT ), timeout = 2 ):
239+ alive = True
240+ except Exception :
241+ alive = False
242+ if not alive :
243+ try :
244+ srv .should_exit = True
245+ except Exception :
246+ pass
247+
248+ wd = threading .Thread (target = _watchdog , daemon = True )
249+ wd .start ()
250+
251+ # 外层看门狗循环:server.run() 退出(异常或 should_exit)后,重建 server 重新绑定端口。
252+ # 每次重建都会创建全新的事件循环与监听 socket,从而彻底摆脱失效的底层网络名。
253+ restart_delay = 1
254+ while True :
215255 try :
216- loop = asyncio .new_event_loop ()
217- asyncio .set_event_loop (loop )
218- loop .set_exception_handler (_loop_exception_handler )
219- run_kwargs ["loop" ] = loop
220- except Exception :
221- pass
222- uvicorn .run (app , host = HOST , port = PORT , ** run_kwargs )
223- finally :shutdown_nvml ()
256+ asyncio .set_event_loop (None ) # 丢弃可能已损坏的旧 loop
257+ if sys .platform == "win32" :
258+ try :
259+ loop = asyncio .new_event_loop ()
260+ asyncio .set_event_loop (loop )
261+ loop .set_exception_handler (_loop_exception_handler )
262+ except Exception :
263+ pass
264+ server = uvicorn .Server (make_config ())
265+ current_server ["ref" ] = server
266+ server .run ()
267+ except Exception as e :
268+ print (f"[WARN] uvicorn server stopped ({ e } ); restarting in { restart_delay } s..." )
269+ current_server ["ref" ] = None
270+ time .sleep (restart_delay )
271+ restart_delay = min (restart_delay * 2 , 30 )
272+ finally :
273+ shutdown_nvml ()
0 commit comments