dotnet/aspnetcore · error · Error
Circuit state ${this._stateName} is already in progress
Error message
Circuit state ${this._stateName} is already in progress What it means
`CircuitState.reset()` begins a new async operation (pause/resume/disconnect) by creating a fresh promise via `Promise.withResolvers`. It refuses if `_promise` already exists, because each state machine allows only one in-flight operation at a time — overlapping resets would lose track of resolvers. The thrown message names the state for diagnosis.
Source
Thrown at src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts:687
public constructor(
private _stateName: string,
_initialValue?: T,
private _resetValue?: T
) {
this._lastValue = _initialValue;
}
private _promise?: Promise<T>;
private _resolve?: (value: T | PromiseLike<T>) => void;
private _reject?: (reason: any) => void;
private _lastValue?: T;
public reset(): void {
if (this._promise) {
throw new Error(`Circuit state ${this._stateName} is already in progress`);
}
const { promise, resolve, reject } = Promise.withResolvers<T>();
this._promise = promise;
this._resolve = resolve;
this._reject = reject;
this._lastValue = this._resetValue;
}
public complete(value: T): void {
if (!this._resolve) {
throw new Error(`Circuit state ${this._stateName} not initialized`);
}
const resolve = this._resolve;
this._lastValue = value;
this._promise = undefined;View on GitHub (pinned to 3600ca084e)
Solutions
- Before starting a new operation, await the existing one via `currentProgress()` or check `isInprogress()`.
- Serialize pause/resume/disconnect through a single queue in your orchestration code so resets never overlap.
- Debounce rapid user actions that map to circuit state changes.
- Use the framework-provided `pauseCircuit`/`resume`/`disconnect` entry points which already check progress, rather than driving `reset()` indirectly through re-entrant calls.
Example fix
// before
async function toggle(cm: CircuitManager) {
await cm.pause(); // user clicks again before pause settles
await cm.pause(); // second reset -> throw
}
// after
async function toggle(cm: CircuitManager) {
await cm.pause();
await cm.resume(); // sequence, never overlap
} Defensive patterns
Strategy: validation
Validate before calling
function safeReset<T>(s: { isInprogress(): boolean; reset(): void }) {
if (s.isInprogress()) throw new Error('operation already in progress');
s.reset();
} Try / catch
try { state.reset(); }
catch (e) { if (/already in progress/.test(e.message)) { await state.currentProgress(); return; } throw e; } Prevention
- Await the in-flight operation (currentProgress()) before resetting.
- Serialize pause/resume/disconnect through a single queue.
- Debounce rapid user-driven state changes.
- Use the framework entry points that already check progress.
When it happens
Trigger: Calling `reset()` on a `CircuitState` (pausing/resuming/disconnecting) while a previous operation started by an earlier `reset()` has not yet completed or failed.
Common situations: Two concurrent `pause()` calls; a reconnect triggered while a disconnect is mid-flight; user rapidly toggling states (pause/resume) faster than the server responds; a reconnection handler racing with an explicit disconnect.
Related errors
- Circuit state ${this._stateName} is in progress
- Circuit state ${this._stateName} not initialized
- Circuit state ${this._stateName} is not in progress
- Cannot start the circuit until Blazor Server has started.
- Cannot start a disposed circuit.
AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11).
Data as JSON: /api/errors/7cf41cd1366bbd8b.
Report an issue: GitHub.