alibaba/nacos · error · IllegalMonitorStateException

Non-reentrant lock does not allow reentry on the same thread

Error message

Non-reentrant lock does not allow reentry on the same thread, key={}

What it means

Thrown by NacosLock.checkReentrantGuard() when a NON_REENTRANT lock is acquired a second time on a thread that already holds it (localReentrantCount > 0). This is a deliberate client-side guard: without it, the request would reach the server, which would reject and enqueue it, causing self-deadlock. The exception type is IllegalMonitorStateException (not NacosException).

Source

Thrown at client/src/main/java/com/alibaba/nacos/client/lock/NacosLock.java:133

        instance.setLockType(lockType);
        instance.setOwner(currentOwner());
        instance.setExpiredTime(expiredTime);
        return instance;
    }
    
    /**
     * Guard against non-reentrant lock reentry on the same thread.
     *
     * <p>For {@code NON_REENTRANT} locks, if the current thread already holds the lock
     * ({@code localReentrantCount > 0}), a second acquisition attempt is immediately
     * rejected with {@link IllegalMonitorStateException}. Without this client-side check,
     * the request would be sent to the server, which rejects it and places the thread in
     * the wait queue, causing self-deadlock (the thread waits for itself to release the lock).
     */
    private void checkReentrantGuard() {
        if (LockConstants.NON_REENTRANT_LOCK_TYPE.equals(lockType)
            && localReentrantCount.get() > 0) {
            throw new IllegalMonitorStateException(
                "Non-reentrant lock does not allow reentry on the same thread, key=" + key);
        }
    }
    
    @Override
    public void lock() {
        checkReentrantGuard();
        boolean firstAttempt = true;
        while (true) {
            try {
                LockInstance instance = buildInstance(-1);
                instance.setWaitTime(DEFAULT_SERVER_WAIT_TIME_MS);
                if (!firstAttempt) {
                    instance.setWaiterRetry(true);
                }
                grpcClient.registerForNotification(key, currentOwner());
                LockResult result = grpcClient.lockWithResult(instance);
                if (result.isSuccess()) {

View on GitHub (pinned to 9b989acdf1)

Solutions

  1. Switch the lock type to REENTRANT if same-thread reentry is expected.
  2. Restructure the code so the lock is not acquired twice on the same thread — extract the inner logic outside the lock scope.
  3. Ensure unlock() is called before re-acquiring the lock on the same thread.
  4. Audit the call chain to find the nested acquisition point and eliminate it.

Example fix

// before
Lock lock = lockService.getLock("my-key", LockConstants.NON_REENTRANT_LOCK_TYPE);
lock.lock();
doWorkThatAlsoLocks(); // throws IllegalMonitorStateException
lock.unlock();
// after
Lock lock = lockService.getLock("my-key", LockConstants.REENTRANT_LOCK_TYPE);
lock.lock();
doWorkThatAlsoLocks(); // reentry succeeds
lock.unlock();
Defensive patterns

Strategy: validation

Validate before calling

void safeLock(NacosLock lock) {
    // before acquiring, verify the thread does not already hold the lock
    // (for non-reentrant locks)
    if (LockConstants.NON_REENTRANT_LOCK_TYPE.equals(lock.getLockType())
        && isHeldByCurrentThread(lock)) {
        throw new IllegalStateException(
            "Thread already holds non-reentrant lock: " + lock.getKey());
    }
    lock.lock();
}

Type guard

// Use a REENTRANT lock type when same-thread reentry is possible
String chooseLockType(boolean mayReenter) {
    return mayReenter
        ? LockConstants.REENTRANT_LOCK_TYPE
        : LockConstants.NON_REENTRANT_LOCK_TYPE;
}

Try / catch

try {
    lock.lock();
} catch (IllegalMonitorStateException e) {
    if (e.getMessage().contains("Non-reentrant")) {
        // switch to reentrant or restructure to avoid nested acquisition
        logger.error("Reentrant acquisition on non-reentrant lock: {}", lock.getKey());
    }
}

Prevention

When it happens

Trigger: Calling lock(), lockInterruptibly(), tryLock(), or tryLock(time, unit) on a NON_REENTRANT NacosLock instance that the current thread has already acquired and not yet released. Common in code that wraps the lock call in a method that is itself called from within a locked region.

Common situations: A service method acquires the lock, then calls another method that also tries to acquire the same lock key on the same thread; refactoring moved code that was previously outside a locked block into a locked block; using a non-reentrant lock type where a reentrant one was intended; the lock type was configured incorrectly (NON_REENTRANT instead of REENTRANT).

Related errors


AI-assisted analysis of alibaba/nacos@9b989acdf1 (2026-08-14). Data as JSON: /api/errors/d39487dc4c56be77. Report an issue: GitHub.