Skip to content

feat(cache): revalidate and serve stale responses - #19

Merged
jamiehdev merged 3 commits into
mainfrom
feat/cache-revalidation
Sep 27, 2026
Merged

jamiehdev merged 3 commits into
mainfrom
feat/cache-revalidation

Conversation

@jamiehdev

Copy link
Copy Markdown
Owner

The cache now keeps stale responses that have a validator or a stale window. It revalidates them with conditional requests and no longer fetches them again in full. It honours stale-while-revalidate and stale-if-error (RFC 5861) and never serves must-revalidate, proxy-revalidate or s-maxage responses stale (RFC 9111 section 4.3).

Changes

  • A stale response stays stored for a grace period. With an ETag or Last-Modified, the grace is --cache-ttl-seconds. Without one, the grace is the larger stale window, capped at the same value. The byte bound still applies.
  • A GET for a stale response sends If-None-Match and If-Modified-Since from the stored validators. A 304 updates the stored fields and freshness, and the client gets 200 with the stored body, marked X-Shadowstep-Cache: REVALIDATED.
  • If the client sends its own conditional headers, the proxy forwards them unchanged. The origin's 304 then goes to the client and leaves the entry as it was.
  • Within stale-while-revalidate, the client gets the stale response (STALE) and one background task per entry revalidates it. The task builds its request like a miss does.
  • Within stale-if-error, a 500, 502, 503 or 504, a refused connection or a timeout gets the stale response.
  • An unreachable origin gives 504 when the stored response needs revalidation.
  • /health adds revalidations, stale and background_refreshes. hit_ratio now counts every response whose body came from the cache.

Verification

tests/integration/revalidation.rs holds 15 tests. Twelve fail on main and pass here. Three guard behaviour that main already has: eviction without a validator or stale window, an error outside stale-if-error, and must-revalidate with an origin 503. The suite passed five runs in a row.

Risks

Responses with s-maxage now carry proxy-revalidate semantics, so their stale windows have no effect.

@jamiehdev

Copy link
Copy Markdown
Owner Author

@codex review, and also report simplification opportunities in the changed code as P1: logic that duplicates an existing helper, dead code, needless indirection or abstraction, and conditionals or loops with a shorter equivalent. Flag one only when the shorter version behaves the same, and show the replacement code. Do not flag style, naming or formatting.

A stale response with an ETag or Last-Modified stays stored for up to
--cache-ttl-seconds more, so the proxy can revalidate it with a
conditional request. Responses with s-maxage now count as
proxy-revalidate, as RFC 9111 section 5.2.2.10 requires, so they are
never served stale.
@jamiehdev
jamiehdev force-pushed the feat/cache-revalidation branch from 65af2b0 to 06c1696 Compare September 27, 2026 16:14
@jamiehdev
jamiehdev merged commit e0decd7 into main Sep 27, 2026
2 checks passed
@jamiehdev
jamiehdev deleted the feat/cache-revalidation branch September 27, 2026 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant