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

  1. Retry after the lock timeout expires once the current holder finishes activation
  2. Check for stale locks in the lock store (database table/file) and clear entries from dead instances after confirming no holder is alive
  3. Ensure only one activation flow runs per tenant at a time (avoid duplicate background activation tasks)
  4. 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

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


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)