NeahNew/MARK_ALL_READ_CACHE_ISSUE.md
2026-01-06 18:53:55 +01:00

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

  1. User clicks "Mark all as read"
  2. markAllAsRead() is called
  3. 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
  4. Filters unread: Gets 66 unread from cached data
  5. Marks each as read: Calls Leantime API for each
  6. Invalidates cache: After marking completes
  7. Count is fetched: But might use stale cache or be fetched before invalidation

The Issue

Cache Race Condition:

  • markAllAsRead uses 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:

  1. Mark all as read (uses cached data)
  2. Invalidate cache
  3. 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.


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 forceRefresh parameter to getNotifications
  • Or fetch directly in markAllAsRead using 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 markAllAsRead completes, immediately call getNotificationCount() 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

  1. Fix cache usage in markAllAsRead: Fetch fresh data, not cached
  2. Improve cache invalidation: Always invalidate, even on partial success
  3. Force count refresh: Immediately fetch fresh count after marking
  4. Test: Verify count updates correctly after marking

Status: Analysis complete. Ready to implement fixes.