OrchardCMS/OrchardCore · error · ShellHostReloadException

Unable to reload the tenant

Error message

Unable to reload the tenant '{settings.Name}' as too many concurrent processes are trying to do so.

What it means

ShellHost throws ShellHostReloadException when a tenant's shell context cannot be reloaded because concurrent reload attempts exhausted the retry/lock attempts. Orchard Core serializes shell reloads so only a limited number of processes may reload a given tenant at once; when the concurrency guard gives up, this error is raised to protect against an inconsistent shell state.

Solutions

  1. Avoid issuing concurrent updates/reloads to the same tenant; serialize tenant settings updates in your code.
  2. Retry the operation after a short delay — the error is transient by design (the reload was blocked, not failed).
  3. Reduce slow startup work in tenant modules so reloads finish quickly and stop colliding.
  4. If triggered by parallel scripts, run tenant updates sequentially or use a lock around UpdateShellSettingsAsync.

Example fix

// before: parallel
await Task.WhenAll(tenants.Select(t => _shellHost.UpdateShellSettingsAsync(t)));
// after: sequential with retry
foreach (var t in tenants)
{
    try { await _shellHost.UpdateShellSettingsAsync(t); }
    catch (ShellHostReloadException) { await Task.Delay(500); await _shellHost.UpdateShellSettingsAsync(t); }
}
Defensive patterns

Strategy: retry

Validate before calling

// Track in-flight reloads per tenant in application code
private static readonly HashSet<string> _reloading = new();
lock (_reloading) { if (_reloading.Contains(name)) return; _reloading.Add(name); }
try { await shellHost.UpdateShellSettingsAsync(settings); }
finally { lock (_reloading) _reloading.Remove(name); }

Try / catch

try
{
    await _shellHost.UpdateShellSettingsAsync(settings);
}
catch (ShellHostReloadException ex)
{
    _logger.LogWarning(ex, "Tenant {Tenant} reload was blocked by concurrent reloads; retrying.", settings.Name);
    await Task.Delay(Random.Shared.Next(250, 1000));
    await _shellHost.UpdateShellSettingsAsync(settings);
}

Prevention

When it happens

Trigger: Calling UpdateShellSettingsAsync (or ReloadShellContextAsync directly) for the same tenant from multiple concurrent requests/threads, e.g. updating tenant settings while background tasks or several admin requests simultaneously trigger a reload of that tenant.

Common situations: Admin UI saves tenant settings while another request reloads the shell; automated scripts updating many tenants in parallel; a slow reload (long tenant startup) exceeding the retry window so other reload attempts time out and throw.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of OrchardCMS/OrchardCore@4306c0717f (2026-09-13). Data as JSON: /api/errors/f09320ec83a9458f. Report an issue: GitHub.

Appendix: source

Thrown at src/OrchardCore/OrchardCore/Shell/ShellHost.cs:243

            _shellSettings[settings.Name] = settings;

            if (CanRegisterShell(settings))
            {
                _runningShellTable.Add(settings);
            }

            // Consistency: We may have been the last to add the shell but not with the last settings.
            var loaded = await _shellSettingsManager.LoadSettingsAsync(settings.Name);
            if (settings.VersionId == loaded.VersionId)
            {
                loaded.AsDisposable().Dispose();
                return;
            }

            settings = loaded;
        }

        throw new ShellHostReloadException(
            $"Unable to reload the tenant '{settings.Name}' as too many concurrent processes are trying to do so.");
    }

    /// <summary>
    /// Releases a shell so that a new one will be built for subsequent requests.
    /// Note: Can be used to free up resources after a given time of inactivity.
    /// </summary>
    /// <param name="settings">The <see cref="ShellSettings"/> to reload.</param>
    /// <param name="eventSource">Whether the related <see cref="ShellEvent"/> is invoked.</param>
    public async Task ReleaseShellContextAsync(ShellSettings settings, bool eventSource = true)
    {
        if (ReleasingAsync is not null && eventSource && !settings.IsInitializing())
        {
            foreach (var d in ReleasingAsync.GetInvocationList())
            {
                await ((ShellEvent)d)(settings.Name);
            }
        }

View on GitHub (pinned to 4306c0717f)