Skip to content

Fix race conditions in queuePerItem promise lifecycle#67

Open
Copilot wants to merge 2 commits intomainfrom
copilot/fix-race-conditions-queue-per-item
Open

Fix race conditions in queuePerItem promise lifecycle#67
Copilot wants to merge 2 commits intomainfrom
copilot/fix-race-conditions-queue-per-item

Conversation

Copy link
Contributor

Copilot AI commented Feb 26, 2026

Two race conditions in queuePerItem cause promise coalescing to silently fail under concurrent access, producing duplicate task executions and stale results.

Race Condition 1 — Registration gap before sentinel

state.promises.set(key, promise) was called after task() inside runTask(). Any same-key call occurring synchronously within task() would find no entry and spawn a duplicate task.

Fix: Create a deferred sentinel promise and register it in state.promises before invoking task(), then forward the task result to the sentinel:

// Before: task() runs first, then state.promises.set()
promise = task().then(...);
state.promises.set(key, promise);

// After: sentinel registered before task() runs
const sentinel = new Promise<unknown>((res, rej) => { resolveSentinel = res; rejectSentinel = rej; });
state.promises.set(key, sentinel);          // registered before task()
task().then(result => { onComplete(true); resolveSentinel(result); }, ...);
return sentinel;

Race Condition 2 — forceRefresh chained promise not stored

When forceRefresh: true and a promise is in-flight, a chained promise was created and returned but never written back to state.promises. The next caller with forceRefresh: false would coalesce onto the stale original instead of the refresh chain.

Fix: One line — store the chained promise before returning:

state.promises.set(key, chained);
return chained;

Tests

Two new tests directly exercise each race condition, verifying no duplicate task execution and correct promise identity.

Type of Change

  • Bug fix
  • New feature
  • Refactor
  • Documentation

Checklist

  • npm run lint passes
  • npx tsc --noEmit passes
  • npm run test:run passes
  • npm run build succeeds
Original prompt

This section details on the original issue you should resolve

<issue_title>[Critical/Bug] Race conditions in queuePerItem promise lifecycle</issue_title>
<issue_description>## Description

Two related race conditions in the eval queue's queuePerItem function cause promise coalescing to fail under concurrent access.

Race Condition 1: Promise registration gap

lib/eval-queue.ts:341-438: Between the runTask() call (line ~415) and state.promises.set(key, promise) (line ~410 inside runTask), there's a window where the key isn't in state.promises. A concurrent call for the same key during this window bypasses coalescing entirely, causing duplicate work.

Race Condition 2: forceRefresh chained promise not stored

lib/eval-queue.ts:334-338: When forceRefresh is true, a new chained promise is created and returned to the caller, but it is NOT stored back into state.promises. A third caller with forceRefresh: false sees the stale original promise and gets outdated results.

Proposed Fix

  1. Register a sentinel promise in state.promises before execution begins
  2. When creating a forceRefresh chain, store the new promise back in state.promises so all subsequent callers see the refreshed version</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

- Race condition 1: register sentinel promise in state.promises before
  calling task() so concurrent same-key calls coalesce correctly even
  when they occur synchronously inside task()
- Race condition 2: store the forceRefresh chained promise back into
  state.promises so subsequent callers see the refreshed promise

Adds two targeted tests that directly validate each race condition fix.

Co-authored-by: NiveditJain <40313233+NiveditJain@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix race conditions in queuePerItem promise lifecycle Fix race conditions in queuePerItem promise lifecycle Feb 26, 2026
@NiveditJain NiveditJain marked this pull request as ready for review February 27, 2026 04:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Critical/Bug] Race conditions in queuePerItem promise lifecycle

2 participants