3.7 KiB
Mark All As Read - Cache Issue Analysis
Date: 2026-01-01
Issue: After marking all as read, list is empty but count still shows 66
🔍 Problem Analysis
Current Flow
- User clicks "Mark all as read"
markAllAsRead()is called- Fetches notifications:
this.getNotifications(userId, 1, 1000)- ⚠️ PROBLEM: This goes through
NotificationService.getNotifications() - ⚠️ PROBLEM: Which uses CACHED data if available
- ⚠️ PROBLEM: Cached notifications still have
isRead: false
- ⚠️ PROBLEM: This goes through
- Filters unread: Gets 66 unread from cached data
- Marks each as read: Calls Leantime API for each
- Invalidates cache: After marking completes
- Count is fetched: But might use stale cache or be fetched before invalidation
The Issue
Cache Race Condition:
markAllAsReaduses cached notifications (which are stale)- Marks them as read in Leantime
- Invalidates cache
- But count might be fetched from cache before invalidation completes
- Or count cache might not be properly invalidated
Why List is Empty:
- After marking, all notifications are read
- List might filter to show only unread
- So list is empty (correct behavior)
- But count still shows 66 (stale cache)
🔧 Root Causes
1. Using Cached Data in markAllAsRead
Current Code:
// In markAllAsRead
const allNotifications = await this.getNotifications(userId, 1, 1000);
Problem: getNotifications() uses cache, so we're working with stale data.
Solution: Fetch directly from Leantime API, bypassing cache.
2. Cache Invalidation Timing
Current Flow:
- Mark all as read (uses cached data)
- Invalidate cache
- Count is fetched (might use stale cache if fetched too soon)
Problem: Race condition between invalidation and count fetch.
Solution:
- Invalidate cache before marking (or fetch fresh data)
- Force immediate count refresh after marking
- Add delay before count fetch to ensure cache is cleared
3. Count Cache Not Properly Invalidated
Current Code:
if (success) {
await this.invalidateCache(userId);
}
Problem: If markAllAsRead fails partially, cache might not be invalidated.
Solution: Always invalidate cache, even on partial success.
✅ Recommended Fixes
Fix 1: Bypass Cache in markAllAsRead
Change: Fetch notifications directly from Leantime API, not through cached service.
Implementation:
- Add a method to fetch notifications directly from adapter (bypassing cache)
- Or add a
forceRefreshparameter togetNotifications - Or fetch directly in
markAllAsReadusing Leantime API
Fix 2: Always Invalidate Cache
Change: Invalidate cache even if some notifications fail to mark.
Implementation:
- Invalidate cache if any notifications were successfully marked
- Not just if all succeeded
Fix 3: Force Fresh Count After Marking
Change: After marking, force an immediate fresh count fetch.
Implementation:
- After
markAllAsReadcompletes, immediately callgetNotificationCount()with cache bypass - Or add a delay before count fetch to ensure cache is cleared
📊 Expected Behavior After Fixes
After Mark All As Read
Before:
- List: Empty (all read) ✅
- Count: 66 (stale cache) ❌
After:
- List: Empty (all read) ✅
- Count: 0 (fresh data) ✅
🎯 Next Steps
- Fix cache usage in
markAllAsRead: Fetch fresh data, not cached - Improve cache invalidation: Always invalidate, even on partial success
- Force count refresh: Immediately fetch fresh count after marking
- Test: Verify count updates correctly after marking
Status: Analysis complete. Ready to implement fixes.