Honor the RamCache copy flag in LRU and S3-FIFO - #13380
Conversation
CacheVC requests copy semantics (put copy=true) whenever RAM cache compression is configured, because it unmarshals HTTP headers in place in its own buffer after the put and again on every RAM hit. LRU and S3-FIFO ignored the flag and shared buffers with the caller in both directions, so with proxy.config.cache.ram_cache.compress enabled and algorithm=1 (the default) or 2, the cached bytes were mutated after insertion and every subsequent HTTP RAM hit re-unmarshalled an already-unmarshalled header (assert in debug builds, header unmarshal failure in release). Copy entries now copy on put, copy on get, and a copy=true put refreshes a resident entry that may still be sharing a caller's buffer, matching CLFUS. This is a backport candidate for all release branches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
514d888 to
1b53b1e
Compare
|
Tracking issue: #13381 |
There was a problem hiding this comment.
Pull request overview
This PR fixes a correctness bug in the RAM cache implementations (LRU and S3-FIFO) by honoring the put(..., copy=true) contract, ensuring the cache never shares buffers with callers for copy entries (copy-on-put and copy-on-get). This prevents caller-side in-place mutations (e.g., CacheVC HTTP header unmarshal when RAM cache compression is enabled) from corrupting cached content and breaking subsequent RAM hits.
Changes:
- Centralizes copy semantics into
RamCache::copy_data_in()/RamCache::copy_data_out()and reuses them from CLFUS (no behavior change there). - Updates LRU and S3-FIFO to track per-entry
copystate, copy on put/get when requested, and “refresh” existing resident entries when a latercopy=trueput occurs. - Adds a Catch2 unit test covering
copy=trueisolation in both directions, the copy=false sharing behavior, and the resident refresh transition across all three RAM cache policies.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/iocore/cache/unit_tests/test_RamCacheCopy.cc | New unit test that pins the copy contract across LRU/CLFUS/S3-FIFO and covers regression scenarios. |
| src/iocore/cache/RamCacheS3FIFO.cc | Implements copy handling on put/get and resident refresh for S3-FIFO entries. |
| src/iocore/cache/RamCacheLRU.cc | Implements copy handling on put/get and resident refresh for LRU entries. |
| src/iocore/cache/RamCacheCLFUS.cc | Replaces open-coded copy logic with shared base-class helpers (behavior-preserving refactor). |
| src/iocore/cache/P_RamCache.h | Adds shared copy_data_in() / copy_data_out() helpers on the RamCache base class. |
| src/iocore/cache/CMakeLists.txt | Registers the new RamCacheCopy Catch2 unit test. |
|
Read through this and ran the tests locally. The bug reproduces as described: I checked a couple of other cases: Tested on a 5000 byte object to exercise the size rounding in Checked the I also recommend an S3-FIFO ghost admit test. The copy handling is inherited -- the ghost is removed and the fresh entry goes through the same insert site -- but the readmit lands in Happy to approve once it comes out of draft. |
Summary
put(..., copy = true)is the caller's contract that buffers may not be shared between the cache and the caller in either direction.CacheVCrequests it whenever RAM cache compression is configured (http_copy_hdr,CacheVC.cc), because it unmarshals HTTP headers in place in its own buffer after the put, and again on every RAM-cache hit.The LRU and S3-FIFO RAM caches ignored the flag and shared buffers both ways. With
proxy.config.cache.ram_cache.compressset andalgorithm = 1(the default) or2:HTTPInfo::unmarshalfails on the already-unmarshalled header (ink_assertin debug builds; broken HTTP RAM hits in release).The
http_copy_hdrgate keys off the compress config only, never the algorithm, so this is reachable purely by configuration today. This correctness fix stands alone (compression support for LRU is proposed separately) and is a backport candidate.Changes
RamCache::copy_data_in()/RamCache::copy_data_out()on the base class; CLFUS's three pre-existing open-coded copies of the same logic are replaced with calls to them (no behavior change).RamCacheLRUandRamCacheS3FIFOnow honorcopythe way CLFUS always has: copy on put, copy on get, and acopy = trueput refreshes a resident entry that may still be sharing a caller's buffer from before a configuration change.copy = falsebehavior is unchanged: zero-copy sharing, pinned by a test.test_RamCacheCopy) pins the contract for all three policies: caller mutation after put, caller mutation of the returned buffer, the resident-refresh transition, and copy=false sharing. Before the fix, the three copy cases fail for LRU and S3-FIFO; CLFUS passes.Notes for reviewers
block_size()recovers the data length and neither entry struct needs a new length field.