{"record":{"id":"c7618657727dfafe","repo":"apache/iceberg","slug":"failed-to-release-lock-c76186","errorCode":null,"errorMessage":"Failed to release lock","messagePattern":"Failed to release lock","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"flink/v2.2/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.2/flink/src/main/java/org/apache/iceberg/flink/maintenance/api/ZkLockFactory.java#L225-L261","documentation":"ZkLockFactory's unlock() wraps any exception from resetting the ZooKeeper shared count (SharedCount.setCount(UNLOCKED)) into a plain RuntimeException with the message 'Failed to release lock'. This fires when the ZK session is broken (expired session, connection loss, node deleted) so the distributed lock count cannot be reset. The lock may still be held or the ephemeral node may already be gone, so the caller cannot assume the lock was cleanly released.","triggerScenarios":"Calling unlock() (directly or via the maintenance TableMaintenance executor) when the Curator/ZooKeeper client has lost its session or the SharedCount cannot write the new count value to ZK.","commonSituations":"ZooKeeper session timeout during a long maintenance task; ZK ensemble restart or network partition; the lock znode/counter was deleted by session expiry while the job was still running.","solutions":["Check ZooKeeper client health and session timeout settings; increase sessionTimeoutMs if maintenance tasks run long.","Verify network connectivity and ZK ensemble stability between the Flink JobManager/TaskManagers and the ensemble.","Ensure the lock path (lockPath) still exists and the service account has write ACLs on it.","Retry the maintenance cycle; the failure is typically transient once ZK connectivity is restored.","Inspect the wrapped cause (getCause()) for the real Curator exception (ConnectionLossException, SessionExpiredException)."],"exampleFix":"// before\nzkLockFactoryBuilder.lockPath(\"/iceberg/maintenance/lock\").build();\n// after\nzkLockFactoryBuilder\n    .lockPath(\"/iceberg/maintenance/lock\")\n    .connectionTimeoutMs(15000)\n    .sessionTimeoutMs(120000) // tolerate long maintenance cycles\n    .build();","handlingStrategy":"try-catch","validationCode":"// verify ZK connectivity before unlocking\nif (!curatorClient.getZookeeperClient().isConnected()) {\n  LOG.warn(\"ZK session down; lock release may fail\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  lockFactory.withLock(() -> maintenance.run());\n} catch (RuntimeException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"Failed to release lock\")) {\n    LOG.warn(\"Lock release failed; ZK session issue\", e.getCause());\n    // do not assume lock is free; wait for session expiry before retrying\n  } else {\n    throw e;\n  }\n}","preventionTips":["Set generous ZK session timeouts relative to maintenance task duration.","Monitor ZK session-expiry metrics on the Flink job.","Ensure lock path ACLs permit the service principal to write.","Treat the lock as possibly-held after this error; rely on session expiry before re-acquiring."],"tags":["zookeeper","lock","flink","distributed-lock"],"backgroundTag":"lock-release-failed","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}