kestra-io/kestra · error · LockException

Unable to hold the lock inside the configured timeout of {}

Error message

Unable to hold the lock inside the configured timeout of {}

What it means

Thrown by LockService.doInLock when the lock cannot be acquired within the given timeout. lock(category, id, timeout) returns false (another holder kept it for the whole wait); doInLock then throws LockException with the timeout value. The runnable never executes. Used for mutual exclusion around critical sections keyed by category+id (e.g. executions, scheduling).

Source

Thrown at core/src/main/java/io/kestra/core/lock/LockService.java:72

        doInLock(category, id, DEFAULT_TIMEOUT, runnable);
    }

    /**
     * Executes a Runnable inside a lock.
     * If the lock is already taken, it will wait for at most the <code>timeout</code> duration.
     * WARNING: for Elasticsearch, fencing via concurrency control should be used to avoid stale writes if a lock is taken over.
     *
     * @see #doInLock(String, String, Runnable)
     *
     * @param category lock category, ex 'executions'
     * @param id identifier of the lock identity inside the category, ex an execution ID
     * @param timeout how much time to wait for the lock if another process already holds the same lock
     *
     * @throws LockException if the lock cannot be hold before the timeout or the thread is interrupted.
     */
    public void doInLock(String category, String id, Duration timeout, Runnable runnable) {
        if (!lock(category, id, timeout)) {
            throw new LockException("Unable to hold the lock inside the configured timeout of " + timeout);
        }

        try {
            runnable.run();
        } finally {
            unlock(category, id);
        }
    }

    /**
     * Acquires the lock only if it is not held by another process at the time of invocation.
     *
     * @param category the category of the lock, e.g., 'executions'
     * @param id the identifier of the lock within the specified category, e.g., an execution ID
     * @return an optional {@link Disposable} to release the lock.
     */
    public Optional<Disposable> tryLock(String category, String id) {
        return lock(category, id, Duration.ZERO) ? Optional.of(Disposable.of(() -> this.unlock(category, id))) : Optional.empty();

View on GitHub (pinned to 823fada927)

Solutions

  1. Increase the timeout passed to doInLock to match the expected critical-section duration.
  2. Investigate prior holders — check logs/lock table for a stuck or orphaned lock.
  3. Reduce the critical section size so locks are held for shorter windows.
  4. If a stale lock is suspected, use the lock admin API/CLI to inspect and release orphaned locks.
  5. Consider idempotent optimistic updates instead of a coarse lock.

Example fix

// before
lockService.doInLock("executions", id, Duration.ofSeconds(5), runnable);
// after
lockService.doInLock("executions", id, Duration.ofSeconds(30), runnable);
Defensive patterns

Strategy: retry

Validate before calling

// Estimate required timeout from prior hold durations before locking
Duration budget = expectedHoldDuration.multipliedBy(2);
if (timeout.compareTo(budget) < 0) { /* warn: timeout may be too short */ }

Try / catch

try {
  lockService.doInLock(category, id, timeout, runnable);
} catch (LockException e) {
  if (e.getMessage().contains('configured timeout')) {
    log.warn('Lock contention on {}/{}; consider widening timeout', category, id);
  }
  throw e;
}

Prevention

When it happens

Trigger: doInLock(category, id, timeout, runnable) calls lock(...); lock() returns false because another process held the lock past the timeout; LockException("Unable to hold the lock inside the configured timeout of <timeout>") is thrown before runnable.run().

Common situations: Concurrent executions/schedulers contending on the same lock id, a previous holder crashed without releasing (stale lock) and the backend lacks auto-expiry, the timeout is too short for the workload, or a long-running critical section under load serializes waiters.

Understand the failure class

Related errors


AI-assisted analysis of kestra-io/kestra@823fada927 (2026-08-14). Data as JSON: /api/errors/4f86881285da37a4. Report an issue: GitHub.