{"record":{"id":"e6d6864523e53092","repo":"apache/iceberg","slug":"cannot-call-acquirelock-twice-for-s","errorCode":null,"errorMessage":"Cannot call acquireLock twice for %s","messagePattern":"Cannot call acquireLock twice for (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"hive-metastore/src/main/java/org/apache/iceberg/hive/MetastoreLock.java","lineNumber":440,"sourceCode":"        Thread.currentThread().interrupt(); // Set back the interrupt status\n        LOG.warn(\"Interrupted finding locks to unlock {}.{}\", databaseName, tableName, ie);\n      }\n    } catch (Exception e) {\n      LOG.warn(\"Failed to unlock {}.{}\", databaseName, tableName, e);\n    }\n  }\n\n  private void doUnlock(long lockId) throws TException, InterruptedException {\n    metaClients.run(\n        client -> {\n          client.unlock(lockId);\n          return null;\n        });\n  }\n\n  private void acquireJvmLock() {\n    if (jvmLock != null) {\n      throw new IllegalStateException(\n          String.format(\"Cannot call acquireLock twice for %s\", fullName));\n    }\n\n    jvmLock = commitLockCache.get(fullName, t -> new ReentrantLock(true));\n    jvmLock.lock();\n  }\n\n  private void releaseJvmLock() {\n    if (jvmLock != null) {\n      jvmLock.unlock();\n      jvmLock = null;\n    }\n  }\n\n  private static void initTableLevelLockCache(long evictionTimeout) {\n    if (commitLockCache == null) {\n      synchronized (MetastoreLock.class) {\n        if (commitLockCache == null) {","sourceCodeStart":422,"sourceCodeEnd":458,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/hive-metastore/src/main/java/org/apache/iceberg/hive/MetastoreLock.java#L422-L458","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Create a fresh MetastoreLock per commit/lock cycle instead of reusing a locked instance.","If retrying a commit, go through the catalog/table commit API which constructs a new lock each time.","Guard shared access to the lock object with synchronization or thread confinement so acquireJvmLock runs once per instance.","Check for double invocation of lock() in custom wrappers around MetastoreLock."],"exampleFix":"// before\nMetastoreLock lock = lockCache.get(table);\nlock.lock();\nretryCommit(lock); // lock() again on same instance -> IllegalStateException\n// after\nMetastoreLock lock = new MetastoreLock(clients, database, table, heartbeatInterval);\nlock.lock();\ncommit();\nlock.unlock();","handlingStrategy":"validation","validationCode":"// never reuse a MetastoreLock across lock cycles\nMap<String, MetastoreLock> active = new ConcurrentHashMap<>();\nvoid guard(String table) {\n  if (active.putIfAbsent(table, newLock(table)) != null) {\n    throw new IllegalStateException(\"Lock already active for \" + table);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  lock.lock();\n  doCommit();\n} catch (IllegalStateException e) {\n  if (e.getMessage().contains(\"Cannot call acquireLock twice\")) {\n    // misuse: recreate the lock object for this commit cycle\n    lock = newLock(table); lock.lock(); doCommit();\n  } else throw e;\n}","preventionTips":["Construct a fresh MetastoreLock per commit; never cache locked instances","Retry commits via the table/catalog commit API, which builds new locks","Confine each MetastoreLock to one thread; don't share via global maps","Audit custom wrappers for double lock() calls"],"tags":["locking","concurrency","state","hive"],"backgroundTag":"invalid-state-transition","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"}