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.
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)
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
- The client is not the lock owner of the lock:
- Acquire the lock: error
- no master server available
- no master server available
- no master server available
AI-assisted analysis of apache/dolphinscheduler@02eac45a1b (2026-09-06).
Data as JSON: /api/errors/381f83ebf2b1487c.
Report an issue: GitHub.