Bug (backend): cacheWithTTL marks cache fresh before fetch resolves — failed fetch serves stale data for full TTL, no in-flight dedup
Author: priyanshu1976Created Aug 26, 2026Updated Aug 26, 2026
Did you clear cache before opening an issue?
- I have cleared my cache
Is there an existing issue for this?
- I have searched the existing open and closed issues
Does the issue happen when logged in?
N/A
Does the issue happen when logged out?
N/A (backend source bug)
Does the issue happen in incognito mode when logged in?
N/A
Does the issue happen in incognito mode when logged out?
N/A
Issue details
Current Behavior
cacheWithTTL in backend/src/utils/ttl-cache.ts marks the cache as fresh before the fetch resolves:
// backend/src/utils/ttl-cache.ts:20-26
return async () => {
if (lastFetchTime < Date.now() - ttlMs) {
lastFetchTime = Date.now(); // updated before await completes
cache = await fn();
}
return cache;
};Consequences:
- Failed fetch poisons the cache for the full TTL. If
fn()rejects, the rejection propagates to that caller, butlastFetchTimehas already been advanced — every subsequent call within the TTL returns stale cached data instead of retrying. This utility backs the PSA endpoint (controllers/psa.ts), so one failed upstream fetch serves stale content for the whole TTL window. - No in-flight promise dedup. When the TTL expires under concurrent requests, all callers run
fn()simultaneously (thundering herd) since nothing records the pending promise.
Expected Behavior
Only advance lastFetchTime after a successful fetch, and dedupe concurrent calls by caching the promise itself:
let lastFetchTime = 0;
let cache: T | undefined;
let inflight: Promise<T> | undefined;
return async () => {
if (lastFetchTime < Date.now() - ttlMs) {
inflight ??= fn()
.then((result) => {
cache = result;
lastFetchTime = Date.now();
return result;
})
.finally(() => {
inflight = undefined;
});
return inflight;
}
return cache;
};Steps To Reproduce
- Call a
cacheWithTTL-wrapped getter whosefnthrows. - Call again within the TTL — stale data is returned with no retry until TTL expiry.
Environment
- Backend,
master@ 91bd24bb8
Source: monkeytypegame/monkeytype