OrchardCMS/OrchardCore · error · TimeoutException
Failed to acquire a lock before activating the tenant
Error message
Failed to acquire a lock before activating the tenant: {ShellContext.Settings.Name} What it means
During shell activation (ActivateShellInternalAsync), the tenant must acquire its distributed lock before activation proceeds. If the lock could not be acquired within the timeout, a TimeoutException naming the tenant is thrown, signaling that another process (or a stuck prior run) holds the lock on this tenant.
Solutions
- Retry after the lock timeout expires once the current holder finishes activation
- Check for stale locks in the lock store (database table/file) and clear entries from dead instances after confirming no holder is alive
- Ensure only one activation flow runs per tenant at a time (avoid duplicate background activation tasks)
- Verify the lock provider (database) is reachable and performant; slow DB lookups can push acquisition past the timeout
Example fix
// before
await shellHost.GetSettings(...); // races with another instance activating the same tenant
// after
try
{
await ShellScope.UsingAsync(shellHost, tenant);
}
catch (TimeoutException) when (message.Contains("Failed to acquire a lock"))
{
await Task.Delay(TimeSpan.FromSeconds(5));
// retry activation
} Defensive patterns
Strategy: retry
Validate before calling
// Detect an already-activated shell before forcing activation
if (shellHost.TryGetShellContext(tenant, out var ctx) && ctx.IsActivated)
return; // no lock/activation needed Try / catch
try { await ShellScope.UsingAsync(shellHost, tenant); }
catch (TimeoutException ex) when (ex.Message.Contains("Failed to acquire a lock"))
{
await Task.Delay(TimeSpan.FromSeconds(10));
// retry with bounded attempts
} Prevention
- Avoid multiple processes activating the same tenant concurrently
- Monitor and clean stale locks after crashed deployments
- Keep the lock store (database) healthy and responsive
- Use single-flight/background activation rather than ad-hoc triggers
When it happens
Trigger: Starting/concurrently activating the same tenant from multiple hosts or threads while a database/file distributed lock is held; a crashed process leaving a stale lock row/file until expiry; long-running activation exceeding the lock timeout while activation is retried via UsingAsync.
Common situations: Multi-instance farms (web garden or scaled-out deployments) racing tenant startup; a previous deployment terminated mid-activation leaving locks in the database; slow database causing lock acquisition to exceed the timeout.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
Related errors
- Couldn't acquire a lock to update the sitemap within
- Unable to reload the tenant
- The 'Default' tenant can't be removed.
- The tenant ' ' can't be removed as it is neither…
- File path must be a non-empty string.
AI-assisted analysis of OrchardCMS/OrchardCore@4306c0717f (2026-09-13).
Data as JSON: /api/errors/1b264798d065af86.
Report an issue: GitHub.
Appendix: source
Thrown at src/OrchardCore/OrchardCore.Abstractions/Shell/Scope/ShellScope.cs:326
if (_state.HasFlag(ShellScopeStates.ServiceScopeOnly))
{
return;
}
// Try to acquire a lock before using a new scope, so that a next process gets the last committed data.
(var locker, var locked) = await ShellContext.TryAcquireShellActivateLockAsync();
if (!locked)
{
// The retry logic increases the delay between 2 attempts (max of 10s), so if there are too
// many concurrent requests, one may experience a timeout while waiting before a new retry.
if (ShellContext.IsActivated)
{
// Don't throw if the shell is activated.
return;
}
throw new TimeoutException($"Failed to acquire a lock before activating the tenant: {ShellContext.Settings.Name}");
}
await using var acquiredLock = locker;
// The tenant gets activated here.
if (!ShellContext.IsActivated)
{
await new ShellScope(ShellContext).UsingAsync(async scope =>
{
var tenantEvents = scope.ServiceProvider.GetServices<IModularTenantEvents>();
foreach (var tenantEvent in tenantEvents)
{
await tenantEvent.ActivatingAsync();
}
foreach (var tenantEvent in tenantEvents.Reverse())
{
await tenantEvent.ActivatedAsync();View on GitHub (pinned to 4306c0717f)