Jump to solution
Verify

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%.

Jump to Verify Open PR/Commit
@@ -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
repro.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)
verify
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
fix.md
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
Production impact:
  • 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

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…”
@bdraco · 2024-12-05 · source
“ie this block would become something like”
@bdraco · 2024-12-05 · source

Failure Signature (Search String)

  • return await self._precondition_failed(request)
  • return FileResponseState.PRE_CONDITION_FAILED
Copy-friendly signature
signature.txt
Failure Signature ----------------- return await self._precondition_failed(request) return FileResponseState.PRE_CONDITION_FAILED

Error Message

Signature-only (no traceback captured)
error.txt
Error Message ------------- return await self._precondition_failed(request) return FileResponseState.PRE_CONDITION_FAILED

Minimal Reproduction

repro.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)

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.

When NOT to use: This fix is not suitable if the application does not frequently serve 304 responses.

Fix reference: https://github.com/aio-libs/aiohttp/pull/10113

Last verified: 2026-02-11. Validate in your environment.

Get updates

We publish verified fixes weekly. No spam.

Subscribe

When NOT to Use This Fix

  • This fix is not suitable if the application does not frequently serve 304 responses.

Verify Fix

verify
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.