agent-session-lock.js
1.82 KB
/**
* Agent 级并发互斥锁的共享存储(32 号文档方案 B)。
*
* 从 `routes/agent-chat.js` 抽出来:那边的 `POST /:agentId/chat` 路由用一个进程内
* `Map` 挡「同一个 agent 同时只允许一个活跃会话」,`company.js` 的招聘装配重试逻辑
* (`services/agent-body-filler.js`)现在也要用到同一个判据——如果两条路径各自维护
* 一份锁,各自都以为自己没有并发,实际上会同时往同一个 `SessionPool` 会话发两轮请求。
* `SessionPool.getOrCreate()`(`session-pool.js:160`)只看连接在不在,不看上一次调用有没有
* 跑完,两条路径不共享锁的话,这个坑照样在,只是换了个触发组合。
*
* 纯数据结构,不含业务逻辑——acquire/release/超时判断仍然各自在调用方写,行为跟
* agent-chat.js 原来内联声明时逐字一致,这次只是换了个文件。
*/
const agentChatLocks = new Map(); // agentId → { convId, startedAt }
const AGENT_LOCK_TIMEOUT = 30 * 60 * 1000; // 30 分钟安全阀,与原实现一致
/**
* 40 号文档「打断-排队」用:中止一个 turn 之后,轮询等这把锁真正被清掉
* (即原请求的 handler 走到 finally),而不是前端凭感觉睡一个固定时长。
* 查不到锁本身也算"已清空"——用于 stop 端点在锁已经因为别的原因释放时仍能返回 cleaned:true。
*/
function waitForLockRelease(agentId, budgetMs = 3000, pollMs = 100) {
return new Promise((resolve) => {
const deadline = Date.now() + budgetMs;
const check = () => {
if (!agentChatLocks.has(agentId)) return resolve(true);
if (Date.now() >= deadline) return resolve(false);
setTimeout(check, pollMs);
};
check();
});
}
module.exports = { agentChatLocks, AGENT_LOCK_TIMEOUT, waitForLockRelease };