fix(integrations): 「接收中」跨进程一致,不再跟着 gunicorn 进程抖动
CI / backend (push) Canceled after 0s

上一版用 `manager.polling()` 判"有没有人在收消息",那是进程内状态:请求落到持有者
回答 true、落到备用进程回答 false,页面上就会每两秒闪一次"已停止"。

改成查数据库里的 advisory lock(`pg_locks`,objsubid=2 的两段式锁),谁抢到租约所有
进程都看得见。`lease_held()` 自己开一条短会话查,不复用请求会话——接口里常在
`await session.commit()` 之后才拼返回值,那时原事务已经关了。
This commit is contained in:
Steven
2026-11-05 23:39:33 +08:00
parent 2170a0664d
commit f6e7616756
3 changed files with 43 additions and 8 deletions
+21
View File
@@ -54,6 +54,27 @@ def site_base() -> str:
return ""
async def lease_held(channel_id: uuid.UUID) -> bool:
"""这个通道现在有没有进程在收消息。
不能看 `manager.polling()`:生产是 gunicorn 多进程,每个进程只知道自己那把锁,
请求落到哪个进程就回哪个答案,页面会在"接收中"和"已停止"之间闪。
advisory lock 是数据库级的,`pg_locks` 里看得见——谁抢到了,所有进程都认。
"""
async with SessionFactory() as session:
result = await session.execute(
sa_text(
"SELECT 1 FROM pg_locks"
" WHERE locktype = 'advisory' AND granted AND objsubid = 2"
" AND classid::bigint = (hashtext(:ns)::bigint & 4294967295)"
" AND objid::bigint = (hashtext(:cid)::bigint & 4294967295)"
" LIMIT 1"
),
{"ns": LEASE_NAMESPACE, "cid": str(channel_id)},
)
return result.first() is not None
async def active_channel_ids() -> list[uuid.UUID]:
"""列出"已扫码登录、该收消息"的通道。