refactor: VIP 单飞锁改 asyncio.Lock,去掉每请求一次线程跳转

web/routers/account.py 的音质探测单飞锁原本是 core.state 里的 threading.Lock,
在协程里用 `await asyncio.to_thread(lock.acquire, True, 0.5)` 做有界等待,每次
VIP 请求都白付一次线程池调度。

为什么可以直接换:这把锁**只在协程里用**。原注释写「与 core.state 中同一把锁
共享」,但 grep 全仓只有 _detect_vip 一个消费者(sync 时代另有一个线程侧使用者,
早已不存在)。改为 asyncio.Lock + asyncio.wait_for(acquire(), 0.5),超时分支用
`except TimeoutError` 走「不等 8 级探测、直接用账号接口降级」的原有语义。

顺带把锁从 core.state 移出:core.state 自己声明「依赖仅限标准库
threading/collections」,而这把锁是 asyncio 的。现在它跟着唯一消费者走。

锁按事件循环隔离(以 id(loop) 为键):asyncio.Lock 绑定创建它的 loop,应用主 loop
与测试各自的 loop 并存,跨 loop 复用会抛 "bound to a different event loop"
(B2 迁移 auth_system 时踩过同一个坑)。

门禁:ruff 全过 / pyright 0-0-0 / pytest 382 项全绿(0 失败)。
This commit is contained in:
snowy-yang committed 2026-09-29 14:57:24 +08:00
1 parent 133beab492
commit 6c7f8f589e
2 files changed
+33 -12

No files matched your search

+3 -1
View File
@@ -92,7 +92,9 @@ GLOBAL_MUSIC_U_CACHE: _TimedValue = {'v': None, 'ts': 0.0}
# 服务器网易云账号昵称缓存(10 分钟):非管理员可见响应中遮蔽真实昵称
SERVER_NICK_CACHE: _NickCache = {'nick': None, 'ts': 0.0}
SERVER_NICK_LOCK = threading.Lock() # 刷新单飞锁,防并发重复打上游
VIP_PROBE_LOCK = threading.Lock() # 音质探测单飞锁(有界等待)
# 注:音质探测的单飞锁(原 VIP_PROBE_LOCK)已移出本模块——本模块声明「依赖仅限
# 标准库 threading/collections」,而那是把 asyncio 锁;且它全仓只有
# web/routers/account.py 一个消费者(sync 时代遗留的「共享」注释已不成立)。
# 全局账号 VIP 探测缓存(5 分钟):probe 一次要打 8 次网易云接口,
# 公网下账号栏高频刷新会连累服务器账号触发风控。