apache/iceberg · error · IllegalStateException

Cannot call acquireLock twice for %s

Error message

Cannot call acquireLock twice for %s

What it means

MetastoreLock combines an in-JVM ReentrantLock (per table full name, from a cache) with the metastore lock. acquireJvmLock guards against double-acquisition within one MetastoreLock instance; calling lock() twice on the same instance throws this IllegalStateException. It is an internal-state misuse of the lock object, not a metastore failure.

Source

Thrown at hive-metastore/src/main/java/org/apache/iceberg/hive/MetastoreLock.java:440

        Thread.currentThread().interrupt(); // Set back the interrupt status
        LOG.warn("Interrupted finding locks to unlock {}.{}", databaseName, tableName, ie);
      }
    } catch (Exception e) {
      LOG.warn("Failed to unlock {}.{}", databaseName, tableName, e);
    }
  }

  private void doUnlock(long lockId) throws TException, InterruptedException {
    metaClients.run(
        client -> {
          client.unlock(lockId);
          return null;
        });
  }

  private void acquireJvmLock() {
    if (jvmLock != null) {
      throw new IllegalStateException(
          String.format("Cannot call acquireLock twice for %s", fullName));
    }

    jvmLock = commitLockCache.get(fullName, t -> new ReentrantLock(true));
    jvmLock.lock();
  }

  private void releaseJvmLock() {
    if (jvmLock != null) {
      jvmLock.unlock();
      jvmLock = null;
    }
  }

  private static void initTableLevelLockCache(long evictionTimeout) {
    if (commitLockCache == null) {
      synchronized (MetastoreLock.class) {
        if (commitLockCache == null) {

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Create a fresh MetastoreLock per commit/lock cycle instead of reusing a locked instance.
  2. If retrying a commit, go through the catalog/table commit API which constructs a new lock each time.
  3. Guard shared access to the lock object with synchronization or thread confinement so acquireJvmLock runs once per instance.
  4. Check for double invocation of lock() in custom wrappers around MetastoreLock.

Example fix

// before
MetastoreLock lock = lockCache.get(table);
lock.lock();
retryCommit(lock); // lock() again on same instance -> IllegalStateException
// after
MetastoreLock lock = new MetastoreLock(clients, database, table, heartbeatInterval);
lock.lock();
commit();
lock.unlock();
Defensive patterns

Strategy: validation

Validate before calling

// never reuse a MetastoreLock across lock cycles
Map<String, MetastoreLock> active = new ConcurrentHashMap<>();
void guard(String table) {
  if (active.putIfAbsent(table, newLock(table)) != null) {
    throw new IllegalStateException("Lock already active for " + table);
  }
}

Try / catch

try {
  lock.lock();
  doCommit();
} catch (IllegalStateException e) {
  if (e.getMessage().contains("Cannot call acquireLock twice")) {
    // misuse: recreate the lock object for this commit cycle
    lock = newLock(table); lock.lock(); doCommit();
  } else throw e;
}

Prevention

When it happens

Trigger: Calling lock() (directly or via a second commit path) on the same MetastoreLock instance that already holds its JVM lock — e.g. reusing a cached lock object across retries or sharing the instance between threads without synchronization.

Common situations: Application code caching MetastoreLock instances and re-committing; custom catalog integrations that call lock() in a retry loop on the same object; race where two threads grab the same MetastoreLock from a shared map.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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