{"record":{"id":"3ff9a03f6c3ab9b7","repo":"alibaba/nacos","slug":"unlock-rejected-by-server-key-msg","errorCode":null,"errorMessage":"Unlock rejected by server, key={}, msg={}","messagePattern":"Unlock rejected by server, key=(.+?), msg=(.+?)","errorType":"exception","errorClass":"IllegalMonitorStateException","httpStatus":null,"severity":"error","filePath":"client/src/main/java/com/alibaba/nacos/client/lock/NacosLock.java","lineNumber":314,"sourceCode":"            if (count <= 0) {\n                throw new IllegalMonitorStateException(\"Current thread does not hold the lock\");\n            }\n            try {\n                LockInstance instance = buildInstance(0);\n                LockResult result = grpcClient.unLockWithResult(instance);\n                if (result.isSuccess()) {\n                    localReentrantCount.set(count - 1);\n                    if (result.getReentrantCount() == 0) {\n                        watchdog.unregister(key);\n                        localReentrantCount.remove();\n                        removed = true;\n                    }\n                } else {\n                    localReentrantCount.set(0);\n                    watchdog.unregister(key);\n                    localReentrantCount.remove();\n                    removed = true;\n                    throw new IllegalMonitorStateException(\n                        \"Unlock rejected by server, key=\" + key + \", msg=\"\n                            + result.getErrorMessage());\n                }\n            } catch (NacosException e) {\n                // Server may have already released the lock — clear client state\n                // to prevent the lock from becoming permanently unusable.\n                localReentrantCount.set(0);\n                watchdog.unregister(key);\n                localReentrantCount.remove();\n                removed = true;\n                LOGGER.error(\"Failed to unlock, key={}\", key, e);\n                throw new IllegalStateException(\"Failed to unlock: \" + key, e);\n            }\n        } finally {\n            inUnlock.remove();\n            if (!removed && localReentrantCount.get() <= 0) {\n                localReentrantCount.remove();\n            }","sourceCodeStart":296,"sourceCodeEnd":332,"githubUrl":"https://github.com/alibaba/nacos/blob/9b989acdf181d00898f2e8839257bb2b2a3cefe3/client/src/main/java/com/alibaba/nacos/client/lock/NacosLock.java#L296-L332","documentation":"Thrown by NacosLock.unlock() when the server's RELEASE operation returns a non-success LockResult. The client sends an unlock request via grpcClient.unLockWithResult(instance); if result.isSuccess() is false, it clears local state (resets reentrant count, unregisters the watchdog) and then throws IllegalMonitorStateException carrying the server's error message. The local state is force-cleared so the lock does not become permanently unusable, but the server-side error (e.g. ownership mismatch, already released, expired) is surfaced.","triggerScenarios":"The lock lease already expired on the server (watchdog renewal failed silently) before unlock() is called; another client/owner holds or already released the same key; server-side lock metadata was evicted or corrupted; using a stale LockInstance whose owner token no longer matches the current server state.","commonSituations":"Network partitions that silently kill watchdog renewals, causing the server to expire the lease while the client still believes it holds the lock; clock skew between client and server pushing expiredTime into the past; calling unlock long after the operation completed; multiple NacosLockService instances or clients targeting the same key with different owner tokens.","solutions":["Inspect the server error message embedded in the exception (result.getErrorMessage()) to determine whether it was an ownership mismatch, already-released, or expiry.","Ensure the watchdog renewal mechanism is healthy (check logs for renewal failures) before relying on the lock to still be held.","Set an appropriate expiredTime/lease and avoid holding the lock far beyond it; prefer the JUC lock()/lockInterruptibly() path which auto-renews.","Treat this exception as non-fatal for local state — local client state is already cleared, so a retry with a fresh lock() cycle is safe."],"exampleFix":"// before\nlock.unlock(); // propagates IllegalMonitorStateException on server reject\n\n// after\ntry {\n    lock.unlock();\n} catch (IllegalMonitorStateException e) {\n    LOGGER.warn(\"Server rejected unlock for key={}, local state already cleared: {}\", key, e.getMessage());\n    // local reentrant count and watchdog already reset by NacosLock\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { lock.unlock(); } catch (IllegalMonitorStateException e) { LOGGER.warn(\"Server rejected unlock ({}); local state already cleared\", e.getMessage()); }","preventionTips":["Monitor watchdog renewal logs — silent renewal failures lead to server-side expiry before unlock.","Set a lease time appropriate to the critical section length.","Treat server-rejected unlock as non-fatal: local state is already cleared by NacosLock."],"tags":["lock","server-reject","distributed-lock","lease-expiry","watchdog"],"backgroundTag":null,"analyzedSha":"9b989acdf181d00898f2e8839257bb2b2a3cefe3","analyzedAt":"2026-08-14T07:17:31.569Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}