Skip to content

Navigation Menu

Sign in
Appearance settings
Sign up
Appearance settings

Commit a5dbddb

Browse filesBrowse the repository at this point in the historyBrowse files
committed
fix(main): 增加 uvicorn 服务自愈看门狗
1 parent e26e0eb commit a5dbddb
Copy full SHA for a5dbddb

1 file changed

+59-9Lines changed: 59 additions & 9 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎main.py‎

Copy file name to clipboardExpand all lines: main.py
+59-9Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.