Bug (backend): cacheWithTTL marks cache fresh before fetch resolves — failed fetch serves stale data for full TTL, no in-flight dedup
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 76/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
- Domain
- backend
Research direction
Start in backend/src/utils/ttl-cache.ts and review how cacheWithTTL is used by controllers/psa.ts. Verify the behavior for failed fetches and concurrent calls, then update the utility so successful fetches refresh the TTL, failures remain retryable, and concurrent requests share one in-flight fetch. Done means stale data is not served after a failed refresh and the upstream function runs only once for concurrent expired-cache calls.
Written by the indexing model from the issue text.
Description
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
- Dominant language
- TypeScript
- Stars
- 20.7k
- Forks
- 3.3k
- PR merge metrics
- No merged PRs in 30d
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from monkeytypegame/monkeytype
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
monkeytypegame/monkeytype#8366 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
monkeytypegame/monkeytype#8365 · 1 comment ·
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
monkeytypegame/monkeytype#8364 · 1 comment ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
monkeytypegame/monkeytype#8362 · 1 comment ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
monkeytypegame/monkeytype#8361 · 2 comments ·
All issues in monkeytypegame/monkeytype
Similar issues
-
calcite-components needs triage refactor
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Esri/calcite-design-system#15203 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 91/100
-
community first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Difficulty 1/5 Under an hour Newbie friendliness 95/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Automattic/studio#4908 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 90/100