alibaba/spring-ai-alibaba · error · RuntimeException
Failed to acquire lock for thread:
Error message
Failed to acquire lock for thread:
What it means
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.
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)
Example fix
// before
saver.put(config, checkpoint); // may throw lock timeout under concurrency
// after
boolean done = false;
for (int i = 0; i < 3 && !done; i++) {
try { saver.put(config, checkpoint); done = true; }
catch (RuntimeException e) {
if (!e.getMessage().startsWith("Failed to acquire lock")) throw e;
Thread.sleep(200L * (i + 1));
}
} Defensive patterns
Strategy: retry
Validate before calling
// no pre-call check possible; reduce contention by ensuring a single writer per threadName // e.g. assert !writerInFlightFor(threadId)
Try / catch
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); } } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- redisson cannot be null
- DeleteMCPServerError
- Failed to delete experiment
- Failed to deserialize checkpoints
- Failed to retrieve item from Redis-like storage
AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09).
Data as JSON: /api/errors/1def2d5862f072bf.
Report an issue: GitHub.
Appendix: source
Thrown at spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/redis/RedisSaver.java:325
}
}
}
@Override
public RunnableConfig put(RunnableConfig config, Checkpoint checkpoint) throws Exception {
Optional<String> threadNameOpt = config.threadId();
if (!threadNameOpt.isPresent()) {
throw new IllegalArgumentException("threadId isn't allow null");
}
String threadName = threadNameOpt.get();
RLock lock = redisson.getLock(LOCK_PREFIX + threadName);
boolean tryLock = false;
try {
// 3 seconds timeout for write operations (put) - longer timeout for concurrent scenarios
tryLock = lock.tryLock(3, TimeUnit.SECONDS);
if (!tryLock) {
throw new RuntimeException("Failed to acquire lock for thread: " + threadName);
}
// Get or create thread_id
String threadId = getOrCreateThreadId(threadName);
// Use thread_id as key for checkpoint storage
String contentKey = CHECKPOINT_PREFIX + threadId;
LinkedList<Checkpoint> checkpoints = deserializeCheckpoints(contentKey);
if (config.checkPointId().isPresent()) {
// Replace Checkpoint
String checkPointId = config.checkPointId().get();
int index = IntStream.range(0, checkpoints.size())
.filter(i -> checkpoints.get(i).getId().equals(checkPointId))
.findFirst()
.orElseThrow(() -> new NoSuchElementException(
format("Checkpoint with id %s not found!", checkPointId)));
checkpoints.set(index, checkpoint);View on GitHub (pinned to f82da0b50f)