LuciferYang opened a new issue, #12424:
URL: https://github.com/apache/gluten/issues/12424

   ### Describe the bug
   
   `DynamicOffHeapSizingMemoryTarget` books the requested size into its 
counters and discards the wrapped target's return value:
   
   ```java
   public long borrow(long size) {
     ...
     USED_OFF_HEAP_BYTES.addAndGet(size);
     recorder.inc(size);
     target.borrow(size);   // return value discarded
     return size;            // unconditional
   }
   
   public long repay(long size) {
     USED_OFF_HEAP_BYTES.addAndGet(-size);
     recorder.inc(-size);
     target.repay(size);    // return value discarded
     return size;            // unconditional
   }
   ```
   
   The wrapped `target` is a Spark-facing consumer (`TreeMemoryConsumer` / 
`TreeMemoryConsumer.Node`) whose `borrow(size)` is allowed to grant less than 
`size` — Spark's `MemoryConsumer.acquireMemory` can return anywhere from `0` to 
`size`. `DynamicOffHeapSizingMemoryTarget` credits the full requested size into 
`USED_OFF_HEAP_BYTES` and its per-instance recorder, and returns `size` to the 
caller as if the reservation fully succeeded. The upstream 
`ThrowOnOomMemoryTarget` therefore also treats the reservation as successful 
and hands the returned amount to native code, while Spark's execution pool 
actually reserved less.
   
   `repay` has the mirrored bug: `TreeMemoryConsumer.Node.repay` uses 
`Math.min(usedBytes(), size)` to bound what it can free, but 
`DynamicOffHeapSizingMemoryTarget.repay` decrements the full `size` regardless 
of what was actually freed.
   
   There is a related failure mode when the wrapped target throws: the pre-fix 
code increments the counters before calling `target.borrow`, so an exception 
(from Spark's memory manager, a `Preconditions.checkState` inside 
`ensureFreeCapacity`, or task-teardown races) permanently leaks the requested 
bytes into the static counter for the lifetime of the executor JVM.
   
   ### Expected behavior
   
   Book only what the wrapped target actually grants / frees, and only after 
the wrapped call returns normally:
   
   ```java
   public long borrow(long size) {
     ...
     final long granted = target.borrow(size);
     USED_OFF_HEAP_BYTES.addAndGet(granted);
     recorder.inc(granted);
     return granted;
   }
   
   public long repay(long size) {
     if (size == 0) return 0;
     final long freed = target.repay(size);
     USED_OFF_HEAP_BYTES.addAndGet(-freed);
     recorder.inc(-freed);
     return freed;
   }
   ```
   
   Found while reviewing the L2 memory subsystem in `gluten-core`. Patch and 
tests ready — will send a PR.


-- 
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