{"record":{"id":"012646b10cd8c07a","repo":"TencentCloud/TencentDB-Agent-Memory","slug":"skill-agent-queue-tasks-mutex-wait-timeout-for","errorCode":null,"errorMessage":"[skill-agent-queue] tasks-mutex wait timeout for ${key}","messagePattern":"\\[skill-agent-queue\\] tasks-mutex wait timeout for (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"MemoryCore/src/core/skill/conversation-add/agent-task-queue.ts","lineNumber":337,"sourceCode":"    fn: () => Promise<T>,\n  ): Promise<T> {\n    const key = `mutex:${serializeAgentTuple(tuple)}`;\n    const deadline = Date.now() + opts.waitDeadlineMs;\n    while (true) {\n      const now = Date.now();\n      const cur = this.tasksMutex.get(key);\n      if (!cur || cur.expireAt <= now) {\n        const token = randomUUID();\n        this.tasksMutex.set(key, { token, expireAt: now + opts.lockTtlMs });\n        try {\n          return await fn();\n        } finally {\n          const held = this.tasksMutex.get(key);\n          if (held && held.token === token) this.tasksMutex.delete(key);\n        }\n      }\n      if (Date.now() > deadline) {\n        throw new Error(`[skill-agent-queue] tasks-mutex wait timeout for ${key}`);\n      }\n      await sleep(10);\n    }\n  }\n\n  // ── extract lock ──\n\n  async acquireExtractLock(tuple: AgentTuple, ttlMs: number): Promise<ExtractLockHandle | null> {\n    const key = serializeAgentTuple(tuple);\n    const now = Date.now();\n    const cur = this.extractLocks.get(key);\n    if (cur && cur.expireAt > now) return null;\n    const token = randomUUID();\n    this.extractLocks.set(key, { token, expireAt: now + ttlMs });\n    return { key, token };\n  }\n\n  async renewExtractLock(handle: ExtractLockHandle, ttlMs: number): Promise<boolean> {","sourceCodeStart":319,"sourceCodeEnd":355,"githubUrl":"https://github.com/TencentCloud/TencentDB-Agent-Memory/blob/3efcd317b84146d6a08518ac0f7ee7c8a8d200ec/MemoryCore/src/core/skill/conversation-add/agent-task-queue.ts#L319-L355","documentation":"MemoryCore's in-process skill agent task queue guards per-key task execution with an in-memory mutex map. withTasksMutex busy-waits (polling every 10ms) until the key's lock is free, up to a deadline; if the deadline passes it throws this timeout error. It indicates another task is holding the same mutex key for too long or a holder leaked/never released.","triggerScenarios":"Calling an operation wrapped in withTasksMutex(key) while another concurrent task holds the same key's lock beyond the deadline — e.g. a long-running skill agent task, a deadlock where the current async context re-enters withTasksMutex for the same key, or a holder that threw before its finally-block deleted the entry (stale token mismatch).","commonSituations":"High concurrency on the same conversation/skill key; a stuck async task blocking the queue; test environments with fake timers making sleep(10) never advance while Date.now() does; lock leakage after a crashed holder whose cleanup ran with a different token.","solutions":["Identify what task holds the mutex for the key (log acquisition/release in withTasksMutex) and fix or shorten it","Check for re-entrant calls: the same logical task must not call withTasksMutex with the same key recursively","Increase the wait deadline if the critical section legitimately takes long","Ensure the finally block always deletes the lock for the correct token; guard against token mismatch leaks","Add monitoring/metrics on mutex wait duration to catch contention early"],"exampleFix":"// before: nested lock on same key causes self-deadlock -> timeout\nawait withTasksMutex(key, async () => {\n  await withTasksMutex(key, async () => { /* never runs */ });\n});\n// after: hoist the outer lock, do nested work inside one critical section\nawait withTasksMutex(key, async () => {\n  await doWork();\n  await doMoreWork();\n});","handlingStrategy":"try-catch","validationCode":"// best-effort pre-check for in-process lock contention\nconst deadline = Date.now() + waitMs;\n// wrap the call and classify:\nconst isMutexTimeout = (e: unknown) =>\n  e instanceof Error && e.message.startsWith(\"[skill-agent-queue] tasks-mutex wait timeout\");","typeGuard":"function isTasksMutexTimeout(e: unknown): e is Error & { message: string } {\n  return e instanceof Error && /tasks-mutex wait timeout for /.test(e.message);\n}","tryCatchPattern":"try {\n  await queue.withTasksMutex(key, () => runTask());\n} catch (e) {\n  if (isTasksMutexTimeout(e)) {\n    logger.warn({ key }, \"tasks-mutex contention; retrying after backoff\");\n    await sleep(100 + Math.random() * 200);\n    return retryOnce();\n  }\n  throw e;\n}","preventionTips":["Never nest withTasksMutex calls on the same key","Keep critical sections short; move slow I/O outside the lock","Always release in finally and verify the token matches","Alert on mutex wait durations approaching the deadline"],"tags":["concurrency","mutex","timeout","deadlock"],"backgroundTag":"lock-acquisition-timeout","analyzedSha":"3efcd317b84146d6a08518ac0f7ee7c8a8d200ec","analyzedAt":"2026-09-01T05:44:22.276Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}