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
- Avoid issuing concurrent updates/reloads to the same tenant; serialize tenant settings updates in your code.
- Retry the operation after a short delay — the error is transient by design (the reload was blocked, not failed).
- Reduce slow startup work in tenant modules so reloads finish quickly and stop colliding.
- 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
- Never update the same tenant's settings concurrently; queue or lock per-tenant updates.
- Serialize tenant administrative operations in scripts instead of parallelizing them.
- Keep tenant startup light so reload windows are short.
- Treat ShellHostReloadException as transient and retry with backoff.
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
- Unexpected shell state for
- Can't resolve a scope on tenant
- Invalid serial number for shell descriptor
- The 'Default' feature is not registered.
- The 'Default' tenant can't be removed.
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)