{"record":{"id":"5a315d902b0bdb63","repo":"apache/dolphinscheduler","slug":"release-the-lock-error","errorCode":null,"errorMessage":"Release the lock:  error","messagePattern":"Release the lock:  error","errorType":"exception","errorClass":"RegistryException","httpStatus":null,"severity":"error","filePath":"dolphinscheduler-registry/dolphinscheduler-registry-plugins/dolphinscheduler-registry-jdbc/src/main/java/org/apache/dolphinscheduler/plugin/registry/jdbc/server/JdbcRegistryServer.java","lineNumber":253,"sourceCode":"            throw new RegistryException(\"Acquire the lock: \" + lockKey + \" error\", ex);\n        }\n    }\n\n    @Override\n    public boolean acquireJdbcRegistryLock(Long clientId, String lockKey, long timeout) {\n        try {\n            return jdbcRegistryLockManager.acquireJdbcRegistryLock(clientId, lockKey, timeout);\n        } catch (Exception ex) {\n            throw new RegistryException(\"Acquire the lock: \" + lockKey + \" error\", ex);\n        }\n    }\n\n    @Override\n    public void releaseJdbcRegistryLock(Long clientId, String lockKey) {\n        try {\n            jdbcRegistryLockManager.releaseJdbcRegistryLock(clientId, lockKey);\n        } catch (Exception ex) {\n            throw new RegistryException(\"Release the lock: \" + lockKey + \" error\", ex);\n        }\n    }\n\n    @Override\n    public void close() {\n        jdbcRegistryServerState = JdbcRegistryServerState.STOPPED;\n        schedulerThreadExecutor.shutdown();\n        List<Long> clientIds = jdbcRegistryClients.stream()\n                .map(IJdbcRegistryClient::getJdbcRegistryClientIdentify)\n                .map(JdbcRegistryClientIdentify::getClientId)\n                .collect(Collectors.toList());\n        doPurgeJdbcRegistryClientInDB(clientIds);\n        jdbcRegistryClients.clear();\n        jdbcRegistryClientDTOMap.clear();\n    }\n\n    private void purgeInvalidJdbcRegistryMetadata() {\n        final StopWatch stopWatch = StopWatch.createStarted();","sourceCodeStart":235,"sourceCodeEnd":271,"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/JdbcRegistryServer.java#L235-L271","documentation":"Thrown by JdbcRegistryServer.releaseJdbcRegistryLock when the underlying JdbcRegistryLockManager fails to release a distributed lock held by the given client for the given lock key. The original exception is wrapped, so the cause carries the real reason (SQL failure, lock not owned, connection loss). It indicates the caller's lock release did not complete successfully.","triggerScenarios":"Calling releaseJdbcRegistryLock(clientId, lockKey) when the clientId no longer owns the lock, the lock row was already deleted/expired, or the underlying database update/delete fails (connection error, deadlock, table missing).","commonSituations":"Database outage or connection-pool exhaustion during shutdown; lock TTL expired and was re-acquired by another client before release; double-release after an earlier session crash; schema not initialized (lock table absent).","solutions":["Inspect the wrapped cause (getCause) to determine whether it is a SQL/connection failure or a non-existent/foreign lock row","Verify the clientId and lockKey passed match exactly those returned when the lock was acquired","Ensure the lock table exists and the datasource is healthy before shutdown","Release the lock in a finally block only once, guarding against double release; on repeated failure log and let lock TTL expiry clean up","Retry the release with backoff on transient SQL exceptions"],"exampleFix":"// before\nregistry.releaseJdbcRegistryLock(clientId, lockKey);\n// after\ntry {\n    registry.releaseJdbcRegistryLock(clientId, lockKey);\n} catch (RegistryException e) {\n    log.warn(\"Failed to release lock {}, will rely on TTL expiry: {}\", lockKey, e.getCause());\n}","handlingStrategy":"try-catch","validationCode":"boolean lockOwned = jdbcRegistryLockManager.getLockThread(lockKey)\n        .map(t -> Objects.equals(t.getClientId(), clientId)).orElse(false);\nif (!lockOwned) throw new IllegalStateException(\"clientId does not own lock \" + lockKey);","typeGuard":"boolean ownsLock(Long clientId, String lockKey) {\n    return Optional.ofNullable(lockMap.get(lockKey))\n        .map(entry -> Objects.equals(entry.getClientId(), clientId))\n        .orElse(false);\n}","tryCatchPattern":"try {\n    registry.releaseJdbcRegistryLock(clientId, lockKey);\n} catch (RegistryException e) {\n    log.warn(\"Lock {} release failed; relying on TTL expiry\", lockKey, e.getCause());\n}","preventionTips":["Only release locks with the clientId/key pair returned at acquisition","Wrap release in finally exactly once to avoid double release","Verify the lock table exists and the DB connection pool is healthy before shutdown","Rely on lock TTL expiry as a fallback and log release failures"],"tags":["registry","lock","database","cleanup"],"backgroundTag":"database-write-failed","analyzedSha":"02eac45a1b6676e639fcbfb4be2243de5771b05d","analyzedAt":"2026-09-06T17:43:00.555Z","contentChangedAt":"2026-09-06T17:43:00.555Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}