{"record":{"id":"0c9cf80ba9b5216f","repo":"apache/iceberg","slug":"failed-to-release-lock-0c9cf8","errorCode":null,"errorMessage":"Failed to release lock","messagePattern":"Failed to release lock","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"flink/v2.3/flink/src/main/java/org/apache/iceberg/flink/maintenance/api/ZkLockFactory.java","lineNumber":243,"sourceCode":"      return isHeld(sharedCount.getVersionedValue());\n    }\n\n    private static boolean isHeld(VersionedValue<Integer> versionedValue) {\n      try {\n        return versionedValue.getValue() == LOCKED;\n      } catch (Exception e) {\n        throw new RuntimeException(\"Failed to check Zookeeper lock status\", e);\n      }\n    }\n\n    @Override\n    public void unlock() {\n      try {\n        sharedCount.setCount(UNLOCKED);\n        LOG.debug(\"Released lock for path: {}\", lockPath);\n      } catch (Exception e) {\n        LOG.warn(\"Failed to release lock for path: {}\", lockPath, e);\n        throw new RuntimeException(\"Failed to release lock\", e);\n      }\n    }\n  }\n\n  @VisibleForTesting\n  RetryPolicy createRetryPolicy() {\n    ZKRetryPolicies effectivePolicy =\n        (retryPolicy == null) ? ZKRetryPolicies.EXPONENTIAL_BACKOFF : retryPolicy;\n\n    switch (effectivePolicy) {\n      case ONE_TIME:\n        return new RetryOneTime(baseSleepTimeMs);\n\n      case N_TIME:\n        return new RetryNTimes(maxRetries, baseSleepTimeMs);\n\n      case BOUNDED_EXPONENTIAL_BACKOFF:\n        return new BoundedExponentialBackoffRetry(baseSleepTimeMs, maxSleepTimeMs, maxRetries);","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/flink/v2.3/flink/src/main/java/org/apache/iceberg/flink/maintenance/api/ZkLockFactory.java#L225-L261","documentation":"ZkLock.unlock() sets the SharedCount back to UNLOCKED. If that ZooKeeper write fails, the failure is logged as a warning with the path and rethrown as RuntimeException \"Failed to release lock\". The lock row/counter may remain LOCKED, blocking subsequent maintenance cycles until released or expired.","triggerScenarios":"Calling unlock() on the ZK-based TriggerLockFactory lock when sharedCount.setCount(UNLOCKED) fails due to connection loss, session expiry, version conflict, or no permission.","commonSituations":"ZooKeeper ensemble briefly unreachable at commit time; session expired during a long maintenance run; ACL denies write on the lock node; stale version of the shared counter.","solutions":["Inspect the wrapped cause (KeeperException) for connection/auth/version issues","Retry the unlock or restart the job so a fresh lock factory reconnects and clears the counter","Manually reset the shared-count znode to the unlocked value if the lock is stuck","Increase session timeout / retry policy so short ZK hiccups do not break unlock"],"exampleFix":"// before\nlock.unlock(); // RuntimeException: Failed to release lock\n// after\ntry {\n  lock.unlock();\n} catch (RuntimeException e) {\n  LOG.warn(\"Unlock failed, will reset lock znode\", e); // remediate stale LOCKED state\n}","handlingStrategy":"retry","validationCode":"// preflight: confirm write ACL and session freshness\nclient.checkExists().forPath(lockPath);","typeGuard":null,"tryCatchPattern":"try { lock.unlock(); } catch (RuntimeException e) { /* retry unlock; if still stuck reset the shared-count znode to UNLOCKED */ }","preventionTips":["Use a Curator retry policy with enough attempts to absorb brief ZK hiccups","Grant write ACLs on lock nodes","Keep maintenance cycles shorter than the session timeout","Have an operator runbook to reset stuck lock znodes"],"tags":["zookeeper","locking","unlock","flink"],"backgroundTag":"database-write-failed","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}