apache/dolphinscheduler · error · IllegalMonitorStateException
Jdbc lock count has gone negative for lock:
Error message
Jdbc lock count has gone negative for lock:
What it means
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.
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
Example fix
// before
try {
// may fail to acquire
} finally {
registryClient.releaseJdbcRegistryLock(clientId, lockKey); // releases even on failed acquire
}
// after
if (registryClient.acquireJdbcRegistryLock(clientId, lockKey, timeout)) {
try {
// work
} finally {
registryClient.releaseJdbcRegistryLock(clientId, lockKey);
}
} Defensive patterns
Strategy: validation
Validate before calling
private final AtomicInteger held = new AtomicInteger(); // increment only after successful acquire; release only if held.get() > 0
Try / catch
try { registryClient.releaseJdbcRegistryLock(clientId, lockKey); } catch (IllegalMonitorStateException e) { log.error("Unbalanced release for {}: {}", lockKey, e.getMessage()); } Prevention
- Release only after a confirmed successful acquire
- Match every acquire with exactly one release
- Avoid unconditional releases in finally blocks
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
AI-assisted analysis of apache/dolphinscheduler@02eac45a1b (2026-09-06).
Data as JSON: /api/errors/381f83ebf2b1487c.
Report an issue: GitHub.
Appendix: source
Thrown at dolphinscheduler-registry/dolphinscheduler-registry-plugins/dolphinscheduler-registry-jdbc/src/main/java/org/apache/dolphinscheduler/plugin/registry/jdbc/server/JdbcRegistryLockManager.java:149
}
@Override
public void releaseJdbcRegistryLock(Long clientId, String lockKey) {
String lockOwner = LockUtils.getLockOwner();
LockEntry lockEntry = jdbcRegistryLockHolderMap.get(lockKey);
if (lockEntry == null || !lockOwner.equals(lockEntry.getLockOwner())) {
return;
}
if (!clientId.equals(lockEntry.getJdbcRegistryLock().getClientId())) {
throw new UnsupportedOperationException(
"The client " + clientId + " is not the lock owner of the lock: " + lockKey);
}
int newLockCount = lockEntry.lockCount.decrementAndGet();
if (newLockCount > 0) {
return;
}
if (newLockCount < 0) {
throw new IllegalMonitorStateException("Jdbc lock count has gone negative for lock: " + lockKey);
}
jdbcRegistryLockRepository.deleteById(lockEntry.getJdbcRegistryLock().getId());
jdbcRegistryLockHolderMap.remove(lockKey);
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class LockEntry {
private String lockKey;
private String lockOwner;
final AtomicInteger lockCount = new AtomicInteger(1);
private JdbcRegistryLockDTO jdbcRegistryLock;
}
}
View on GitHub (pinned to 02eac45a1b)