This is an automated email from the ASF dual-hosted git repository.
bneradt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new 20b4cb2ec5 Fix negative revalidating for zero-length objects (#12697)
20b4cb2ec5 is described below
commit 20b4cb2ec5d76cbd3699f1cde8ff88a1fe6d4f7f
Author: Brian Neradt <[email protected]>
AuthorDate: Mon Nov 24 14:47:09 2025 -0600
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
---
src/iocore/cache/CacheRead.cc | 4 +
.../negative-revalidating-enabled.replay.yaml | 117 +++++++++++++++++++++
2 files changed, 121 insertions(+)
diff --git a/src/iocore/cache/CacheRead.cc b/src/iocore/cache/CacheRead.cc
index 7565d9f761..894678a2b1 100644
--- a/src/iocore/cache/CacheRead.cc
+++ b/src/iocore/cache/CacheRead.cc
@@ -1105,6 +1105,10 @@ CacheVC::openReadStartHead(int event, Event *e)
}
// the first fragment might have been gc'ed. Make sure the first
// fragment is there before returning CACHE_EVENT_OPEN_READ
+ // For zero-length documents, don't attempt to read additional fragments.
+ if (doc_len == 0 && !f.single_fragment) {
+ f.single_fragment = true;
+ }
if (!f.single_fragment) {
goto Learliest;
}
diff --git
a/tests/gold_tests/cache/replay/negative-revalidating-enabled.replay.yaml
b/tests/gold_tests/cache/replay/negative-revalidating-enabled.replay.yaml
index 32c5ad66eb..607c3816d3 100644
--- a/tests/gold_tests/cache/replay/negative-revalidating-enabled.replay.yaml
+++ b/tests/gold_tests/cache/replay/negative-revalidating-enabled.replay.yaml
@@ -81,6 +81,29 @@ sessions:
proxy-response:
status: 200
+ # Also populate cache with a zero-length response (for issue #6649).
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ scheme: "http"
+ url: /path/zero_length_item
+ headers:
+ fields:
+ - [ Host, example.com ]
+ - [ uuid, 31 ]
+
+ # Populate the cache with a 200 response with zero-length content.
+ server-response:
+ status: 200
+ reason: "OK"
+ headers:
+ fields:
+ - [ Content-Length, 0 ]
+ - [ Cache-Control, max-age=2 ]
+
+ proxy-response:
+ status: 200
+
# Verify we serve the 200 OK out of the cache if it is not stale.
- client-request:
method: "GET"
@@ -107,6 +130,29 @@ sessions:
proxy-response:
status: 200
+ # Verify zero-length is also served from cache when fresh.
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ scheme: "http"
+ url: /path/zero_length_item
+ headers:
+ fields:
+ - [ Host, example.com ]
+ - [ uuid, 32 ]
+
+ # This should not reach the origin server.
+ server-response:
+ status: 503
+ reason: "Service Unavailable"
+ headers:
+ fields:
+ - [ Content-Length, 0 ]
+
+ # Again, we should serve this out of the cache.
+ proxy-response:
+ status: 200
+
# Verify that with negative_revalidating enabled, we serve the 200 OK out of
# the cache even though it is stale (but younger than max-age +
max_stale_age).
- client-request:
@@ -134,6 +180,54 @@ sessions:
proxy-response:
status: 200
+ # Test zero-length while stale - first request (regression test for issue
#6649).
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ scheme: "http"
+ url: /path/zero_length_item
+ headers:
+ fields:
+ - [ Host, example.com ]
+ - [ uuid, 33 ]
+
+ server-response:
+ status: 503
+ reason: "Service Unavailable"
+ headers:
+ fields:
+ - [ Content-Length, 0 ]
+
+ # With negative_revalidating enabled, the cached response should be served
+ # even though it is stale.
+ proxy-response:
+ status: 200
+
+ # Make a second consecutive stale request for zero-length to verify the bug
scenario.
+ # Per issue #6649: "only the first get on a 0 length cached object comes back
+ # as stale and the second succeeds" (meaning the second might fail).
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ scheme: "http"
+ url: /path/zero_length_item
+ headers:
+ fields:
+ - [ Host, example.com ]
+ - [ uuid, 34 ]
+
+ server-response:
+ status: 503
+ reason: "Service Unavailable"
+ headers:
+ fields:
+ - [ Content-Length, 0 ]
+
+ # This second consecutive stale request should also be served from cache.
+ # This is where the #6649 bug manifests - without the fix, it returns 503.
+ proxy-response:
+ status: 200
+
# Verify that max_stale_age is respected.
- client-request:
method: "GET"
@@ -161,6 +255,29 @@ sessions:
proxy-response:
status: 503
+ # Verify max_stale_age is also respected for zero-length responses.
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ scheme: "http"
+ url: /path/zero_length_item
+ headers:
+ fields:
+ - [ Host, example.com ]
+ - [ uuid, 35 ]
+
+ server-response:
+ status: 503
+ reason: "Service Unavailable"
+ headers:
+ fields:
+ - [ Content-Length, 0 ]
+
+ # negative_revalidating is enabled, but now the cached item is older than
+ # max_stale_age.
+ proxy-response:
+ status: 503
+
#
# Test 2: Negative revalidating for a cached chunk encoded response.
#