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

guoweijie pushed a commit to branch release-1.19
in repository https://gitbox.apache.org/repos/asf/flink.git


The following commit(s) were added to refs/heads/release-1.19 by this push:
     new 6045bd8bf93 [FLINK-35169][runtime] Recycle buffers to freeSegments 
before releasing data buffer for sort accumulator
6045bd8bf93 is described below

commit 6045bd8bf935b12c8056e98945e054511da9c251
Author: Yuxin Tan <[email protected]>
AuthorDate: Fri Apr 19 16:40:55 2024 +0800

    [FLINK-35169][runtime] Recycle buffers to freeSegments before releasing 
data buffer for sort accumulator
---
 .../tiered/storage/SortBufferAccumulator.java      | 92 +++++++++++++++-------
 .../tiered/storage/SortBufferAccumulatorTest.java  | 44 +++++++++++
 2 files changed, 108 insertions(+), 28 deletions(-)

diff --git 
a/flink-runtime/src/main/java/org/apache/flink/runtime/io/network/partition/hybrid/tiered/storage/SortBufferAccumulator.java
 
b/flink-runtime/src/main/java/org/apache/flink/runtime/io/network/partition/hybrid/tiered/storage/SortBufferAccumulator.java
index 12e63aba55d..4c9d3cf7e79 100644
--- 
a/flink-runtime/src/main/java/org/apache/flink/runtime/io/network/partition/hybrid/tiered/storage/SortBufferAccumulator.java
+++ 
b/flink-runtime/src/main/java/org/apache/flink/runtime/io/network/partition/hybrid/tiered/storage/SortBufferAccumulator.java
@@ -30,6 +30,7 @@ import 
org.apache.flink.runtime.io.network.partition.hybrid.tiered.common.Tiered
 import org.apache.flink.util.function.TriConsumer;
 
 import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -65,6 +66,7 @@ public class SortBufferAccumulator implements 
