peterxcli commented on code in PR #5493:
URL: https://github.com/apache/datafusion-comet/pull/5493#discussion_r3879394575
##########
spark/src/main/java/org/apache/spark/shuffle/comet/CometBoundedShuffleMemoryAllocator.java:
##########
@@ -112,6 +131,96 @@ public synchronized MemoryBlock allocate(long required) {
return allocateMemoryBlock(size);
}
+ /**
+ * Like {@link #allocate(long)}, but waits for other tasks of this shared
pool to free memory,
+ * mirroring how Spark's unified memory manager blocks a task until memory
becomes available.
+ * Callers must only use this after spilling their own buffered data. The
wait fails fast when it
+ * can never succeed: when the request does not fit next to the memory this
thread itself still
+ * retains (e.g. the sorter's pointer array), or when all allocated memory
is retained by threads
+ * that are themselves blocked here and none of their requests fits in the
free pool. Interrupting
+ * the task (e.g. task kill) aborts the wait.
+ */
+ @Override
+ public synchronized MemoryBlock allocateBlocking(long required) {
+ long size = Math.max(pageSize, required);
+ Thread self = Thread.currentThread();
+ long waitStart = 0;
+ long lastLog = 0;
+ try {
+ while (true) {
+ try {
+ return allocateMemoryBlock(size);
+ } catch (SparkOutOfMemoryError e) {
+ if (waitingThreads.put(self, size) == null) {
+ // Wake existing waiters so they re-evaluate the deadlock check
against the enlarged
+ // waiting set.
+ notifyAll();
+ }
+ // This thread cannot free what it retains while it waits, so a
request that does not
+ // fit next to its own retained memory can never be satisfied.
+ if (size > totalMemory - retainedMemory.getOrDefault(self, 0L)) {
+ throw e;
+ }
+ // The allocation just failed, so the request does not fit in the
unallocated pool.
+ // Waiting can only succeed while some thread can still free memory:
either a thread
+ // outside the waiting set retains pool memory, or another waiter's
request fits in the
+ // free pool, in which case that waiter can proceed and eventually
free what it retains.
+ if (allocatedMemory <= retainedByWaitingThreads() &&
!anyWaiterCanProceed()) {
Review Comment:
Follow-up addressed in 4b592b9c2. The constructor cleanup now covers the
whole tail of `SpillSorter`'s constructor — `serializeSchema` and the `Native`
library load included — not just the pointer-array allocation. An `adopted`
flag keeps ownership single: before `expandPointerArray` adopts the array it is
freed directly (the sorter still owns only its initial one-entry array), and
after adoption `inMemSorter.free()` releases it — avoiding a double free, which
would trip `TaskMemoryManager`'s already-freed assertion in off-heap mode.
Extended the constructor-leak regression test with your exact trigger: a nested
field with `parquet.field.id = 2147483648` makes `ParquetUtils.getFieldId`
throw `IllegalArgumentException` after the array is adopted, and the test then
asserts a full-pool 1,048,576-byte allocation succeeds — which fails while the
adopted 32-KiB array leaks. Also hardened the bypass writer's failure cleanup
to skip null slots in case a mid-loop writer constructio
n failure leaves the tail of `partitionWriters` unassigned.
--
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]