fix(provider): discard stale drinks responses on rapid festival switch - #263
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes a race condition where a slower in-flight drinks fetch could overwrite a newer festival's drinks. Introduces a monotonically incremented _drinksLoadToken captured at the start of each loadDrinks / setFestival call; responses (success and error paths) are discarded when the token no longer matches the latest one, and the trailing _isLoading = false / notifyListeners() is also gated on token equality. Adds a test that races two setFestival calls.
Changes:
- Add
_drinksLoadTokencounter incremented on every new load. - Gate success, catch, and finally blocks in
loadDrinksand_loadDrinksInternalon token equality; thread token through_loadDrinksInternal(int token). - Add a race-condition test for rapid festival switching.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| lib/providers/beer_provider.dart | Adds load-token mechanism to discard stale drink responses on rapid festival switches. |
| test/beer_provider_test.dart | Adds a test intended to verify that the last-selected festival's drinks win when two setFestival calls race. |
| if (festival.id == 'cbf2024') { | ||
| await Future.delayed(const Duration(milliseconds: 100)); | ||
| return [drinkA]; | ||
| } | ||
| return [drinkB]; | ||
| }); | ||
|
|
||
| await provider.initialize(); | ||
|
|
||
| // Switch to A (slow), then immediately to B (fast) — don't await A | ||
| final futureA = provider.setFestival(festivalA); | ||
| final futureB = provider.setFestival(festivalB); | ||
| await Future.wait([futureA, futureB]); | ||
|
|
||
| // B's result must win; A's stale response must be discarded | ||
| expect(provider.currentFestival.id, 'cbf2025'); | ||
| expect(provider.drinks.length, 1); | ||
| expect(provider.drinks.first.name, 'Ale B'); |
| Future<void> _loadDrinksInternal(int token) async { | ||
| try { | ||
| // Repository returns drinks with favorites and ratings already populated | ||
| _allDrinks = await _drinkRepository!.getDrinks(currentFestival); | ||
| final drinks = await _drinkRepository!.getDrinks(currentFestival); | ||
| if (token != _drinksLoadToken) return; | ||
| _allDrinks = drinks; |
LCOV of commit
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
🚀 Cloudflare Pages PreviewYour preview deployment is ready! Preview URL: https://claude-analyze-repo-weakness.staging-cambeerfestival.pages.dev This preview will be automatically updated when you push new commits to this PR. |
Rapid festival switches could result in the slower first request overwriting the faster second request's results, showing the wrong festival's drinks. Fix by incrementing a load token on every new drinks load (setFestival, loadDrinks) and discarding any response whose token no longer matches the current one. Adds a test that verifies the last-selected festival always wins when two setFestival calls race and the first is slower. https://claude.ai/code/session_01P4MUavTmHYCgaCTKVhEFf4
Two issues identified in Copilot review of #263: 1. _loadDrinksInternal now accepts an explicit Festival parameter instead of reading the live currentFestival getter. After the awaits in setFestival (analytics, persist), a concurrent setFestival call could overwrite _currentFestival before getDrinks ran, causing both loads to fetch for the same festival and making the mock's per-festival delay logic ineffective. 2. Race condition test was a no-op: initialize() set _currentFestival to festivalA (cbf2024, the defaultFestivalId), so setFestival(festivalA) hit the early-return guard and never ran. Added festivalC (cbf2023) as the default so both race competitors actually initiate a getDrinks call.
c135987 to
1ba1593
Compare
🚀 Cloudflare Pages PreviewYour preview deployment is ready! Preview URL: https://claude-analyze-repo-weakness.staging-cambeerfestival.pages.dev This preview will be automatically updated when you push new commits to this PR. |
Rapid festival switches could result in the slower first request
overwriting the faster second request's results, showing the wrong
festival's drinks. Fix by incrementing a load token on every new
drinks load (setFestival, loadDrinks) and discarding any response
whose token no longer matches the current one.
Adds a test that verifies the last-selected festival always wins when
two setFestival calls race and the first is slower.
https://claude.ai/code/session_01P4MUavTmHYCgaCTKVhEFf4