iflytek/astron-agent · error · IllegalStateException

Timed out acquiring distributed lock, please try again later

Error message

Timed out acquiring distributed lock, please try again later

What it means

createOrGetUser uses a Redisson distributed lock (tryLock with 5s wait / 10s lease) to make the check-then-create idempotent across instances. If the lock cannot be acquired within 5 seconds it throws IllegalStateException('Timed out acquiring distributed lock, please try again later') rather than proceeding unsynchronized.

Solutions

  1. Retry the operation with backoff — the error message explicitly says 'try again later' and the user is usually created by the holder shortly after
  2. Reduce time inside the critical section (the findByUid + insert) so the lock is held briefly
  3. Lower contention: deduplicate concurrent requests per uid at the API layer (cache/single-flight)
  4. Check Redis health/latency and confirm lock waitTime (5s) vs DB operation time is a sane ratio

Example fix

// before
try {
    boolean acquired = lock.tryLock(5, 10, TimeUnit.SECONDS);
    if (!acquired) throw new IllegalStateException("Timed out acquiring distributed lock...");
// after
boolean acquired = lock.tryLock(5, 10, TimeUnit.SECONDS);
if (!acquired) {
    // caller may safely retry: holder likely completed creation
    Optional<UserInfo> existing = findByUid(userInfo.getUid());
    if (existing.isPresent()) return existing.get();
    throw new IllegalStateException("Timed out acquiring distributed lock, please try again later");
}
Defensive patterns

Strategy: retry

Try / catch

try {
    return userInfoService.createOrGetUser(userInfo);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("Timed out acquiring distributed lock")) {
        Thread.sleep(200);
        return userInfoService.createOrGetUser(userInfo); // single retry
    }
    throw e;
}

Prevention

When it happens

Trigger: Many concurrent createOrGetUser calls for the same or contended lock keys; a lock holder stuck longer than 5s (slow DB inside the critical section); Redisson lease expiry/rebalance issues; Redis latency or connectivity problems making tryLock slow.

Common situations: Burst of first-login requests for the same new user; DB slow queries inside the locked section pushing hold time near the 10s lease; Redis network hiccups in the cluster; thread dumps showing many threads parked on tryLock.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of iflytek/astron-agent@5e758547a8 (2026-09-12). Data as JSON: /api/errors/77bc23c46adf435b. Report an issue: GitHub.

Appendix: source

Thrown at console/backend/commons/src/main/java/com/iflytek/astron/console/commons/data/impl/UserInfoDataServiceImpl.java:177

        if (userInfo.getUid() == null) {
            throw new IllegalArgumentException("User UID cannot be null");
        }

        // First check: fail fast to avoid unnecessary lock contention
        Optional<UserInfo> existingUser = findByUid(userInfo.getUid());
        if (existingUser.isPresent()) {
            return existingUser.get();
        }

        String lockKey = "user:create:uid:" + userInfo.getUid();
        RLock lock = redissonClient.getLock(lockKey);

        try {
            // Attempt to acquire the lock: wait up to 5s, hold up to 10s
            boolean acquired = lock.tryLock(5, 10, TimeUnit.SECONDS);

            if (!acquired) {
                throw new IllegalStateException("Timed out acquiring distributed lock, please try again later");
            }

            try {
                // Second check: re-validate whether UID exists inside the lock
                Optional<UserInfo> existingUserInLock = findByUid(userInfo.getUid());
                if (existingUserInLock.isPresent()) {
                    return existingUserInLock.get();
                }

                // Set default values
                LocalDateTime now = LocalDateTime.now();
                if (userInfo.getCreateTime() == null) {
                    userInfo.setCreateTime(now);
                }
                if (userInfo.getUpdateTime() == null) {
                    userInfo.setUpdateTime(now);
                }
                if (userInfo.getDeleted() == null) {

View on GitHub (pinned to 5e758547a8)