dotnet/aspnetcore · error · Error
Cannot start a disposed circuit.
Error message
Cannot start a disposed circuit.
What it means
`CircuitManager.start()` bootstraps the SignalR circuit for a Blazor Server/Global app. Before doing any work it checks `isDisposedOrDisposing()`, which is true once `_disposePromise` is set (i.e. `dispose()` has been called). Starting a circuit that is already tearing down is a lifecycle violation, so it throws immediately rather than producing a half-initialized connection.
Solutions
- Guard any custom `start`/reconnect call with `isDisposedOrDisposing()` before invoking it.
- Avoid calling `start()` more than once — it is idempotent via `_startPromise`, so reuse the existing promise instead of re-driving startup.
- Cancel pending reconnect timers in your `dispose`/`onCircuitClosed` handler so they cannot fire after disposal.
- For bfcache, listen for `pageshow` with `persisted` and do not re-start a disposed circuit; let a fresh page load build a new one.
Example fix
// before
async function tryRestart(cm: CircuitManager) {
await cm.start(); // throws if already disposing
}
// after
async function tryRestart(cm: CircuitManager) {
if (cm.isDisposedOrDisposing()) return;
await cm.start();
} Defensive patterns
Strategy: validation
Validate before calling
function safeStart(cm: CircuitManager) {
return cm.isDisposedOrDisposing() ? Promise.resolve(false) : cm.start();
} Try / catch
try { await cm.start(); }
catch (e) { if (/disposed circuit/.test(e.message)) { /* circuit gone, do not restart */ return; } throw e; } Prevention
- Check isDisposedOrDisposing() before calling start/reconnect.
- Reuse the existing start promise rather than re-driving start.
- Cancel pending reconnect timers on dispose.
- Handle bfcache pageshow(persisted) without re-starting a disposed circuit.
When it happens
Trigger: Calling `circuitManager.start()` (directly or via a re-entrant `Blazor.start`/reconnect path) after `dispose()` has begun — `_disposePromise` is defined, so `isDisposedOrDisposing()` returns true.
Common situations: A tab being closed/unloaded while a manual reconnect or late `start()` is queued; SPA fast-back/forward caches (bfcache) replaying startup after the page already tore down; custom reconnection logic calling `start` in a retry loop without checking disposal; hot reload or render-mode switching disposing the old circuit while a deferred start runs.
Related errors
- Circuit host not initialized.
- Cannot start the circuit until Blazor Server has started.
- Circuit state is already in progress
- Circuit state is in progress
- Circuit state is not in progress
AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11).
Data as JSON: /api/errors/906c5e7161c566a3.
Report an issue: GitHub.
Appendix: source
Thrown at src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts:88
componentManager: RootComponentManager<ServerComponentDescriptor>,
appState: string,
options: CircuitStartOptions,
logger: ConsoleLogger,
eventRegistry: JSEventRegistry,
) {
this._circuitId = undefined;
this._applicationState = appState;
this._componentManager = componentManager;
this._options = options;
this._logger = logger;
this._eventRegistry = eventRegistry;
this._renderQueue = new RenderQueue(this._logger);
this._dispatcher = DotNet.attachDispatcher(this);
}
public start(): Promise<boolean> {
if (this.isDisposedOrDisposing()) {
throw new Error('Cannot start a disposed circuit.');
}
if (!this._startPromise) {
this._startPromise = this.startCore();
}
return this._startPromise;
}
public updateRootComponents(operations: string, serverState: string): Promise<void> | undefined {
if (this._isFirstRender) {
this._isFirstRender = false;
return this._connection?.send('UpdateRootComponents', operations, this._applicationState);
} else {
return this._connection?.send('UpdateRootComponents', operations, serverState);
}
}
View on GitHub (pinned to 3600ca084e)