{"record":{"id":"381f83ebf2b1487c","repo":"apache/dolphinscheduler","slug":"jdbc-lock-count-has-gone-negative-for-lock","errorCode":null,"errorMessage":"Jdbc lock count has gone negative for lock: ","messagePattern":"Jdbc lock count has gone negative for lock: ","errorType":"exception","errorClass":"IllegalMonitorStateException","httpStatus":null,"severity":"error","filePath":"dolphinscheduler-registry/dolphinscheduler-registry-plugins/dolphinscheduler-registry-jdbc/src/main/java/org/apache/dolphinscheduler/plugin/registry/jdbc/server/JdbcRegistryLockManager.java","lineNumber":149,"sourceCode":"    }\n\n    @Override\n    public void releaseJdbcRegistryLock(Long clientId, String lockKey) {\n        String lockOwner = LockUtils.getLockOwner();\n        LockEntry lockEntry = jdbcRegistryLockHolderMap.get(lockKey);\n        if (lockEntry == null || !lockOwner.equals(lockEntry.getLockOwner())) {\n            return;\n        }\n        if (!clientId.equals(lockEntry.getJdbcRegistryLock().getClientId())) {\n            throw new UnsupportedOperationException(\n                    \"The client \" + clientId + \" is not the lock owner of the lock: \" + lockKey);\n        }\n        int newLockCount = lockEntry.lockCount.decrementAndGet();\n        if (newLockCount > 0) {\n            return;\n        }\n        if (newLockCount < 0) {\n            throw new IllegalMonitorStateException(\"Jdbc lock count has gone negative for lock: \" + lockKey);\n        }\n        jdbcRegistryLockRepository.deleteById(lockEntry.getJdbcRegistryLock().getId());\n        jdbcRegistryLockHolderMap.remove(lockKey);\n    }\n\n    @Data\n    @Builder\n    @NoArgsConstructor\n    @AllArgsConstructor\n    public static class LockEntry {\n\n        private String lockKey;\n        private String lockOwner;\n        final AtomicInteger lockCount = new AtomicInteger(1);\n        private JdbcRegistryLockDTO jdbcRegistryLock;\n    }\n}\n","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/apache/dolphinscheduler/blob/02eac45a1b6676e639fcbfb4be2243de5771b05d/dolphinscheduler-registry/dolphinscheduler-registry-plugins/dolphinscheduler-registry-jdbc/src/main/java/org/apache/dolphinscheduler/plugin/registry/jdbc/server/JdbcRegistryLockManager.java#L131-L167","documentation":"JdbcRegistryLockManager.releaseJdbcRegistryLock decrements the reentrant lockCount for the lock key; if the count goes below zero, more releases than acquires occurred, indicating unbalanced lock bookkeeping. It throws IllegalMonitorStateException, mirroring java.util.concurrent lock semantics where unlocking an unheld lock is illegal.","triggerScenarios":"Calling releaseJdbcRegistryLock more times than acquireJdbcRegistryLock for the same lockKey/clientId; concurrent release from multiple threads racing the decrement; release after the entry was already removed by a prior final release but a stale reference re-decrements.","commonSituations":"finally blocks that release unconditionally even when acquire failed, double-release due to retry logic, non-reentrant usage patterns where code assumes one release per process rather than per acquire.","solutions":["Only release in a finally block when acquireJdbcRegistryLock returned/entered successfully","Track acquired count locally and release exactly that many times","Use the timeout-based acquire which returns false on failure, and skip release when it returns false"],"exampleFix":"// before\ntry {\n    // may fail to acquire\n} finally {\n    registryClient.releaseJdbcRegistryLock(clientId, lockKey); // releases even on failed acquire\n}\n// after\nif (registryClient.acquireJdbcRegistryLock(clientId, lockKey, timeout)) {\n    try {\n        // work\n    } finally {\n        registryClient.releaseJdbcRegistryLock(clientId, lockKey);\n    }\n}","handlingStrategy":"validation","validationCode":"private final AtomicInteger held = new AtomicInteger(); // increment only after successful acquire; release only if held.get() > 0","typeGuard":null,"tryCatchPattern":"try { registryClient.releaseJdbcRegistryLock(clientId, lockKey); } catch (IllegalMonitorStateException e) { log.error(\"Unbalanced release for {}: {}\", lockKey, e.getMessage()); }","preventionTips":["Release only after a confirmed successful acquire","Match every acquire with exactly one release","Avoid unconditional releases in finally blocks"],"tags":["registry","distributed-lock","state"],"backgroundTag":"internal-invariant-violation","analyzedSha":"02eac45a1b6676e639fcbfb4be2243de5771b05d","analyzedAt":"2026-09-06T17:43:00.555Z","contentChangedAt":"2026-09-06T17:43:00.555Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}