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

  1. Before starting a new operation, await the existing one via `currentProgress()` or check `isInprogress()`.
  2. Serialize pause/resume/disconnect through a single queue in your orchestration code so resets never overlap.
  3. Debounce rapid user actions that map to circuit state changes.
  4. 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

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


AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11). Data as JSON: /api/errors/7cf41cd1366bbd8b. Report an issue: GitHub.