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

  1. Retry the put() with backoff — the lock holder usually releases within seconds
  2. Serialize writes per thread: route all checkpoint writes for a threadName through one writer/queue instead of concurrent writers
  3. Increase lock wait time or set a lock leaseTime so crashed holders' locks expire faster
  4. 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

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


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)