Skip to content

fix(dependencies): register one fallback backend under concurrent first AppCache calls - #91

Merged
allen0099 merged 1 commit into
masterfrom
fix/app-cache-init-race
Sep 25, 2026
Merged

allen0099 merged 1 commit into
masterfrom
fix/app-cache-init-race

Conversation

@allen0099

Copy link
Copy Markdown
Owner

Closes #76.

Problem

get_app_cache is a sync dependency, so FastAPI runs it in worker threads. In an app that never configured a backend, concurrent first requests each built a MemoryBackend and a CacheManager. The later set() replaced the earlier one, so for a while requests used caches that could not see each other's entries.

Change

  • New proxy.get_backend_or_fallback(): it returns the configured backend or registers a MemoryBackend. It re-checks under a threading.Lock, so every concurrent first caller gets the same instance.
  • @cache uses the same helper instead of its own copy of the fallback. The dependency and the decorator can no longer install competing fallbacks, even when one runs on the event loop and the other in a worker thread.
  • get_app_cache double-checks the manager under its own lock. It stays a sync dependency, so app.dependency_overrides users are unaffected.
  • The fast path, where a backend or manager is already set, takes no lock.

Tests

  • New test_get_app_cache_concurrent_first_calls_share_one_backend: 8 threads start together behind a barrier, with a slowed-down MemoryBackend constructor.
    • On the old code, the same test (patched at the old import site) fails with 8 == 1 managers.
    • With this change it passed 5 of 5 runs.
  • Full suite: 521 passed and 146 skipped. The skips are the Redis/Memcached live suites; no test servers were running. Coverage is 92%.

…st AppCache calls

get_app_cache is a sync dependency, so FastAPI runs it in worker threads.
In an app with no configured backend, concurrent first requests each built a
MemoryBackend and a CacheManager, and the later set() replaced the earlier,
leaving caches that could not see each other's entries.

The lazy fallback now lives in proxy.get_backend_or_fallback(), which
re-checks under a lock, and @cache uses the same helper. get_app_cache
double-checks the manager under its own lock.

Closes #76
@allen0099
allen0099 merged commit 0e50ebd into master Sep 25, 2026
10 checks passed
@allen0099
allen0099 deleted the fix/app-cache-init-race branch September 25, 2026 08:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

get_app_cache can create two backends on the very first requests

1 participant