The Fix
Restores the performance of 304 responses after addressing feedback from issue #10112. It avoids opening files when a 304 response is to be sent.
Based on closed aio-libs/aiohttp issue #10112 · PR/commit linked
Production note: Most teams hit this during upgrades or environment changes. Roll out with a canary and smoke critical endpoints (health, OpenAPI/docs) before 100%.
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+10101.bugfix.rst
\ No newline at end of file
diff --git a/aiohttp/web_fileresponse.py b/aiohttp/web_fileresponse.py
etag_value = f"{st.st_mtime_ns:x}-{st.st_size:x}"
last_modified = st.st_mtime
# https://www.rfc-editor.org/rfc/rfc9110#section-13.1.1-2
ifmatch = request.if_match
if ifmatch is not None and not self._etag_match(
etag_value, ifmatch, weak=False
):
return await self._precondition_failed(request)
unmodsince = request.if_unmodified_since
if (
unmodsince is not None
and ifmatch is None
and st.st_mtime > unmodsince.timestamp()
):
return await self._precondition_failed(request)
# https://www.rfc-editor.org/rfc/rfc9110#section-13.1.2-2
ifnonematch = request.if_none_match
if ifnonematch is not None and self._etag_match(
etag_value, ifnonematch, weak=True
):
return await self._not_modified(request, etag_value, last_modified)
modsince = request.if_modified_since
if (
modsince is not None
and ifnonematch is None
and st.st_mtime <= modsince.timestamp()
):
return await self._not_modified(request, etag_value, last_modified)
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Option A — Apply the official fix\nRestores the performance of 304 responses after addressing feedback from issue #10112. It avoids opening files when a 304 response is to be sent.\nWhen NOT to use: This fix is not suitable if the application does not frequently serve 304 responses.\n\n
Why This Fix Works in Production
- Trigger: return await self._precondition_failed(request)
- Mechanism: The fix introduced overhead due to file openings when serving 304 responses
- If left unfixed, the same config can fail only in production (env differences), causing startup failures or partial feature outages.
Why This Breaks in Prod
- The fix introduced overhead due to file openings when serving 304 responses
- Production symptom (often without a traceback): return await self._precondition_failed(request)
Proof / Evidence
- GitHub issue: #10112
- Fix PR: https://github.com/aio-libs/aiohttp/pull/10113
- Reproduced locally: No (not executed)
- Last verified: 2026-02-11
- Confidence: 0.80
- Did this fix it?: Yes (upstream fix exists)
- Own content ratio: 0.46
Discussion
High-signal excerpts from the issue thread (symptoms, repros, edge-cases).
“I think we could move the checks into the executor job as they are thread-safe already. Then return the state from executor job without the…”
“ie this block would become something like”
Failure Signature (Search String)
- return await self._precondition_failed(request)
- return FileResponseState.PRE_CONDITION_FAILED
Copy-friendly signature
Failure Signature
-----------------
return await self._precondition_failed(request)
return FileResponseState.PRE_CONDITION_FAILED
Error Message
Signature-only (no traceback captured)
Error Message
-------------
return await self._precondition_failed(request)
return FileResponseState.PRE_CONDITION_FAILED
Minimal Reproduction
etag_value = f"{st.st_mtime_ns:x}-{st.st_size:x}"
last_modified = st.st_mtime
# https://www.rfc-editor.org/rfc/rfc9110#section-13.1.1-2
ifmatch = request.if_match
if ifmatch is not None and not self._etag_match(
etag_value, ifmatch, weak=False
):
return await self._precondition_failed(request)
unmodsince = request.if_unmodified_since
if (
unmodsince is not None
and ifmatch is None
and st.st_mtime > unmodsince.timestamp()
):
return await self._precondition_failed(request)
# https://www.rfc-editor.org/rfc/rfc9110#section-13.1.2-2
ifnonematch = request.if_none_match
if ifnonematch is not None and self._etag_match(
etag_value, ifnonematch, weak=True
):
return await self._not_modified(request, etag_value, last_modified)
modsince = request.if_modified_since
if (
modsince is not None
and ifnonematch is None
and st.st_mtime <= modsince.timestamp()
):
return await self._not_modified(request, etag_value, last_modified)
What Broke
Increased latency when serving 304 responses due to unnecessary file operations.
Why It Broke
The fix introduced overhead due to file openings when serving 304 responses
Fix Options (Details)
Option A — Apply the official fix
Restores the performance of 304 responses after addressing feedback from issue #10112. It avoids opening files when a 304 response is to be sent.
Fix reference: https://github.com/aio-libs/aiohttp/pull/10113
Last verified: 2026-02-11. Validate in your environment.
When NOT to Use This Fix
- This fix is not suitable if the application does not frequently serve 304 responses.
Verify Fix
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Did This Fix Work in Your Case?
Quick signal helps us prioritize which fixes to verify and improve.
Prevention
- Capture the exact failing error string in logs and tests so you can reproduce via a minimal script.
- Pin production dependencies and upgrade only with a reproducible test that hits the failing path.
Related Issues
No related fixes found.
Sources
We don’t republish the full GitHub discussion text. Use the links above for context.