Skip to content

Commit 2c4a883

Browse files
committed
Avoid a race-condition on ETag validation.
For example, in case multiple Python versions are using the same cache and some are <3.14 and other >=3.14, the Accept-Encoding header will vary between `gzip, deflate` and `gzip, deflate, zstd`. If two processes, say p1 (with zstd support) and p2 (without zstd support) do two requests to the same URL, with an already cached result (for the without zstd support request) it could lead to the following race condition: - P1 reads the cache, fails because Accept-Encoding does not match - P1 sends a normal request - P2 reads the cache, succeeds, but needs validation - P2 sends a validation request (with If-None-Match) - P1 receives a response for its Accept-Encoding: gzip, deflate, zstd - P1 updates the cache with it - P1 returns the 200 OK to the user - P2 receives a 304 Not Modified response - P2 wants to return from the cache, but fails to read it (wrong Accept-Encoding!) - P2 fallbacks to returning a 304 Not Modified response to the user :(
1 parent a4b13be commit 2c4a883

1 file changed

Lines changed: 3 additions & 1 deletion

File tree

cachecontrol/controller.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def __init__(
6161
self.cache_etags = cache_etags
6262
self.serializer = serializer or Serializer()
6363
self.cacheable_status_codes = status_codes or (200, 203, 300, 301, 308)
64+
self._request_to_cache_response = weakref.WeakKeyDictionary()
6465

6566
@classmethod
6667
def _urlnorm(cls, uri: str) -> str:
@@ -281,6 +282,7 @@ def conditional_headers(self, request: PreparedRequest) -> dict[str, str]:
281282
new_headers = {}
282283

283284
if resp:
285+
self._request_to_cache_response[request] = resp
284286
headers: CaseInsensitiveDict[str] = CaseInsensitiveDict(resp.headers)
285287

286288
if "etag" in headers:
@@ -479,7 +481,7 @@ def update_cached_response(
479481
"""
480482
assert request.url is not None
481483
cache_url = self.cache_url(request.url)
482-
cached_response = self._load_from_cache(request)
484+
cached_response = self._request_to_cache_response.get(request) or self._load_from_cache(request)
483485

484486
if not cached_response:
485487
# we didn't have a cached response

0 commit comments

Comments
 (0)