You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by li...@apache.org on 2023/06/29 12:15:12 UTC

[arrow] branch main updated: GH-35960: [Java] Detect overflow in allocation (#36185)

This is an automated email from the ASF dual-hosted git repository.

lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 1ab00aefb4 GH-35960: [Java] Detect overflow in allocation (#36185)
1ab00aefb4 is described below

commit 1ab00aefb493bb4f38bde6e41c92e2dfe1782452
Author: David Li <li...@gmail.com>
AuthorDate: Thu Jun 29 08:15:04 2023 -0400

    GH-35960: [Java] Detect overflow in allocation (#36185)
    
    
    
    ### Rationale for this change
    
    The Java allocator wasn't detecting overflow (which is admittedly a corner case since you can't actually allocate that many bytes), causing an unexpected exception.
    
    ### What changes are included in this PR?
    
    Detect overflow and provide the right exception.
    
    ### Are these changes tested?
    
    Yes
    
    ### Are there any user-facing changes?
    
    No
    * Closes: #35960
    
    Authored-by: David Li <li...@gmail.com>
    Signed-off-by: David Li <li...@gmail.com>
---
 .../java/org/apache/arrow/memory/Accountant.java   |  9 ++++--
 .../org/apache/arrow/memory/TestBaseAllocator.java | 33 ++++++++++++++++++++++
 2 files changed, 40 insertions(+), 2 deletions(-)

diff --git a/java/memory/memory-core/src/main/java/org/apache/arrow/memory/Accountant.java b/java/memory/memory-core/src/main/java/org/apache/arrow/memory/Accountant.java
index 42dac7b8c6..87769dd122 100644
--- a/java/memory/memory-core/src/main/java/org/apache/arrow/memory/Accountant.java
+++ b/java/memory/memory-core/src/main/java/org/apache/arrow/memory/Accountant.java
@@ -169,9 +169,14 @@ class Accountant implements AutoCloseable {
    */
   private AllocationOutcome.Status allocate(final long size, final boolean incomingUpdatePeak,
       final boolean forceAllocation, AllocationOutcomeDetails details) {
-    final long newLocal = locallyHeldMemory.addAndGet(size);
+    final long oldLocal = locallyHeldMemory.getAndAdd(size);
+    final long newLocal = oldLocal + size;
+    // Borrowed from Math.addExact (but avoid exception here)
+    // Overflow if result has opposite sign of both arguments
+    // No need to reset locallyHeldMemory on overflow; allocateBytesInternal will releaseBytes on failure
+    final boolean overflow = ((oldLocal ^ newLocal) & (size ^ newLocal)) < 0;
     final long beyondReservation = newLocal - reservation;
-    final boolean beyondLimit = newLocal > allocationLimit.get();
+    final boolean beyondLimit = overflow || newLocal > allocationLimit.get();
     final boolean updatePeak = forceAllocation || (incomingUpdatePeak && !beyondLimit);
 
     if (details != null) {
diff --git a/java/memory/memory-netty/src/test/java/org/apache/arrow/memory/TestBaseAllocator.java b/java/memory/memory-netty/src/test/java/org/apache/arrow/memory/TestBaseAllocator.java
index 7c0df0e98e..7613d073f8 100644
--- a/java/memory/memory-netty/src/test/java/org/apache/arrow/memory/TestBaseAllocator.java
+++ b/java/memory/memory-netty/src/test/java/org/apache/arrow/memory/TestBaseAllocator.java
@@ -1134,6 +1134,39 @@ public class TestBaseAllocator {
     }
   }
 
+  @Test
+  public void testOverlimit() {
+    try (BufferAllocator allocator = new RootAllocator(1024)) {
+      try (BufferAllocator child1 = allocator.newChildAllocator("ChildA", 0, 1024);
+           BufferAllocator child2 = allocator.newChildAllocator("ChildB", 1024, 1024)) {
+        assertThrows(OutOfMemoryException.class, () -> {
+          ArrowBuf buf1 = child1.buffer(8);
+          buf1.close();
+        });
+        assertEquals(0, child1.getAllocatedMemory());
+        assertEquals(0, child2.getAllocatedMemory());
+        assertEquals(1024, allocator.getAllocatedMemory());
+      }
+    }
+  }
+
+  @Test
+  public void testOverlimitOverflow() {
+    // Regression test for https://github.com/apache/arrow/issues/35960
+    try (BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE)) {
+      try (BufferAllocator child1 = allocator.newChildAllocator("ChildA", 0, Long.MAX_VALUE);
+           BufferAllocator child2 = allocator.newChildAllocator("ChildB", Long.MAX_VALUE, Long.MAX_VALUE)) {
+        assertThrows(OutOfMemoryException.class, () -> {
+          ArrowBuf buf1 = child1.buffer(1024);
+          buf1.close();
+        });
+        assertEquals(0, child1.getAllocatedMemory());
+        assertEquals(0, child2.getAllocatedMemory());
+        assertEquals(Long.MAX_VALUE, allocator.getAllocatedMemory());
+      }
+    }
+  }
+
   public void assertEquiv(ArrowBuf origBuf, ArrowBuf newBuf) {
     assertEquals(origBuf.readerIndex(), newBuf.readerIndex());
     assertEquals(origBuf.writerIndex(), newBuf.writerIndex());