{"record":{"id":"1def2d5862f072bf","repo":"alibaba/spring-ai-alibaba","slug":"failed-to-acquire-lock-for-thread","errorCode":null,"errorMessage":"Failed to acquire lock for thread: ","messagePattern":"Failed to acquire lock for thread: ","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/redis/RedisSaver.java","lineNumber":325,"sourceCode":"\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic RunnableConfig put(RunnableConfig config, Checkpoint checkpoint) throws Exception {\n\t\tOptional<String> threadNameOpt = config.threadId();\n\t\tif (!threadNameOpt.isPresent()) {\n\t\t\tthrow new IllegalArgumentException(\"threadId isn't allow null\");\n\t\t}\n\n\t\tString threadName = threadNameOpt.get();\n\t\tRLock lock = redisson.getLock(LOCK_PREFIX + threadName);\n\t\tboolean tryLock = false;\n\t\ttry {\n\t\t\t// 3 seconds timeout for write operations (put) - longer timeout for concurrent scenarios\n\t\t\ttryLock = lock.tryLock(3, TimeUnit.SECONDS);\n\t\t\tif (!tryLock) {\n\t\t\t\tthrow new RuntimeException(\"Failed to acquire lock for thread: \" + threadName);\n\t\t\t}\n\n\t\t\t// Get or create thread_id\n\t\t\tString threadId = getOrCreateThreadId(threadName);\n\n\t\t\t// Use thread_id as key for checkpoint storage\n\t\t\tString contentKey = CHECKPOINT_PREFIX + threadId;\n\t\t\tLinkedList<Checkpoint> checkpoints = deserializeCheckpoints(contentKey);\n\n\t\t\tif (config.checkPointId().isPresent()) {\n\t\t\t\t// Replace Checkpoint\n\t\t\t\tString checkPointId = config.checkPointId().get();\n\t\t\t\tint index = IntStream.range(0, checkpoints.size())\n\t\t\t\t\t\t.filter(i -> checkpoints.get(i).getId().equals(checkPointId))\n\t\t\t\t\t\t.findFirst()\n\t\t\t\t\t\t.orElseThrow(() -> new NoSuchElementException(\n\t\t\t\t\t\t\t\tformat(\"Checkpoint with id %s not found!\", checkPointId)));\n\t\t\t\tcheckpoints.set(index, checkpoint);","sourceCodeStart":307,"sourceCodeEnd":343,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/redis/RedisSaver.java#L307-L343","documentation":"RedisSaver.put() throws RuntimeException('Failed to acquire lock for thread: <name>') when lock.tryLock(3, TimeUnit.SECONDS) fails to obtain the Redisson distributed write lock for the thread within 3 seconds. Another client/process currently holds the lock for that threadName, so the checkpoint write is aborted rather than blocked indefinitely.","triggerScenarios":"Concurrent put()/release() calls on the same threadName from multiple threads or application instances that exceed the 3s wait; a previous lock holder crashed without unlocking (leaked lock held until lease expiry); a slow Redis/network making lock grant take longer than 3s.","commonSituations":"Multiple app replicas writing checkpoints for the same conversation thread simultaneously; long GC pauses or Redis latency spikes; stale locks after a JVM kill -9; overloaded Redis causing Redisson lock acquisition to time out.","solutions":["Retry the put() with backoff — the lock holder usually releases within seconds","Serialize writes per thread: route all checkpoint writes for a threadName through one writer/queue instead of concurrent writers","Increase lock wait time or set a lock leaseTime so crashed holders' locks expire faster","Check Redis health/latency and ensure the previous holder's finally-block unlock() runs (no swallowed exceptions)"],"exampleFix":"// before\nsaver.put(config, checkpoint); // may throw lock timeout under concurrency\n// after\nboolean done = false;\nfor (int i = 0; i < 3 && !done; i++) {\n    try { saver.put(config, checkpoint); done = true; }\n    catch (RuntimeException e) {\n        if (!e.getMessage().startsWith(\"Failed to acquire lock\")) throw e;\n        Thread.sleep(200L * (i + 1));\n    }\n}","handlingStrategy":"retry","validationCode":"// no pre-call check possible; reduce contention by ensuring a single writer per threadName\n// e.g. assert !writerInFlightFor(threadId)","typeGuard":null,"tryCatchPattern":"for (int i = 0; i < 3; i++) { try { saver.put(config, cp); break; } catch (RuntimeException e) { if (!String.valueOf(e.getMessage()).startsWith(\"Failed to acquire lock\") || i == 2) throw e; Thread.sleep(200L << i); } }","preventionTips":["Route all writes for a given thread through one owner/queue","Use lock leaseTime so crashed holders' locks expire","Monitor Redis latency; avoid very large checkpoint payloads inside the critical section"],"tags":["redis","distributed-lock","concurrency","redisson"],"backgroundTag":"lock-acquisition-timeout","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}