You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Jul 11, 2026. It is now read-only.
The purchaseItem controller in shopController.js checks item.stock === 0 and then later decrements item.stock in two separate database operations. This is a classic Time-Of-Check / Time-Of-Use (TOCTOU) race condition: under concurrent purchases of the same limited-stock item, multiple requests can pass the stock check simultaneously and all proceed to decrement, resulting in the stock going negative and the item being sold more times than intended.
Current Behavior
// Check (read):if(item.stock===0){returnres.status(400)...}// ... many operations later ...// Use (write):item.soldCount+=1;if(item.stock>0){item.stock-=1;}awaititem.save();
If two users purchase the last unit of a stock-1 item simultaneously:
Both requests read stock = 1 → both pass the check
Both create a Purchase document and debit points
Both decrement stock → stock ends at -1
Why This Improvement Is Needed
Corrupts stock data, making inventory tracking unreliable.
Members can receive items that were already sold to someone else.
The bug is silent — no error is thrown and no log is generated.
This is a data integrity issue affecting the core reward system.
Proposed Solution
Replace the two-step check+decrement with a single atomic findOneAndUpdate call using MongoDB's $gt operator as a conditional guard:
constupdatedItem=awaitShopItem.findOneAndUpdate({_id: item._id,stock: {$gt: 0}},// only match if stock > 0{$inc: {soldCount: 1,stock: -1}},{new: true},);if(!updatedItem){// another concurrent request claimed the last unit — refund pointsreturnres.status(400).json({message: "Item sold out — please try again"});}
This is an atomic read-modify-write at the database level, making the race condition impossible.
Expected Outcome
Limited-stock items can never be oversold, regardless of concurrent load.
If two requests race for the last unit, exactly one succeeds and the other receives a clear error with a points refund.
soldCount always accurately reflects the number of successful purchases.
Additional Notes
Items with stock = -1 (unlimited) are unaffected.
The fix adds a best-effort points refund when the race is lost, protecting the member's balance.
Problem
The
purchaseItemcontroller inshopController.jschecksitem.stock === 0and then later decrementsitem.stockin two separate database operations. This is a classic Time-Of-Check / Time-Of-Use (TOCTOU) race condition: under concurrent purchases of the same limited-stock item, multiple requests can pass the stock check simultaneously and all proceed to decrement, resulting in the stock going negative and the item being sold more times than intended.Current Behavior
If two users purchase the last unit of a stock-1 item simultaneously:
stock = 1→ both pass the checkPurchasedocument and debit pointsstockends at-1Why This Improvement Is Needed
Proposed Solution
Replace the two-step check+decrement with a single atomic findOneAndUpdate call using MongoDB's $gt operator as a conditional guard:
This is an atomic read-modify-write at the database level, making the race condition impossible.
Expected Outcome
soldCountalways accurately reflects the number of successful purchases.Additional Notes