ariadne: app-wide HTTP(s) cache in the state tree (§13.9, PR2) - #472
Merged
Merged
Conversation
Add a caching http(s):// READ handler backed by the cvc::app state tree, over the cvc::net facade (PR1). register_cached_http_uri_handler(app) parks entries at sys.net.http_cache.entries.<key> (key = cvc::sha256_hex of the normalized URL): the body on the entry node's data() channel (a raw std::string blob -- reads back as `bytes` in state_exec and serves via state://...?data, per the precursor), and metadata (status/etag/last_modified/content_type/effective_url/fetched_at/ freshness_ttl) as child value nodes. Freshness (SOFT) serves with no network while now < fetched_at + freshness_ttl (Cache-Control max-age, anchored at now-Age; else a validator-present-=>revalidate heuristic; no-cache => revalidate; no-store => bypass). A stale entry or a miss does a conditional GET (If-None-Match / If-Modified-Since): a 304 refreshes the entry (honoring the 304's own directives + any refreshed validators) and serves the cached body, a 200 replaces body + validators + TTL. Retention (HARD) rides the state tree's node expiry: expireAt = fetched_at + max(retention, freshness), swept on access (entries.sweepExpired()), which frees the body blob; a successful store invalidates the entry. Concurrent requests for one URL coalesce (single-flight). A credentialed request -- provider auth headers OR URL-embedded userinfo -- BYPASSES the cache (no cross-principal reuse in a process-global cache). Prerequisite refactor: the shared request-build / response-map / store policy moves out of uri_http.cpp's anonymous namespace into a private (non-installed) uri_http_detail.h -- consult_http_provider, http_header_present, map_http_fetch_result, http_store_uncached -- so the cache and the plain handler apply identical policy. map_http_fetch_result now also maps a 304 to an error, so a bodyless 304 can never surface as an empty-body success on any path. Concurrency: cvc::state's findDescendant()/operator() hand back a bare pointer/ref whose only owner is the parent's child map, so a concurrent sweepExpired() can free a node mid-access. All access to the entries subtree (sweep + snapshot read + write + invalidation) is serialized under one process-wide cache_mutex, held only for the brief tree work and released across the (blocking) network send; cache_mutex and the single-flight flight_mutex are never held simultaneously. Reviewed adversarially (15 findings, all fixed here). Tests: uri_http_cache_test (12, all offline via PR1's set_http_client fake) -- fresh-hit-no-network, 304-serve-cached, 200-replace, no-store bypass, retention eviction, credentialed + userinfo bypass, unsolicited-304 error, 304 no-store eviction, Age anchoring, single-flight coalescing, concurrent distinct keys. The existing 9 http handler tests + 159 loader + 7 net facade tests stay green (the uri_http refactor is behavior-preserving). No platform-specific code -- rides PR1's validated wasm backend. Deferred to PR3: sys.net.http_cache.policy.* knobs incl. a max-entries/max-bytes LRU budget (today bounded only by per-entry 64 MiB + retention), full Vary/private, Expires-based freshness, richer URL normalization. The transport stays synchronous (§13.8's fetch_sync); an async/awaitable chunked (http-get) intrinsic is a later PR the cache rides unchanged.
This was referenced Sep 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR2 of the §13.9 HTTP-cache work, on the
cvc::netfacade (#468) + the state_execbytesbridge (#469). Adds a cachinghttp(s)://READ handler backed by thecvc::appstate tree: repeatedimport:/load:/source:of the same URL is served fromsys.net.http_cache.entries.<key>instead of re-fetching.What it does
sys.net.http_cache.entries.<key>(key = cvc::sha256_hex(normalized-URL)): body on the node'sdata()(a rawstd::stringblob — shows asbytesin state_exec and serves viastate://…?data, per state_exec: data-channelbytesas a raw std::string (coherent with ?data + the HTTP cache) #469); metadata as child value nodes.now < fetched_at + freshness_ttl.Cache-Control: max-age(anchored atnow - Ageso a CDN hit isn't over-fresh); else a validator-present ⇒ revalidate heuristic;no-cache⇒ revalidate;no-store⇒ bypass.If-None-Match/If-Modified-Since): 304 refreshes the entry (honoring the 304's own directives + refreshed validators) and serves the cached body; 200 replaces body + validators + TTL.expireAt = fetched_at + max(retention, freshness),sweepExpired()on access frees the body blob; a successful store invalidates the entry.Prerequisite refactor
Shared request-build / response-map / store policy moves out of
uri_http.cpp's anon namespace into a private (non-installed)uri_http_detail.h(consult_http_provider,http_header_present,map_http_fetch_result,http_store_uncached), so the cache and the plain handler apply identical policy.map_http_fetch_resultalso now maps a 304 to an error, so a bodyless 304 can never surface as empty-body success on any path.Concurrency (the load-bearing fix)
cvc::state'sfindDescendant/operator()hand back a bare pointer/ref whose only owner is the parent's child map, so a concurrentsweepExpired()can free a node mid-access (UAF). All access to theentriessubtree (sweep + snapshot read + write + invalidation) is serialized under one process-widecache_mutex, held only for the brief tree work and released across the blocking network send;cache_mutexand the single-flightflight_mutexare never nested.Review + tests
Reviewed adversarially (4 lenses × find-then-verify, medium fleet); 15 real findings, all fixed in this PR — the three UAFs + the replace-torn-read (→
cache_mutex), 304 correctness (unsolicited/unconditional 304,no-store/no-cacheon revalidation, validator merge), Cache-Control tokenizing, Age anchoring, userinfo bypass.uri_http_cache_test— 12 tests, all offline via PR1'sset_http_clientfake (incl. single-flight coalescing and a concurrent-distinct-keys smoke, 0/20 flaky). The existing 9 http handler + 159 loader + 7 net facade tests stay green (the refactor is behavior-preserving). No platform-specific code — rides PR1's validated wasm backend.Deferred to PR3
sys.net.http_cache.policy.*knobs incl. a max-entries/max-bytes LRU budget (today bounded only by per-entry 64 MiB + retention), fullVary/private,Expires-based freshness, richer URL normalization. The transport stays synchronous (§13.8'sfetch_sync); an async/awaitable chunked(http-get)intrinsic (off-thread + parking viacompute_async → post_message → msg-recv) is a later PR the cache rides unchanged.