apache/iceberg · error · UncheckedInterruptedException

Interrupted during isHeld

Error message

Interrupted during isHeld

What it means

JdbcLockFactory's JdbcLock.isHeld() queries the JDBC catalog's lock table to determine whether this trigger holds its lock. If the thread waiting on the JDBC query is interrupted, the interruption is re-asserted and an UncheckedInterruptedException wrapping the original InterruptedException is thrown. This converts the checked interruption into a runtime exception so lock-state checks can fail fast in maintenance trigger threads.

Source

Thrown at flink/v2.2/flink/src/main/java/org/apache/iceberg/flink/maintenance/api/JdbcLockFactory.java:227

    }

    @SuppressWarnings("checkstyle:NestedTryDepth")
    @Override
    public boolean isHeld() {
      try {
        return pool.run(
            conn -> {
              try (PreparedStatement sql = conn.prepareStatement(GET_LOCK_SQL)) {
                sql.setString(1, type.key);
                sql.setString(2, lockId);
                try (ResultSet rs = sql.executeQuery()) {
                  return rs.next();
                }
              }
            });
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new UncheckedInterruptedException(e, "Interrupted during isHeld");
      } catch (SQLException e) {
        // SQL exception happened when getting lock information
        throw new UncheckedSQLException(e, "Failed to check the state of the lock %s", this);
      }
    }

    @SuppressWarnings("checkstyle:NestedTryDepth")
    @Override
    public void unlock() {
      try {
        // Possible concurrency issue:
        // - `unlock` and `tryLock` happens at the same time when there is an existing lock
        //
        // Steps:
        // 1. `unlock` removes the lock in the database, but there is a temporary connection failure
        // 2. `lock` finds that there is no lock, so creates a new lock
        // 3. `unlock` retries the lock removal and removes the new lock
        //

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Check why the thread was interrupted (job cancellation, timeout watchdog) and ensure maintenance tasks run in a thread allowed to complete.
  2. Verify the JDBC database and network are responsive; slow queries increase the window for interruption.
  3. Adjust connection/validation timeouts (e.g., connection pool settings) so queries fail with SQLException instead of hanging until interrupted.
  4. Propagate and honor the interruption: since the flag is re-set via Thread.currentThread().interrupt(), let shutdown logic observe it and stop gracefully.

Example fix

// before
boolean held = lock.tryLock(); // may throw UncheckedInterruptedException
// after
try {
  boolean held = lock.tryLock();
} catch (UncheckedInterruptedException e) {
  Thread.currentThread().interrupt(); // restore flag, abort maintenance gracefully
  return;
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (Thread.currentThread().isInterrupted()) { /* skip maintenance attempt */ }

Try / catch

try { boolean held = lock.tryLock(); } catch (UncheckedInterruptedException e) { Thread.currentThread().interrupt(); return; }

Prevention

When it happens

Trigger: Calling tryLock (which calls isHeld) on a JdbcLock when the executing thread is interrupted while blocked on the JDBC query (Tasks.foreach lambda doing rs.next() against the lock table).

Common situations: Flink job shutdown/cancel interrupting the maintenance trigger thread mid-query; JDBC driver stuck on a slow or unresponsive database so the thread sits blocked until interrupted; operator restarts cancelling the thread that acquires locks.

Related errors


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