BufferAccumulator {
     private final int bufferSizeBytes;
 
     /** The empty buffers without storing data. */
+    @GuardedBy("lock")
     private final LinkedList<MemorySegment> freeSegments = new LinkedList<>();
 
     /** The memory manager of the tiered storage. */
@@ -78,7 +80,9 @@ public class SortBufferAccumulator implements 
BufferAccumulator {
      * data integrity. Note that this can be null before using it to store 
records, and this {@link
      * DataBuffer} will be released once flushed.
      */
-    @Nullable private TieredStorageSortBuffer currentDataBuffer;
+    @GuardedBy("lock")
+    @Nullable
+    private TieredStorageSortBuffer currentDataBuffer;
 
     /**
      * The buffer recycler. Note that this can be null before requesting 
buffers from the memory
@@ -97,6 +101,11 @@ public class SortBufferAccumulator implements 
BufferAccumulator {
     /** Whether the current {@link DataBuffer} is a broadcast sort buffer. */
     private boolean isBroadcastDataBuffer;
 
+    @GuardedBy("lock")
+    private boolean isDataBufferReleased;
+
+    private final Object lock = new Object();
+
     public SortBufferAccumulator(
             int numSubpartitions,
             int numBuffers,
@@ -123,38 +132,44 @@ public class SortBufferAccumulator implements 
BufferAccumulator {
             boolean isBroadcast)
             throws IOException {
         int targetSubpartition = subpartitionId.getSubpartitionId();
-        switchCurrentDataBufferIfNeeded(isBroadcast);
-        if (!checkNotNull(currentDataBuffer).append(record, 
targetSubpartition, dataType)) {
-            return;
-        }
+        synchronized (lock) {
+            switchCurrentDataBufferIfNeeded(isBroadcast);
+            if (!checkNotNull(currentDataBuffer).append(record, 
targetSubpartition, dataType)) {
+                return;
+            }
 
-        // The sort buffer is empty, but we failed to write the record into 
it, which indicates the
-        // record is larger than the sort buffer can hold. So the record is 
written into multiple
-        // buffers directly.
-        if (!currentDataBuffer.hasRemaining()) {
-            currentDataBuffer.release();
-            writeLargeRecord(record, targetSubpartition, dataType);
-            return;
+            // The sort buffer is empty, but we failed to write the record 
into it, which indicates
+            // the record is larger than the sort buffer can hold. So the 
record is written into
+            // multiple buffers directly.
+            if (!currentDataBuffer.hasRemaining()) {
+                isDataBufferReleased = true;
+                currentDataBuffer.release();
+                writeLargeRecord(record, targetSubpartition, dataType);
+                return;
+            }
+            flushDataBuffer();
         }
 
-        flushDataBuffer();
         checkState(record.hasRemaining(), "Empty record.");
         receive(record, subpartitionId, dataType, isBroadcast);
     }
 
     @Override
     public void close() {
-        flushCurrentDataBuffer();
-        releaseFreeBuffers();
-        if (currentDataBuffer != null) {
-            currentDataBuffer.release();
+        synchronized (lock) {
+            flushCurrentDataBuffer();
+            isDataBufferReleased = true;
+            releaseFreeBuffers();
+            if (currentDataBuffer != null) {
+                currentDataBuffer.release();
+            }
         }
     }
 
     // ------------------------------------------------------------------------
     //  Internal Methods
     // ------------------------------------------------------------------------
-
+    @GuardedBy("lock")
     private void switchCurrentDataBufferIfNeeded(boolean isBroadcast) {
         if (isBroadcast == isBroadcastDataBuffer
                 && currentDataBuffer != null
@@ -164,9 +179,12 @@ public class SortBufferAccumulator implements 
BufferAccumulator {
         }
         isBroadcastDataBuffer = isBroadcast;
         flushCurrentDataBuffer();
+        isDataBufferReleased = true;
         currentDataBuffer = createNewDataBuffer();
+        isDataBufferReleased = false;
     }
 
+    @GuardedBy("lock")
     private TieredStorageSortBuffer createNewDataBuffer() {
         requestBuffers();
 
@@ -181,6 +199,7 @@ public class SortBufferAccumulator implements 
BufferAccumulator {
                 isPartialRecordAllowed);
     }
 
+    @GuardedBy("lock")
     private void requestBuffers() {
         while (freeSegments.size() < numBuffers) {
             Buffer buffer = requestBuffer();
@@ -191,6 +210,7 @@ public class SortBufferAccumulator implements 
BufferAccumulator {
         }
     }
 
+    @GuardedBy("lock")
     private void flushDataBuffer() {
         if (currentDataBuffer == null
                 || currentDataBuffer.isReleased()
@@ -214,14 +234,17 @@ public class SortBufferAccumulator implements 
BufferAccumulator {
             flushBuffer(bufferWithSubpartition, 
numRemainingConsecutiveBuffers);
         } while (true);
 
+        isDataBufferReleased = true;
         releaseFreeBuffers();
         currentDataBuffer.release();
     }
 
     private void flushCurrentDataBuffer() {
-        if (currentDataBuffer != null) {
-            flushDataBuffer();
-            currentDataBuffer = null;
+        synchronized (lock) {
+            if (currentDataBuffer != null) {
+                flushDataBuffer();
+                currentDataBuffer = null;
+            }
         }
     }
 
@@ -251,11 +274,13 @@ public class SortBufferAccumulator implements 
BufferAccumulator {
     }
 
     private MemorySegment getFreeSegment() {
-        MemorySegment freeSegment = freeSegments.poll();
-        if (freeSegment == null) {
-            freeSegment = requestBuffer().getMemorySegment();
+        synchronized (lock) {
+            MemorySegment freeSegment = freeSegments.poll();
+            if (freeSegment == null) {
+                freeSegment = requestBuffer().getMemorySegment();
+            }
+            return freeSegment;
         }
-        return freeSegment;
     }
 
     private void flushBuffer(
@@ -278,11 +303,22 @@ public class SortBufferAccumulator implements 
BufferAccumulator {
     }
 
     private void releaseFreeBuffers() {
-        freeSegments.forEach(this::recycleBuffer);
-        freeSegments.clear();
+        synchronized (lock) {
+            isDataBufferReleased = true;
+            freeSegments.forEach(this::recycleBuffer);
+            freeSegments.clear();
+        }
     }
 
     private void recycleBuffer(MemorySegment memorySegment) {
-        checkNotNull(bufferRecycler).recycle(memorySegment);
+        synchronized (lock) {
+            if (!isDataBufferReleased
+                    && currentDataBuffer != null
+                    && !currentDataBuffer.isReleased()) {
+                freeSegments.add(memorySegment);
+            } else {
+                checkNotNull(bufferRecycler).recycle(memorySegment);
+            }
+        }
     }
 }
diff --git 
a/flink-runtime/src/test/java/org/apache/flink/runtime/io/network/partition/hybrid/tiered/storage/SortBufferAccumulatorTest.java
 
b/flink-runtime/src/test/java/org/apache/flink/runtime/io/network/partition/hybrid/tiered/storage/SortBufferAccumulatorTest.java
index 50b705730d1..ed093c2b9d7 100644
--- 
a/flink-runtime/src/test/java/org/apache/flink/runtime/io/network/partition/hybrid/tiered/storage/SortBufferAccumulatorTest.java
+++ 
b/flink-runtime/src/test/java/org/apache/flink/runtime/io/network/partition/hybrid/tiered/storage/SortBufferAccumulatorTest.java
@@ -18,9 +18,12 @@
 
 package org.apache.flink.runtime.io.network.partition.hybrid.tiered.storage;
 
+import org.apache.flink.core.memory.MemorySegment;
 import org.apache.flink.runtime.io.network.buffer.Buffer;
+import org.apache.flink.runtime.io.network.buffer.BufferBuilder;
 import org.apache.flink.runtime.io.network.buffer.BufferPool;
 import org.apache.flink.runtime.io.network.buffer.NetworkBufferPool;
+import 
org.apache.flink.runtime.io.network.partition.hybrid.tiered.TestingTieredStorageMemoryManager;
 import 
org.apache.flink.runtime.io.network.partition.hybrid.tiered.common.TieredStorageSubpartitionId;
 
 import org.junit.jupiter.api.AfterEach;
@@ -202,6 +205,47 @@ class SortBufferAccumulatorTest {
         
assertThat(tieredStorageMemoryManager.numOwnerRequestedBuffer(bufferAccumulator)).isZero();
     }
 
+    @Test
+    void testReuseRecycledBuffersWhenFlushDataBuffer() throws IOException {
+        int numBuffers = 10;
+        int numSubpartitions = 10;
+
+        AtomicInteger numRequestedBuffers = new AtomicInteger(0);
+        TestingTieredStorageMemoryManager memoryManager =
+                new TestingTieredStorageMemoryManager.Builder()
+                        .setRequestBufferBlockingFunction(
+                                owner -> {
+                                    numRequestedBuffers.incrementAndGet();
+                                    MemorySegment memorySegment =
+                                            
globalPool.requestPooledMemorySegment();
+                                    // When flushing data from the data 
buffer, the buffers should
+                                    // not be requested from here anymore 
because the freeSegments
+                                    // can be reused before released.
+                                    assertThat(numRequestedBuffers.get() <= 
numBuffers).isTrue();
+                                    return new BufferBuilder(
+                                            memorySegment,
+                                            segment -> 
globalPool.requestPooledMemorySegment());
+                                })
+                        .build();
+
+        SortBufferAccumulator bufferAccumulator =
+                new SortBufferAccumulator(
+                        numSubpartitions, numBuffers, BUFFER_SIZE_BYTES, 
memoryManager, true);
+        bufferAccumulator.setup(
+                ((subpartition, buffer, numRemainingBuffers) -> 
buffer.recycleBuffer()));
+
+        for (int i = 0; i < numSubpartitions; i++) {
+            bufferAccumulator.receive(
+                    generateRandomData(10, new Random()),
+                    new TieredStorageSubpartitionId(i % numSubpartitions),
+                    Buffer.DataType.DATA_BUFFER,
+                    false);
+        }
+
+        // Trigger flushing buffers from data buffer.
+        bufferAccumulator.close();
+    }
+
     private TieredStorageMemoryManagerImpl createStorageMemoryManager(int 
numBuffersInBufferPool)
             throws IOException {
         BufferPool bufferPool =

Reply via email to