Awesome Reviewers expert instructions

domains / / CLINE/CLINE

Async race ownership rules

When multiple async operations can overlap with replacement/teardown (UI unmount, session rebuild, polling vs actions, stop/dispose, in-flight RPCs), require explicit *ownership* and *lifecycle binding*:

raw .md Concurrency TypeScript

When multiple async operations can overlap with replacement/teardown (UI unmount, session rebuild, polling vs actions, stop/dispose, in-flight RPCs), require explicit ownership and lifecycle binding:

1) Claim ownership synchronously before the first await

  • If there is a “pending” operation that others can also resolve (e.g., pending question/answer), assign/consume the claim immediately, then await delivery.
  • This prevents a second submission from resolving the same pending item while the first is mid-await.

2) Bind every async result to an identity token (generation / requestId / sessionId / tabId)

  • Capture a token at the start of the async operation.
  • Before mutating shared state, verify the token still matches the current owner (or that the resource is still alive and not disposed).

3) Serialize or coalesce work per scope

  • For recurring reads (polling) and triggers (actions, refresh reasons), ensure only one inflight request per (resource, scope) runs at a time.
  • If a change happens while inflight, remember it and run exactly once more after the current request completes.

4) Cancellation-aware completion + deterministic cleanup

  • If callers can cancel/stop/teardown, make sure cancellation is observed during waits/sleeps and that cleanup (listeners, inherited pipes, timers) happens when the caller settles.

Code sketch (claim-before-await + generation + serial coalescing)

let pending: null | (() => void) = null;
let pendingToken = 0;
let lastApplied = 0;

async function resolvePendingWithAttachments(
  tokenAtCall: number,
  deliver: () => Promise<void>,
): Promise<boolean> {
  const resolve = pending;
  if (!resolve) return false;

  // Claim synchronously
  pending = null;
  const myToken = ++pendingToken;

  try {
    await deliver();
    // Bind to identity: only apply if still current
    if (myToken !== pendingToken) return true;
    lastApplied = tokenAtCall;
    resolve();
    return true;
  } catch {
    // Release the claim if delivery fails so retries can happen
    if (myToken === pendingToken) pending = resolve;
    throw;
  }
}

// Polling/action coalescing pattern
let inflight: Promise<void> | null = null;
let rerunRequested = false;

async function refresh(reason: string) {
  if (!enabled) return;
  if (inflight) { rerunRequested = true; return; }
  inflight = (async () => {
    do {
      rerunRequested = false;
      const currentReason = reason;
      const response = await fetchState(currentReason);
      apply(response); // only after request completes
    } while (rerunRequested);
  })();
  try { await inflight; } finally { inflight = null; }
}

Adopting this standard prevents:

  • late stragglers overwriting current state,
  • replacement requests “stealing” pending resolution,
  • polls/actions returning out-of-order results,
  • teardown leaving leaked resources (timers/pipes/listeners) alive after the caller is done.