It has a check-then-act race condition.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | // ====================================================================== // Q: Spot the bug (also written by AI) // // It has a check-then-act race condition. Two concurrent orders both read // Available = 5 and each reserves 5 units. // Two fixes: an atomic update with ExecuteUpdateAsync (the best option here), or // optimistic concurrency with a row version (catch DbUpdateConcurrencyException // and retry or return false). // ====================================================================== // AI-generated code with the bug public async Task<bool> ReserveStock(string sku, int qty) { var item = await _db.Stock.FirstAsync(s => s.Sku == sku); if (item.Available >= qty) { item.Available -= qty; await _db.SaveChangesAsync(); return true; } return false; } // Fix 1: atomic update (best option here) var affected = await _db.Stock .Where(s => s.Sku == sku && s.Available >= qty) .ExecuteUpdateAsync(s => s.SetProperty(x => x.Available, x => x.Available - qty), ct); return affected == 1; // Fix 2: optimistic concurrency public class StockItem { public string Sku { get; set; } = default!; public int Available { get; set; } [Timestamp] public byte[] RowVersion { get; set; } = default!; // SQL Server // PostgreSQL: modelBuilder.Entity<StockItem>().Property<uint>("xmin").IsRowVersion(); } // ... catch (DbUpdateConcurrencyException) { /* retry or return false */ } |