Skip to content

Commit 20b4cb2

Browse files
authored
Fix negative revalidating for zero-length objects (#12697)
When negative revalidating was enabled, zero-length cached objects would fail on the second consecutive stale request with a 5xx response from the origin. The first stale request would correctly serve the cached content, but the second would return the origin's error response instead. This occurred because the cache attempted to read additional fragments for zero-length documents when the cache key didn't match the document key, causing a cache read failure (ECACHE_NO_DOC). The fix ensures zero-length documents are always treated as single-fragment, preventing attempts to read non-existent body data. Fixes: #6649
1 parent d74c795 commit 20b4cb2

File tree

2 files changed

+121
-0
lines changed

2 files changed

+121
-0
lines changed

src/iocore/cache/CacheRead.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,10 @@ CacheVC::openReadStartHead(int event, Event *e)
11051105
}
11061106
// the first fragment might have been gc'ed. Make sure the first
11071107
// fragment is there before returning CACHE_EVENT_OPEN_READ
1108+
// For zero-length documents, don't attempt to read additional fragments.
1109+
if (doc_len == 0 && !f.single_fragment) {
1110+
f.single_fragment = true;
1111+
}
11081112
if (!f.single_fragment) {
11091113
goto Learliest;
11101114
}

tests/gold_tests/cache/replay/negative-revalidating-enabled.replay.yaml

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,29 @@ sessions:
8181
proxy-response:
8282
status: 200
8383

84+
# Also populate cache with a zero-length response (for issue #6649).
85+
- client-request:
86+
method: "GET"
87+
version: "1.1"
88+
scheme: "http"
89+
url: /path/zero_length_item
90+
headers:
91+
fields:
92+
- [ Host, example.com ]
93+
- [ uuid, 31 ]
94+
95+
# Populate the cache with a 200 response with zero-length content.
96+
server-response:
97+
status: 200
98+
reason: "OK"
99+
headers:
100+
fields:
101+
- [ Content-Length, 0 ]
102+
- [ Cache-Control, max-age=2 ]
103+
104+
proxy-response:
105+
status: 200
106+
84107
# Verify we serve the 200 OK out of the cache if it is not stale.
85108
- client-request:
86109
method: "GET"
@@ -107,6 +130,29 @@ sessions:
107130
proxy-response:
108131
status: 200
109132

133+
# Verify zero-length is also served from cache when fresh.
134+
- client-request:
135+
method: "GET"
136+
version: "1.1"
137+
scheme: "http"
138+
url: /path/zero_length_item
139+
headers:
140+
fields:
141+
- [ Host, example.com ]
142+
- [ uuid, 32 ]
143+
144+
# This should not reach the origin server.
145+
server-response:
146+
status: 503
147+
reason: "Service Unavailable"
148+
headers:
149+
fields:
150+
- [ Content-Length, 0 ]
151+
152+
# Again, we should serve this out of the cache.
153+
proxy-response:
154+
status: 200
155+
110156
# Verify that with negative_revalidating enabled, we serve the 200 OK out of
111157
# the cache even though it is stale (but younger than max-age + max_stale_age).
112158
- client-request:
@@ -134,6 +180,54 @@ sessions:
134180
proxy-response:
135181
status: 200
136182

183+
# Test zero-length while stale - first request (regression test for issue #6649).
184+
- client-request:
185+
method: "GET"
186+
version: "1.1"
187+
scheme: "http"
188+
url: /path/zero_length_item
189+
headers:
190+
fields:
191+
- [ Host, example.com ]
192+
- [ uuid, 33 ]
193+
194+
server-response:
195+
status: 503
196+
reason: "Service Unavailable"
197+
headers:
198+
fields:
199+
- [ Content-Length, 0 ]
200+
201+
# With negative_revalidating enabled, the cached response should be served
202+
# even though it is stale.
203+
proxy-response:
204+
status: 200
205+
206+
# Make a second consecutive stale request for zero-length to verify the bug scenario.
207+
# Per issue #6649: "only the first get on a 0 length cached object comes back
208+
# as stale and the second succeeds" (meaning the second might fail).
209+
- client-request:
210+
method: "GET"
211+
version: "1.1"
212+
scheme: "http"
213+
url: /path/zero_length_item
214+
headers:
215+
fields:
216+
- [ Host, example.com ]
217+
- [ uuid, 34 ]
218+
219+
server-response:
220+
status: 503
221+
reason: "Service Unavailable"
222+
headers:
223+
fields:
224+
- [ Content-Length, 0 ]
225+
226+
# This second consecutive stale request should also be served from cache.
227+
# This is where the #6649 bug manifests - without the fix, it returns 503.
228+
proxy-response:
229+
status: 200
230+
137231
# Verify that max_stale_age is respected.
138232
- client-request:
139233
method: "GET"
@@ -161,6 +255,29 @@ sessions:
161255
proxy-response:
162256
status: 503
163257

258+
# Verify max_stale_age is also respected for zero-length responses.
259+
- client-request:
260+
method: "GET"
261+
version: "1.1"
262+
scheme: "http"
263+
url: /path/zero_length_item
264+
headers:
265+
fields:
266+
- [ Host, example.com ]
267+
- [ uuid, 35 ]
268+
269+
server-response:
270+
status: 503
271+
reason: "Service Unavailable"
272+
headers:
273+
fields:
274+
- [ Content-Length, 0 ]
275+
276+
# negative_revalidating is enabled, but now the cached item is older than
277+
# max_stale_age.
278+
proxy-response:
279+
status: 503
280+
164281
#
165282
# Test 2: Negative revalidating for a cached chunk encoded response.
166283
#

0 commit comments

Comments
 (0)