fix(cache): add response delay to corrected age value - #5659
fix(cache): add response delay to corrected age value#5659RaphaelFakhri wants to merge 2 commits into
Conversation
mcollina
left a comment
There was a problem hiding this comment.
can you avoid using setTimeout in tests but use fake-timers instead? timers are flaky.
|
Done, switched to @sinonjs/fake-timers with |
RFC 9111 section 4.2.3 computes a stored response's initial age from response_delay = response_time - request_time corrected_age_value = age_value + response_delay corrected_initial_age = max(apparent_age, corrected_age_value) CacheHandler captured a single timestamp at response-header time and computed max(apparentAge, resAge), so response_delay was always zero and corrected_age_value reduced to the bare Age the origin sent. The delay matters because the Age an origin reports is already out of date by the time the response finishes arriving. Dropping it stores a slow response as younger than it is, and a response near its freshness boundary is then served after it has actually expired. Against an origin reporting Age: 100 that takes 3 seconds to respond, the cache served an age of 100 where the RFC requires at least 103. onRequestStart now records the request time, and the correction adds the resulting delay. The field resets on every onRequestStart so a retried or redirected request measures its own delay. A missing Age is treated as age_value = 0 per the same section, so a slow response without one is still aged by the delay.
Real setTimeout in the origin handler made the response delay a wall-clock timer, which is flaky under load. Switch to @sinonjs/fake-timers with toFake: ['Date'], the pattern already used in test/interceptors/cache.js, and inject the response delay by advancing the clock inside the handler: it runs after the request was sent and before the response is received, so Date.now() moves by exactly the delay. The assertions become exact ages rather than lower bounds, and nothing depends on a real timer.
1ba0c97 to
44fbe2f
Compare
|
Rebased onto current main; the fake-timers change from my last comment is unchanged in the second commit. Green locally on the rebased branch: test/interceptors/cache-corrected-age.js, test/interceptors/cache.js and test/cache-interceptor/ all pass, and npm run lint is clean. The workflow runs are sitting at action_required, so CI will need a maintainer to approve them. @mcollina could you take another look when you get a chance? Edit: force-pushed once more to fix the commit author email on both commits; the diff is unchanged. |
44fbe2f to
f4363a3
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5659 +/- ##
==========================================
- Coverage 93.47% 93.46% -0.02%
==========================================
Files 110 110
Lines 38846 38869 +23
==========================================
+ Hits 36310 36327 +17
- Misses 2536 2542 +6 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
RFC 9111 section 4.2.3 defines a stored response's initial age as:
CacheHandlercaptured a single timestamp at response-header time and computedmax(apparentAge, resAge). There is norequest_time, soresponse_delaywas alwayszero and the
corrected_age_valueterm reduced to the bareAgethe origin sent.The delay matters because the Age an origin reports is already out of date by the time the
response finishes arriving. Dropping it stores a slow response as younger than it is, and a
response near its freshness boundary is then served after it has actually expired.
Reproduction
Origin sends
Cache-Control: public, max-age=600,Age: 100, and delays 3 seconds:The understatement scales with upstream latency, so it is largest exactly where caching
matters most.
Change
onRequestStartrecords the request time, andcorrected_age_valueadds the resultingdelay. The field is reset on every
onRequestStart, so a retried or redirected requestmeasures its own delay rather than the first attempt's.
Tests
test/interceptors/cache-corrected-age.js, three cases:Ageheader is aged by at most the response delay itself, sincesection 4.2.3 treats a missing
Ageasage_value = 0max-ageis not reusedCases 1 and 3 fail on main and pass with this change. Case 2 passes on both and is there to
pin the bound on responses that carry no
Age.npm run test:cache-interceptorpasses 76/76, and lint is clean.