apache/iceberg · error · RuntimeException

Failed to release lock

Error message

Failed to release lock

What it means

ZkLock.unlock() failed to set the shared counter back to UNLOCKED; after logging a warning it throws RuntimeException('Failed to release lock', e). The lock may remain held in ZooKeeper and block future maintenance triggers.

Source

Thrown at flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/maintenance/api/ZkLockFactory.java:243

      return isHeld(sharedCount.getVersionedValue());
    }

    private static boolean isHeld(VersionedValue<Integer> versionedValue) {
      try {
        return versionedValue.getValue() == LOCKED;
      } catch (Exception e) {
        throw new RuntimeException("Failed to check Zookeeper lock status", e);
      }
    }

    @Override
    public void unlock() {
      try {
        sharedCount.setCount(UNLOCKED);
        LOG.debug("Released lock for path: {}", lockPath);
      } catch (Exception e) {
        LOG.warn("Failed to release lock for path: {}", lockPath, e);
        throw new RuntimeException("Failed to release lock", e);
      }
    }
  }

  @VisibleForTesting
  RetryPolicy createRetryPolicy() {
    ZKRetryPolicies effectivePolicy =
        (retryPolicy == null) ? ZKRetryPolicies.EXPONENTIAL_BACKOFF : retryPolicy;

    switch (effectivePolicy) {
      case ONE_TIME:
        return new RetryOneTime(baseSleepTimeMs);

      case N_TIME:
        return new RetryNTimes(maxRetries, baseSleepTimeMs);

      case BOUNDED_EXPONENTIAL_BACKOFF:
        return new BoundedExponentialBackoffRetry(baseSleepTimeMs, maxSleepTimeMs, maxRetries);

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Inspect getCause(); if ConnectionLoss/BadVersion, retry unlock() once the session re-establishes.
  2. Manually reset the counter znode (or delete it) to clear a stuck lock, then let the factory reinitialize it.
  3. Fix ACLs so the client principal has write access to the lock path.
  4. Avoid sharing the same lockId across concurrent triggers; give each trigger a unique lockId.
  5. Use unique lockIds and short session timeouts so expired locks self-release on session close.

Example fix

// before
ZkLockFactory.builder().setLockId("maintenance")  // shared by two jobs -> version conflicts
// after
ZkLockFactory.builder().setLockId("maintenance-daily-aggregate") // unique per trigger
Defensive patterns

Strategy: retry

Try / catch

try {
  lock.unlock();
} catch (RuntimeException e) {
  LOG.error("Release failed; lock {} may stay held", lockId, e.getCause());
  // retry unlock with backoff, else manually reset the counter znode
}

Prevention

When it happens

Trigger: sharedCount.setCount(UNLOCKED) throws: session expired (ConnectionLoss), NoAuth on the znode, the versioned set fails because the node changed concurrently, or the znode was deleted externally before the write.

Common situations: ZooKeeper maintenance/failover during unlock; ACLs changed so the client can no longer write; two instances racing on the same lockId causing a BadVersion error; manual znode deletion by an operator.

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/3496b24d733bdfe5. Report an issue: GitHub.