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
- Increase the timeout passed to doInLock to match the expected critical-section duration.
- Investigate prior holders — check logs/lock table for a stuck or orphaned lock.
- Reduce the critical section size so locks are held for shorter windows.
- If a stale lock is suspected, use the lock admin API/CLI to inspect and release orphaned locks.
- 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
- Size the timeout to the longest expected critical section.
- Keep critical sections short to reduce contention.
- Monitor for stale locks and clear them via the admin API.
- Prefer optimistic/CAS updates where the backend supports it.
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Migration lock is held by another process. Another instance
- RegexTimeoutException: e.getMessage()
- RegexTimeoutException: e.getMessage()
- Failed to run subflow '%s'.'%s': %s
- The 'subflow' function 'timeout' must be an ISO-8601 duratio
AI-assisted analysis of kestra-io/kestra@823fada927 (2026-08-14).
Data as JSON: /api/errors/4f86881285da37a4.
Report an issue: GitHub.