Skip to content

fix: eliminate race condition in UserSheetProgress saveProgress#427

Open
Srejoye wants to merge 3 commits into
Canopus-Labs:mainfrom
Srejoye:fix/371-usersheetprogress-race-condition
Open

fix: eliminate race condition in UserSheetProgress saveProgress#427
Srejoye wants to merge 3 commits into
Canopus-Labs:mainfrom
Srejoye:fix/371-usersheetprogress-race-condition

Conversation

@Srejoye

@Srejoye Srejoye commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #371.

This PR resolves a race condition in saveProgress that could create duplicate UserSheetProgress documents for the same { userId, sheetId } pair under concurrent requests.

Previously, the controller followed a non-atomic read-then-write pattern:

  1. findOne({ userId, sheetId })
  2. If no document exists, create a new one.
  3. Otherwise, update the existing document.

When two requests arrived simultaneously, both could pass the findOne check before either write completed, resulting in duplicate progress documents.

Changes

  • Added a compound unique index on { userId: 1, sheetId: 1 } in the UserSheetProgress schema.
  • Replaced the read-then-write logic in saveProgress with a single atomic findOneAndUpdate() using:
    • upsert: true
    • new: true
    • setDefaultsOnInsert: true
  • Added graceful handling for duplicate key (E11000) errors by fetching and returning the existing document, making concurrent requests retry-safe.

Why this Fix

Using an atomic upsert removes the race window between checking for an existing document and creating one. The unique compound index guarantees that only one document can ever exist for a given { userId, sheetId } pair, even under heavy concurrent load.

This ensures:

  • Exactly one progress document per user/sheet pair.
  • No duplicate entries returned by getAllProgress.
  • Deterministic results from getProgress.
  • Safe behavior under concurrent requests.

Testing

  • Verified creating progress for a new sheet.
  • Verified updating an existing progress document.
  • Verified concurrent save requests result in only one document being created.
  • Verified duplicate-key (E11000) races are handled gracefully without creating duplicate records.

Impact

This change is backward compatible and does not modify the API contract. It only improves data consistency and eliminates duplicate progress documents caused by concurrent writes.

Comment thread backend/controllers/userSheetProgressController.js Fixed
Comment thread backend/controllers/userSheetProgressController.js Fixed
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.

[Bug]: Race condition in UserSheetProgress causes duplicate progress documents per user/sheet

2 participants