Truncate fractional seconds in StreamResponse.last_modified setter#13070
Truncate fractional seconds in StreamResponse.last_modified setter#13070HrachShah wants to merge 4 commits into
Conversation
The int/float branch of last_modified rounded up via math.ceil, while the datetime branch used utctimetuple() which drops microseconds. The two branches therefore produced different Last-Modified values for the same point in time, e.g. setting last_modified to 1606895462.4 emitted "...07:51:03 GMT" but datetime.datetime(2020, 12, 2, 7, 51, 2, 400000, tzinfo=utc) emitted "...07:51:02 GMT". Switch the numeric branch to math.floor so both forms agree. Refs aio-libs#5303
for more information, see https://pre-commit.ci
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #13070 +/- ##
========================================
Coverage 98.95% 98.96%
========================================
Files 131 131
Lines 48029 48173 +144
Branches 2495 2499 +4
========================================
+ Hits 47529 47673 +144
Misses 376 376
Partials 124 124
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. |
…dified value The setter change in the previous commit (math.ceil -> math.floor) exposed a long-standing inconsistency: FileResponse compared st.st_mtime (raw float) to modsince.timestamp() (seconds-precision float), but the Last-Modified header it sent carried the seconds- precision value via the setter. After the floor change, a file whose mtime has fractional seconds would respond 200 to a second request that echoed back the Last-Modified from the first response, because the raw st.st_mtime was no longer <= the floored value carried in the header. Set self.last_modified = st.st_mtime at the top of _make_response so the comparison uses the same second-precision value the client will see in the response. Pass that value through to _not_modified and rely on it instead of re-reading st.st_mtime in the 304 path.
for more information, see https://pre-commit.ci
Merging this PR will degrade performance by 8.28%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ | test_simple_web_file_response_not_modified |
58.5 ms | 63.8 ms | -8.28% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing HrachShah:fix/last-modified-fractional-truncation (07ccc70) with master (7f8284a)2
Footnotes
-
83 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
-
No successful run was found on
master(649887c) during the generation of this report, so 7f8284a was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩
What do these changes do?
StreamResponse.last_modifiedacceptsint,float,datetime, andstr.The
datetimebranch usesvalue.utctimetuple(), which truncatesmicroseconds (rounds a fractional second towards negative infinity). The
int/floatbranch usedmath.ceil, which rounds a fractional secondtowards positive infinity. The two paths produced different
Last-Modifiedheader values for the same input.
This change makes the
int/floatbranch round towards negative infinity(
math.floor) so both branches agree.time.gmtimeis then called withan
int, so the input range check remains unchanged.FileResponsewas also affected. The handler recorded the file's mtimeas
self.last_modified(the float branch, which now rounds down) butcompared the raw
st.st_mtime(a float with microseconds) against theclient-supplied
If-Modified-Since/If-Unmodified-Since(second-precision). When the file's mtime had a fractional second, the client
would send back the rounded
Last-Modifiedheader from the previousresponse, the server would compare a raw float to a seconds-precision
float, and the comparison would fail. The first request returned
200and the second request also returned
200(where the previous behaviormade the second request return
304because the raw float was strictlygreater than the rounded
If-Modified-Since).The fix records the file's mtime as
self.last_modifiedonce at the topof
_make_responseand reuses that seconds-precision datetime for thecomparisons, so the value used for the comparison is the same one the
client receives in the
Last-Modifiedheader.Are there changes in behavior for the user?
Yes, but only for the previously-incorrect case. Assigning a fractional
floattolast_modifiednow serializes to the sameLast-Modifiedvalueas the equivalent
datetime(i.e. the second that is already in thepast, not the next second). For integer inputs and whole-second floats
the output is unchanged. The
stranddatetimebranches areuntouched.
For
FileResponse, the second request after the first response nowcorrectly returns
304when the file's mtime has a fractional second(this was previously broken: the first request returned
200and thesecond request also returned
200due to the round-trip mismatch).Is it a substantial burden for the maintainers to support this?
No. The change is a one-line fix in the setter plus a small consistency
fix in
FileResponse(setself.last_modifiedonce and reuse forcomparisons), plus regression tests. The behavior matches the long-
standing behavior of the
datetimebranch.Related issue number
Fixes #5303
Checklist
CONTRIBUTORS.txtCHANGES/folderTest run
The new test
test_last_modified_timestamp_fractional_truncatesfails onthe pre-fix code (it asserts that
last_modified = 1606895462.4resolvesto
2020-12-02 07:51:02 UTC; the pre-fix code returns07:51:03).The pre-existing
test_static_file_if_modified_since(and friends) usedto pass on the pre-fix code (because both branches of the comparison and
the
Last-Modifiedformatting all rounded up) but started failing onceonly the formatting changed to round down. The accompanying
FileResponsefix makes the test pass again, and the round-trip is now consistent.
Drafted with Zo Bot (model minimax-m3, 2026-07-07); reviewed by HrachShah.