📦 deps(thirdparty): update snapshots

This commit is contained in:
ci[bot]
2026-05-29 08:33:53 +00:00
parent fdb52f1e96
commit 06e0d13d57
1615 changed files with 232858 additions and 0 deletions
@@ -0,0 +1,398 @@
# Race Condition Debugging Session
This example demonstrates debugging an intermittent issue using the four-stage framework.
## The Bug
User reports: "Cache occasionally returns stale data. Works most of the time, but sometimes shows old values after updates."
## Stage 1: Collect Evidence
**Task state**: "Collecting evidence" (in_progress)
### Read Error Description
No error messages — behavior is wrong but silent:
- Expected: Updated value from cache
- Actual: Old value returned (intermittently)
- Frequency: ~5% of requests after update
### Reproduce Consistently
Initial attempts fail — bug is intermittent:
- Sometimes works correctly
- Sometimes returns stale data
- No obvious pattern
Run test 100 times to find pattern:
```bash
for i in {1..100}; do
npm test -- --grep "cache update" > /dev/null || echo "Failed: $i"
done
# Results:
# Failed: 3
# Failed: 17
# Failed: 28
# Failed: 41
# Failed: 59
# Failed: 72
# Failed: 88
# Failed: 94
```
Failure rate: ~8% (8 failures out of 100 runs)
### Check Recent Changes
```bash
git log --since="1 week ago" --oneline src/cache/
# f8d3c21 Optimize cache reads with async/await
# 9a2b741 Add cache prewarming on startup
# c4e5d67 Implement cache TTL refresh
```
Recent changes to cache implementation — possible cause.
### Gather Evidence with Timestamps
Add detailed timing logs:
```typescript
export async function updateCache(key: string, value: any): Promise<void> {
console.log(`[${Date.now()}] updateCache START: ${key}`);
await cache.set(key, value);
console.log(`[${Date.now()}] updateCache cache.set COMPLETE: ${key}`);
await invalidateRelated(key);
console.log(`[${Date.now()}] updateCache invalidateRelated COMPLETE: ${key}`);
}
export async function getCache(key: string): Promise<any> {
console.log(`[${Date.now()}] getCache START: ${key}`);
const value = await cache.get(key);
console.log(`[${Date.now()}] getCache COMPLETE: ${key}`, value);
return value;
}
```
### Timeline Analysis
Captured logs from a failure:
```
[1702500123450] updateCache START: user:123
[1702500123455] updateCache cache.set COMPLETE: user:123
[1702500123456] getCache START: user:123 <-- Read started
[1702500123458] getCache COMPLETE: user:123 [OLD] <-- Returned old value
[1702500123460] updateCache invalidateRelated COMPLETE: user:123
```
**Key finding**: `getCache` started (1456) AFTER `cache.set` completed (1455) but BEFORE `invalidateRelated` completed (1460). Returned stale data.
**Transition**: Evidence gathered showing timing issue. Mark "Collect Evidence" complete, add "Isolate Variables" as in_progress.
## Stage 2: Isolate Variables
**Task state**: "Isolating variables" (in_progress)
### Find Working Examples
Check how other cache operations handle this:
```typescript
// Working example - authentication cache
export async function updateAuthCache(userId: string, token: string): Promise<void> {
const key = `auth:${userId}`;
// Atomic operation - no race window
await cache.set(key, token, { ttl: 3600 });
}
```
Difference: No separate invalidation step, single atomic operation.
### Read Recent Optimization Commit
The optimization commit (f8d3c21):
**Before** (synchronous, blocking):
```typescript
export function updateCache(key: string, value: any): void {
cache.set(key, value); // Synchronous
invalidateRelated(key); // Synchronous
// Both complete before function returns
}
```
**After** (async, non-blocking):
```typescript
export async function updateCache(key: string, value: any): Promise<void> {
await cache.set(key, value); // Async - completes
await invalidateRelated(key); // Async - still pending
// Function returns here, but invalidation still running
}
```
### Identify Differences
Working code:
- Single atomic operation
- No race window
- Consistent state
Broken code:
- Two-step process
- Race window between set and invalidate
- Inconsistent state possible
### Understand the Flow
1. `updateCache('user:123', newData)` starts
2. `cache.set` completes — new data in cache
3. **Race window starts**
4. `getCache('user:123')` called from different request
5. Reads from cache — gets new data
6. BUT cache has stale related entries
7. Related entries override with old data
8. **Race window ends**
9. `invalidateRelated` completes
**Root cause hypothesis forming**: The async optimization introduced a race window between setting the value and invalidating related entries.
**Transition**: Pattern identified. Mark "Isolate Variables" complete, add "Formulate Hypotheses" as in_progress.
## Stage 3: Formulate Hypotheses & Test
**Task state**: "Formulating hypotheses" (in_progress)
### Form Hypothesis
**Hypothesis**: "Cache returns stale data because the async optimization (f8d3c21) introduced a race window. When `updateCache` sets a value but hasn't yet invalidated related entries, concurrent `getCache` calls can read the new value while related entries are still stale, causing those stale entries to be returned instead."
Evidence:
- Timeline shows `getCache` called during race window
- Worked before async optimization
- Fails ~8% of time (when timing hits race window)
- Working code uses atomic operations
**Transition**: Hypothesis formed. Mark "Formulate Hypotheses" complete, add "Test Hypothesis" as in_progress.
**Task state**: "Testing hypothesis" (in_progress)
### Design Minimal Test
Add artificial delay to widen race window and make bug consistent:
```typescript
export async function updateCache(key: string, value: any): Promise<void> {
await cache.set(key, value);
// TESTING: Widen race window
await new Promise(resolve => setTimeout(resolve, 100));
await invalidateRelated(key);
}
```
### Execute Test
Run test 100 times with widened race window:
```bash
for i in {1..100}; do
npm test -- --grep "cache update" > /dev/null || echo "Failed: $i"
done
# Results: 67 failures (67%)
```
Failure rate increased dramatically with wider race window. Confirms timing-based hypothesis.
### Test Solution
Make operations atomic by ensuring no reads during update:
```typescript
export async function updateCache(key: string, value: any): Promise<void> {
// Acquire lock to prevent concurrent reads
const lock = await cache.lock(key);
try {
await cache.set(key, value);
await invalidateRelated(key);
} finally {
await lock.release();
}
}
```
Run 100 times:
```bash
# Results: 0 failures (0%)
```
**Result**: Hypothesis confirmed. Lock prevents race condition.
**Transition**: Solution verified. Mark "Test Hypothesis" complete, add "Verify Fix" as in_progress.
## Stage 4: Verify Fix
**Task state**: "Verifying fix" (in_progress)
### Create Failing Test
```typescript
describe('updateCache race condition', () => {
it('prevents stale data during concurrent update and read', async () => {
// Setup initial data
await cache.set('user:123', 'old-value');
await cache.set('related:123', 'old-related');
// Simulate race: update and read concurrently
const [updateResult, readResult] = await Promise.all([
updateCache('user:123', 'new-value'),
getCache('user:123'),
]);
// Read should either see old (before update) or new (after update)
// but never a mix of new + stale related
const finalValue = await getCache('user:123');
expect(finalValue).not.toBe('old-related'); // Should not see stale
expect(['old-value', 'new-value']).toContain(finalValue);
});
});
```
Test fails without lock, passes with lock.
### Implement Fix
Lock-based solution has performance cost. Consider alternative:
```typescript
export async function updateCache(key: string, value: any): Promise<void> {
// Generate update ID to track consistency
const updateId = crypto.randomUUID();
// Set all values atomically with transaction
await cache.multi()
.set(key, value)
.set(`${key}:updateId`, updateId)
.invalidate(getRelatedKeys(key))
.exec();
}
```
This uses cache transaction (multi/exec) for atomicity without explicit locking.
### Verify Fix Works
Run comprehensive tests:
```bash
# Run 1000 times to ensure no failures
for i in {1..1000}; do
npm test -- --grep "cache update" > /dev/null || echo "Failed: $i"
done
# Results: 0 failures
```
All tests pass:
- Race condition test passes
- Existing tests pass
- Load test shows no performance degradation
- No deadlocks or timeouts
### Document Fix
```typescript
/**
* Updates cache value atomically with related entries.
*
* Bug fix (2024-12-12): Prevented race condition during updates.
* Root cause: Async optimization (f8d3c21) introduced race window
* between setting value and invalidating related entries. Concurrent
* reads during this window could return inconsistent data.
*
* Prevention: Use cache transactions (multi/exec) to ensure atomicity.
* All cache updates and invalidations complete together or not at all.
*/
export async function updateCache(key: string, value: any): Promise<void> {
const updateId = crypto.randomUUID();
await cache.multi()
.set(key, value)
.set(`${key}:updateId`, updateId)
.invalidate(getRelatedKeys(key))
.exec();
}
```
### Additional Defense
Add monitoring to detect inconsistency:
```typescript
export async function getCache(key: string): Promise<any> {
const [value, updateId] = await Promise.all([
cache.get(key),
cache.get(`${key}:updateId`),
]);
// Verify related entries have matching updateId
const relatedKeys = getRelatedKeys(key);
for (const relatedKey of relatedKeys) {
const relatedUpdateId = await cache.get(`${relatedKey}:updateId`);
if (relatedUpdateId !== updateId) {
// Log inconsistency for monitoring
console.warn(`Cache inconsistency detected: ${key}`);
// Trigger cache refresh
await refreshCache(key);
}
}
return value;
}
```
**Transition**: Fix verified and monitoring added. Mark "Verify Fix" complete.
**Task state**: All tasks completed.
## Summary
**What broke**: Cache occasionally returned stale data after updates
**Root cause**: Async optimization introduced race window between setting value and invalidating related entries. Concurrent reads during this window could read new value but get stale related data.
**The fix**:
1. Use cache transactions (multi/exec) for atomic updates
2. All updates and invalidations complete atomically
3. Added monitoring to detect inconsistencies
**Prevention**:
- Use atomic operations for multi-step cache updates
- Test concurrent operations explicitly
- Add timing logs to expose race conditions
- Monitor for cache inconsistency in production
## Lessons
- Intermittent bugs require many test runs to find pattern
- Timing logs revealed race window
- Widening race window made bug consistent for testing
- Async optimization can introduce race conditions
- Atomic operations eliminate race windows
- Transaction support in cache library enables atomicity without locks
@@ -0,0 +1,310 @@
# Runtime Error Debugging Session
This example demonstrates systematic debugging of a runtime error using the four-stage framework.
## The Bug
User reports: "Application crashes when processing certain user profiles with `TypeError: Cannot read property 'email' of undefined`"
## Stage 1: Collect Evidence
**Task state**: "Collecting evidence" (in_progress)
### Read Error Message
```
TypeError: Cannot read property 'email' of undefined
at formatUserDisplay (src/users/formatter.ts:42:23)
at UserProfile (src/components/UserProfile.tsx:18:15)
at processProfiles (src/services/profileService.ts:67:8)
```
Stack trace points to line 42 in formatter.ts accessing `.email` on undefined value.
### Reproduce Consistently
Steps to reproduce:
1. Load user profile page
2. Navigate to profile ID: `user-incomplete-123`
3. Error occurs consistently for this user
4. Works fine for other users (e.g., `user-complete-456`)
### Check Recent Changes
```bash
git log --since="2 days ago" --oneline src/users/formatter.ts
# No recent changes to formatter.ts
git log --since="2 days ago" --oneline src/services/
# 3d7a921 Optimize profile fetch to reduce API calls
```
Recent commit optimized profile fetching. Potential cause.
### Gather Evidence
Added logging to formatter.ts:
```typescript
export function formatUserDisplay(user: User): string {
console.log('[DEBUG] formatUserDisplay input:', JSON.stringify(user));
// Line 42 - where error occurs
const email = user.email.toLowerCase();
// ...
}
```
Output:
```
[DEBUG] formatUserDisplay input: {"id":"user-incomplete-123","name":"Test User"}
TypeError: Cannot read property 'email' of undefined
```
**Key finding**: User object missing `email` field entirely.
### Trace Data Flow Backward
Where does user object come from?
```typescript
// profileService.ts:67
const display = formatUserDisplay(profile.user);
```
Check profile.user:
```typescript
console.log('[DEBUG] profile object:', JSON.stringify(profile));
// Output: {"id":"prof-123","user":{"id":"user-incomplete-123","name":"Test User"}}
```
User object from API is missing email field.
**Transition**: Evidence gathered, reproduction confirmed. Mark "Collect Evidence" complete, add "Isolate Variables" as in_progress.
## Stage 2: Isolate Variables
**Task state**: "Isolating variables" (in_progress)
### Find Working Examples
Check working user profile:
```typescript
// user-complete-456 returns:
{"id":"user-complete-456","name":"Complete User","email":"user@example.com"}
// user-incomplete-123 returns:
{"id":"user-incomplete-123","name":"Test User"}
```
Difference: Some users don't have email field in API response.
### Read Reference Implementation
Found similar code that handles missing fields:
```typescript
// src/auth/userValidator.ts
export function validateUser(user: Partial<User>): User {
if (!user.email) {
throw new Error('User must have email');
}
return user as User;
}
```
This validates email exists before using it.
### Identify Differences
Working code:
- Validates email exists before access
- Handles Partial<User> type
- Throws clear error if missing
Broken code:
- Assumes email always exists
- Direct property access
- No validation
### Understand Dependencies
Recent optimization commit changed from:
```typescript
// Old: Fetched full user details
const user = await fetchFullUser(userId);
```
To:
```typescript
// New: Uses cached profile data
const user = profile.user; // May be incomplete
```
**Root cause hypothesis forming**: Optimization changed data source from full user fetch to cached profile, which may have incomplete user data.
**Transition**: Key differences identified. Mark "Isolate Variables" complete, add "Formulate Hypotheses" as in_progress.
## Stage 3: Formulate Hypotheses & Test
**Task state**: "Formulating hypotheses" (in_progress)
### Form Hypothesis
**Hypothesis**: "The function fails because the optimization commit (3d7a921) changed from fetching full user objects to using cached profile data, which doesn't include email for users who haven't completed onboarding. The formatter assumes email always exists, causing undefined access."
Evidence supporting hypothesis:
- Error only occurs for specific users (incomplete profiles)
- Started after optimization commit
- Working users have email, broken users don't
- API response shows missing email field
**Transition**: Hypothesis formed. Mark "Formulate Hypotheses" complete, add "Test Hypothesis" as in_progress.
**Task state**: "Testing hypothesis" (in_progress)
### Design Minimal Test
Temporarily revert optimization to test hypothesis:
```typescript
// Change profile.user back to full fetch
const user = await fetchFullUser(profile.userId);
const display = formatUserDisplay(user);
```
### Execute Test
Run with reverted code:
- Error no longer occurs
- All users display correctly (including user-incomplete-123)
- Full fetch includes all required fields
**Result**: Hypothesis confirmed. The optimization exposed assumption that all user objects have email.
**Transition**: Hypothesis confirmed, ready to implement fix. Mark "Test Hypothesis" complete, add "Verify Fix" as in_progress.
## Stage 4: Verify Fix
**Task state**: "Verifying fix" (in_progress)
### Create Failing Test
```typescript
describe('formatUserDisplay', () => {
it('handles users without email gracefully', () => {
const incompleteUser = {
id: 'user-123',
name: 'Test User',
// email intentionally missing
};
// This currently throws, should handle gracefully
expect(() => formatUserDisplay(incompleteUser)).toThrow(
'User email is required'
);
});
});
```
Test fails as expected (throws TypeError instead of clear error).
### Implement Fix
Two options identified:
1. Fetch full user data (removes optimization)
2. Handle missing email in formatter (preserves optimization)
Choose option 2 to preserve optimization and add defensive validation:
```typescript
export function formatUserDisplay(user: User): string {
// Input validation
if (!user.email || typeof user.email !== 'string') {
throw new Error(`User email is required for display. User ID: ${user.id}`);
}
const email = user.email.toLowerCase();
const name = user.name || 'Unknown';
return `${name} <${email}>`;
}
```
### Verify Fix Works
Run tests:
- Failing test now passes (throws clear error message)
- All existing tests pass
- Manual reproduction: Clear error message instead of crash
- Profile service can catch error and handle incomplete users
### Document Fix
```typescript
/**
* Formats user information for display.
*
* Bug fix (2024-12-12): Added email validation.
* Root cause: Profile optimization (3d7a921) changed data source
* from full user fetch to cached profile data. Some users haven't
* completed onboarding and lack email field.
* Prevention: Always validate required fields before access.
*
* @throws {Error} If user.email is missing or invalid
*/
export function formatUserDisplay(user: User): string {
// ...
}
```
### Additional Defense
Update profile service to handle incomplete users:
```typescript
// src/services/profileService.ts
try {
const display = formatUserDisplay(profile.user);
return { ...profile, display };
} catch (error) {
// User hasn't completed onboarding, fetch full details
const fullUser = await fetchFullUser(profile.userId);
const display = formatUserDisplay(fullUser);
return { ...profile, display };
}
```
**Transition**: Fix verified and deployed. Mark "Verify Fix" complete.
**Task state**: All tasks completed.
## Summary
**What broke**: TypeError when accessing email property on undefined
**Root cause**: Optimization changed data source from full user fetch to cached profile data. Cached data incomplete for users who haven't finished onboarding.
**The fix**:
1. Added input validation in formatter
2. Profile service falls back to full fetch for incomplete users
3. Clear error messages guide developers
**Prevention**:
- Always validate required fields before access
- Consider data completeness when optimizing data fetching
- Add tests for incomplete/partial data scenarios
## Lessons
- Error message stack trace led directly to problem location
- Recent changes (git log) identified likely cause
- Comparing working vs broken cases revealed pattern
- Single hypothesis tested with minimal change
- Fix addresses root cause while preserving optimization
- Defensive validation at multiple layers prevents recurrence