baibaichen opened a new pull request, #12672:
URL: https://github.com/apache/gluten/pull/12672

   ## What changes are proposed in this pull request?
   
   Fixes #12671
   
   `SparkMmapAllocator` subclasses `MmapAllocator` and governs the cache 
capacity in **two** places,
   because reporting it and enforcing it do different jobs:
   
   - **Reporting** is the virtual `capacity()`, which 
`AsyncDataCache::canTryAllocate()` measures
     headroom against, so lowering it is what makes the cache evict older 
entries instead of growing.
     It returns `max(governed, allocated)` rather than the governed value 
alone: the headroom is an
     *unsigned* subtraction, so a capacity below the allocated bytes would wrap 
to a huge number and
     turn the cap into an unlimited allowance. Clamped, the headroom is exactly 
zero.
   - **Enforcing** is the three admission points, which take the bound as a 
parameter. Reporting alone
     would not do: `canTryAllocate()` runs without a lock by design, so readers 
passing it together
     took more than the cap — one entry over a 32MB grant with 16 threads, 
measured.
   
   A periodic checker on the JVM side moves the capacity — halving the room 
above the floor when
   storage tightens, reclaiming a fixed step when it lifts — and does **all** 
of the Spark borrow and
   repay. Growing borrows first and then raises the cap; shrinking lowers the 
cap first and then repays
   what was actually given up. Native code never calls back into Java.
   
   ### What this bound does and does not guarantee
   
   It is a **soft bound that settles, rather than one that holds**:
   
   - The checker runs **periodically** (1s by default), so the capacity follows 
Spark's pressure with a
     lag of up to one interval. It is not synchronous with allocation.
   - Lowering the capacity is a *request*. `AsyncDataCache` evicts what it can, 
but **pinned entries
     survive it**. When a lot is pinned the cache stays above the new target 
and there is nothing to do
     but wait — only what was **actually** freed is repaid to Spark, so the 
reservation is never
     under-stated while that lasts.
   - Cache usage therefore **converges to below** what is reported to Spark. It 
does not step over the
     reservation and stay there.
   - The one window where usage can exceed the reservation is an allocation 
**already in flight** when
     the capacity changes: the bound is read once on the way in, so lowering it 
cannot reach a call
     already past that point. It is bounded by concurrent readers times 
`loadQuantum` (forced <= 8MB
     when the cache is on), and closes as those allocations finish.
   
   ### Why not a hard bound
   
   The original plan was to hook the cache into the memory manager's LRU list, 
so eviction could be
   driven synchronously and the bound made hard. **Pins are what make that 
impossible**: there is no
   answer to "how much can you release right now". A synchronous reclaim would 
have to either block on
   readers or return an unpredictable amount, and the memory manager can act on 
neither. Backing off
   periodically, and repaying only what was really freed, is what pins actually 
permit.
   
   ### Dependency — please read before merging
   
   This depends on facebookincubator/velox#18358, which parameterises the three 
`MmapAllocator`
   admission paths as protected `...WithCapacity` overloads (a pure refactor; 
the existing entry points
   forward the configured capacity). It is unavoidable: `Allocation::append` is 
private to three named
   `friend` classes and **friendship is not inherited**, so copying 
`MmapAllocator` into Gluten does not
   compile.
   
   The last commit is marked `[TMP]` and sets `UPSTREAM_VELOX_PR_ID` in 
`get-velox.sh` so that CI can
   build against that PR. **It must be reverted before this merges**, once the 
PR has landed and the
   pinned branch has picked it up.
   
   Opened as a draft for that reason — the design and the Gluten side are ready 
for review.
   
   The design, including the invariants, the rejected alternatives and the 
known limitations, is in
   `docs/superpowers/specs/2026-07-31-velox-cache-backpressure-design.md`.
   
   ## How was this patch tested?
   
   New tests:
   
   - `cpp/velox/tests/SparkMmapAllocatorTest.cc` — 17 cases, covering the 
invariants, page alignment,
     concurrent readers filling the cache together, and the shutdown contract.
   - `PeriodicMemoryCheckerSuite` — 28 cases, covering the adjustment 
algorithm, a refused borrow, a
     shrink blocked by pins, and the startup configuration checks.
   - `CacheStorageReservationDynamicSizingSuite` — 1 case, that the reservation 
counts towards the
     process memory gate under dynamic off-heap sizing.
   
   All passing locally, along with `dev/format-scala-code.sh` and 
clang-format-15.
   
   Not yet done, and worth a reviewer's attention: the cross-profile compile 
(spark-3.3 / 3.5 /
   scala-2.12) has not been run locally, and the defaults for `minCacheSize`, 
`cacheRatio` and
   `stepSize` have not been calibrated against a real cluster.
   
   ## Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: GitHub Copilot CLI (Claude Opus 5)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to