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

ethanfeng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-celeborn.git


The following commit(s) were added to refs/heads/main by this push:
     new 5654c62f3 [CELEBORN-347][Flink] fix memory leak and refactor 
BufferStreamManager (#1282)
5654c62f3 is described below

commit 5654c62f35b45bac1b564a31e790a5c43122b0b8
Author: Shuang <[email protected]>
AuthorDate: Tue Feb 28 15:18:59 2023 +0800

    [CELEBORN-347][Flink] fix memory leak and refactor BufferStreamManager 
(#1282)
---
 .../common/network/server/BufferStreamManager.java | 571 ++++++---------------
 .../common/network/server/DataPartitionReader.java | 448 ++++++++++++++++
 .../network/server/memory/BufferRecycler.java      |  44 ++
 .../server/memory/ReadBufferDispatcher.java        |  11 +-
 .../common/network/server/memory/Recycler.java     |  30 ++
 .../network/server/memory/WrappedDataBuffer.java   |  40 ++
 6 files changed, 721 insertions(+), 423 deletions(-)

diff --git 
a/common/src/main/java/org/apache/celeborn/common/network/server/BufferStreamManager.java
 
b/common/src/main/java/org/apache/celeborn/common/network/server/BufferStreamManager.java
index e48f28765..9e367776d 100644
--- 
a/common/src/main/java/org/apache/celeborn/common/network/server/BufferStreamManager.java
+++ 
b/common/src/main/java/org/apache/celeborn/common/network/server/BufferStreamManager.java
@@ -20,9 +20,7 @@ package org.apache.celeborn.common.network.server;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
-import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -32,13 +30,14 @@ import java.util.PriorityQueue;
 import java.util.Queue;
 import java.util.Random;
 import java.util.Set;
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.DelayQueue;
+import java.util.concurrent.Delayed;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingDeque;
-import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
 
@@ -47,22 +46,18 @@ import javax.annotation.concurrent.GuardedBy;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import io.netty.buffer.ByteBuf;
 import io.netty.channel.Channel;
-import io.netty.channel.ChannelFutureListener;
 import org.apache.commons.io.IOUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.celeborn.common.meta.FileInfo;
-import org.apache.celeborn.common.network.protocol.BacklogAnnouncement;
-import org.apache.celeborn.common.network.protocol.ReadData;
+import org.apache.celeborn.common.network.server.memory.BufferRecycler;
 import org.apache.celeborn.common.network.server.memory.MemoryManager;
-import org.apache.celeborn.common.util.Utils;
 
 public class BufferStreamManager {
   private static final Logger logger = 
LoggerFactory.getLogger(BufferStreamManager.class);
   private final AtomicLong nextStreamId;
   protected final ConcurrentHashMap<Long, StreamState> streams;
-  protected final ConcurrentHashMap<Long, AtomicInteger> streamCredits;
   protected final ConcurrentHashMap<Long, MapDataPartition> servingStreams;
   protected final ConcurrentHashMap<FileInfo, MapDataPartition> 
activeMapPartitions;
   protected final MemoryManager memoryManager = MemoryManager.instance();
@@ -70,10 +65,10 @@ public class BufferStreamManager {
   protected int minReadBuffers;
   protected int maxReadBuffers;
   protected int threadsPerMountPoint;
-  private final LinkedBlockingQueue<Long> recycleStreamIds = new 
LinkedBlockingQueue<>();
+  private final BlockingQueue<DelayedStreamId> recycleStreamIds = new 
DelayQueue<>();
 
   @GuardedBy("lock")
-  private Thread recycleThread;
+  private volatile Thread recycleThread;
 
   private final Object lock = new Object();
 
@@ -98,7 +93,6 @@ public class BufferStreamManager {
   public BufferStreamManager(int minReadBuffers, int maxReadBuffers, int 
threadsPerMountpoint) {
     nextStreamId = new AtomicLong((long) new 
Random().nextInt(Integer.MAX_VALUE) * 1000);
     streams = new ConcurrentHashMap<>();
-    streamCredits = new ConcurrentHashMap<>();
     servingStreams = new ConcurrentHashMap<>();
     activeMapPartitions = new ConcurrentHashMap<>();
     this.minReadBuffers = minReadBuffers;
@@ -116,7 +110,7 @@ public class BufferStreamManager {
       throws IOException {
     long streamId = nextStreamId.getAndIncrement();
     streams.put(streamId, new StreamState(channel, fileInfo.getBufferSize()));
-
+    logger.debug("Register stream start streamId: {}, fileInfo: {}", streamId, 
fileInfo);
     synchronized (activeMapPartitions) {
       MapDataPartition mapDataPartition = activeMapPartitions.get(fileInfo);
       if (mapDataPartition == null) {
@@ -128,7 +122,7 @@ public class BufferStreamManager {
       servingStreams.put(streamId, mapDataPartition);
       // response streamId to channel first
       callback.accept(streamId);
-      mapDataPartition.setupDataPartitionReader(startSubIndex, endSubIndex, 
streamId);
+      mapDataPartition.setupDataPartitionReader(startSubIndex, endSubIndex, 
streamId, channel);
     }
 
     logger.debug("Register stream streamId: {}, fileInfo: {}", streamId, 
fileInfo);
@@ -138,25 +132,13 @@ public class BufferStreamManager {
 
   public void addCredit(int numCredit, long streamId) {
     logger.debug("streamId: {}, add credit: {}", streamId, numCredit);
-    streamCredits.compute(
-        streamId,
-        (aLong, atomicInteger) -> {
-          if (atomicInteger == null) {
-            return new AtomicInteger(numCredit);
-          } else {
-            atomicInteger.getAndAdd(numCredit);
-            return atomicInteger;
-          }
-        });
-
-    MapDataPartition mapDataPartition = servingStreams.get(streamId);
-    if (mapDataPartition != null) {
-      DataPartitionReader streamReader = 
mapDataPartition.getStreamReader(streamId);
-      if (streamReader != null) {
-        storageFetcherPool
-            .getExecutorPool(streamReader.fileInfo.getMountPoint())
-            .submit(() -> streamReader.sendData());
+    try {
+      MapDataPartition mapDataPartition = servingStreams.get(streamId);
+      if (mapDataPartition != null) {
+        mapDataPartition.addReaderCredit(numCredit, streamId);
       }
+    } catch (Throwable e) {
+      logger.error("streamId: {}, add credit end: {}", streamId, numCredit);
     }
   }
 
@@ -170,7 +152,7 @@ public class BufferStreamManager {
   }
 
   public void recycleStream(long streamId) {
-    recycleStreamIds.add(streamId);
+    recycleStreamIds.add(new DelayedStreamId(streamId));
     startRecycleThread(); // lazy start thread
   }
 
@@ -183,13 +165,10 @@ public class BufferStreamManager {
                   () -> {
                     while (true) {
                       try {
-                        Long streamIds = recycleStreamIds.poll(100, 
TimeUnit.MILLISECONDS);
-                        if (streamIds != null) {
-                          cleanResource(streamIds);
-                        }
-                      } catch (InterruptedException e) {
+                        DelayedStreamId delayedStreamId = 
recycleStreamIds.take();
+                        cleanResource(delayedStreamId.streamId);
+                      } catch (Throwable e) {
                         logger.warn(e.getMessage(), e);
-                        throw new RuntimeException(e);
                       }
                     }
                   },
@@ -204,25 +183,69 @@ public class BufferStreamManager {
   }
 
   public void cleanResource(Long streamId) {
-    logger.debug("clean stream: {}", streamId);
-    streams.remove(streamId);
-    streamCredits.remove(streamId);
-    MapDataPartition mapDataPartition = servingStreams.remove(streamId);
-    if (mapDataPartition != null) {
-      FileInfo fileInfo = mapDataPartition.fileInfo;
-      mapDataPartition.removeStream(streamId);
-      synchronized (activeMapPartitions) {
-        if (mapDataPartition.activeStreamIds.isEmpty()) {
-          for (ByteBuf buffer : mapDataPartition.buffers) {
-            memoryManager.recycleReadBuffer(buffer);
+    logger.debug("received clean stream: {}", streamId);
+    if (streams.containsKey(streamId)) {
+      MapDataPartition mapDataPartition = servingStreams.get(streamId);
+      if (mapDataPartition != null) {
+        if (mapDataPartition.releaseStream(streamId)) {
+          synchronized (activeMapPartitions) {
+            if (mapDataPartition.activeStreamIds.isEmpty()) {
+              mapDataPartition.close();
+              FileInfo fileInfo = mapDataPartition.fileInfo;
+              activeMapPartitions.remove(fileInfo);
+            }
           }
-          activeMapPartitions.remove(fileInfo);
-          mapDataPartition.close();
+        } else {
+          logger.debug("retry clean stream: {}", streamId);
+          recycleStreamIds.add(new DelayedStreamId(streamId));
         }
       }
     }
   }
 
+  public static class DelayedStreamId implements Delayed {
+    private static final long delayTime = 100; // 100ms
+    private long createMillis = System.currentTimeMillis();
+
+    private long streamId;
+
+    public DelayedStreamId(long streamId) {
+      this.createMillis = createMillis + delayTime;
+      this.streamId = streamId;
+    }
+
+    @Override
+    public long getDelay(TimeUnit unit) {
+      long diff = createMillis - System.currentTimeMillis();
+      return unit.convert(diff, TimeUnit.MILLISECONDS);
+    }
+
+    public long getCreateMillis() {
+      return createMillis;
+    }
+
+    @Override
+    public int compareTo(Delayed o) {
+      long otherCreateMillis = ((DelayedStreamId) o).getCreateMillis();
+      if (this.createMillis < otherCreateMillis) {
+        return -1;
+      } else if (this.createMillis > otherCreateMillis) {
+        return 1;
+      }
+
+      return 0;
+    }
+
+    @Override
+    public String toString() {
+      final StringBuilder sb = new StringBuilder("DelayedStreamId{");
+      sb.append("createMillis=").append(createMillis);
+      sb.append(", streamId=").append(streamId);
+      sb.append('}');
+      return sb.toString();
+    }
+  }
+
   // this means active data partition
   protected class MapDataPartition {
     private final List<Long> activeStreamIds = new ArrayList<>();
@@ -233,10 +256,12 @@ public class BufferStreamManager {
         new ConcurrentHashMap<>();
 
     /** All available buffers can be used by the partition readers for 
reading. */
-    protected Queue<ByteBuf> buffers;
+    private Queue<ByteBuf> buffers;
 
-    protected FileChannel dataFileChanel;
-    protected FileChannel indexChannel;
+    private FileChannel dataFileChanel;
+    private FileChannel indexChannel;
+
+    private boolean isReleased;
 
     public MapDataPartition(FileInfo fileInfo) throws FileNotFoundException {
       this.fileInfo = fileInfo;
@@ -246,9 +271,15 @@ public class BufferStreamManager {
     }
 
     public synchronized void setupDataPartitionReader(
-        int startSubIndex, int endSubIndex, long streamId) throws IOException {
+        int startSubIndex, int endSubIndex, long streamId, Channel channel) 
throws IOException {
       DataPartitionReader dataPartitionReader =
-          new DataPartitionReader(startSubIndex, endSubIndex, fileInfo, 
streamId);
+          new DataPartitionReader(
+              startSubIndex,
+              endSubIndex,
+              fileInfo,
+              streamId,
+              channel,
+              () -> recycleStream(streamId));
       dataPartitionReader.open(dataFileChanel, indexChannel);
       // allocate resources when the first reader is registered
       boolean allocateResources = readers.isEmpty();
@@ -256,7 +287,7 @@ public class BufferStreamManager {
       streamReaders.put(streamId, dataPartitionReader);
 
       // create initial buffers for read
-      if (allocateResources) {
+      if (allocateResources && buffers == null) {
         memoryManager.requestReadBuffers(
             minReadBuffers,
             maxReadBuffers,
@@ -268,10 +299,6 @@ public class BufferStreamManager {
       }
     }
 
-    public DataPartitionReader getStreamReader(long streamId) {
-      return streamReaders.get(streamId);
-    }
-
     // Read logic is executed on another thread.
     public void onBuffer(Queue<ByteBuf> buffers) {
       this.buffers = buffers;
@@ -285,393 +312,99 @@ public class BufferStreamManager {
     }
 
     public synchronized void readBuffers() {
-      PriorityQueue<DataPartitionReader> sortedReaders = new 
PriorityQueue<>(readers);
-      while (buffers.size() > 0 && !sortedReaders.isEmpty()) {
-        DataPartitionReader reader = sortedReaders.poll();
-        try {
-          if (!reader.readAndSend(buffers, (buffer) -> this.recycle(buffer, 
buffers))) {
+      if (isReleased) {
+        // some read executor task may already be submitted to the threadpool
+        return;
+      }
+
+      try {
+        PriorityQueue<DataPartitionReader> sortedReaders = new 
PriorityQueue<>(readers);
+        while (buffers != null && buffers.size() > 0 && 
!sortedReaders.isEmpty()) {
+          BufferRecycler bufferRecycler =
+              new BufferRecycler(memoryManager, (buffer) -> 
this.recycle(buffer, buffers));
+          DataPartitionReader reader = sortedReaders.poll();
+          try {
+            if (!reader.readAndSend(buffers, bufferRecycler)) {
+              readers.remove(reader);
+            }
+          } catch (Throwable e) {
+            logger.error("reader exception, reader: {}, message: {}", reader, 
e.getMessage(), e);
             readers.remove(reader);
+            reader.recycleOnError(e);
           }
-        } catch (IOException e) {
-          logger.error("Read thread error occurred, {}", e);
-          throw new RuntimeException(e);
         }
-      }
-    }
-
-    public void triggerRead() {
-      readExecutor.submit(
-          () -> {
-            // Key for IO schedule.
-            readBuffers();
-          });
-    }
-
-    public synchronized void addStream(long streamId) {
-      activeStreamIds.add(streamId);
-    }
-
-    public synchronized void removeStream(long streamId) {
-      activeStreamIds.remove(streamId);
-    }
-
-    public void close() {
-      IOUtils.closeQuietly(dataFileChanel);
-      IOUtils.closeQuietly(indexChannel);
-    }
-  }
-
-  private class Buffer {
-    private ByteBuf byteBuf;
-    private Consumer<ByteBuf> byteBufferConsumer;
-
-    public Buffer(ByteBuf byteBuf, Consumer<ByteBuf> byteBufferConsumer) {
-      this.byteBuf = byteBuf;
-      this.byteBufferConsumer = byteBufferConsumer;
-    }
-  }
-
-  // this is a specific partition reader
-  protected class DataPartitionReader implements 
Comparable<DataPartitionReader> {
-    private final ByteBuffer indexBuffer;
-    private final ByteBuffer headerBuffer;
-    private final int startPartitionIndex;
-    private final int endPartitionIndex;
-    private int numRegions;
-    private int numRemainingPartitions;
-    private int currentDataRegion = -1;
-    private long dataConsumingOffset;
-    private volatile long currentPartitionRemainingBytes;
-    private boolean isClosed;
-    private FileInfo fileInfo;
-    private int INDEX_ENTRY_SIZE = 16;
-    private long streamId;
-    protected final Object lock = new Object();
-
-    @GuardedBy("lock")
-    protected final Queue<Buffer> buffersRead = new ArrayDeque<>();
-
-    /** Whether all the data has been successfully read or not. */
-    @GuardedBy("lock")
-    protected boolean isFinished;
-
-    /** Whether this partition reader has been released or not. */
-    @GuardedBy("lock")
-    protected boolean isReleased;
-
-    /** Exception causing the release of this partition reader. */
-    @GuardedBy("lock")
-    protected Throwable errorCause;
-
-    /** Whether there is any error at the consumer side or not. */
-    @GuardedBy("lock")
-    protected boolean isError;
-
-    private FileChannel dataFileChannel;
-    private FileChannel indexFileChannel;
-
-    public DataPartitionReader(
-        int startPartitionIndex, int endPartitionIndex, FileInfo fileInfo, 
long streamId) {
-      this.startPartitionIndex = startPartitionIndex;
-      this.endPartitionIndex = endPartitionIndex;
-
-      int indexBufferSize = 16 * (endPartitionIndex - startPartitionIndex + 1);
-      this.indexBuffer = ByteBuffer.allocateDirect(indexBufferSize);
-
-      this.headerBuffer = ByteBuffer.allocateDirect(16);
-      this.streamId = streamId;
-
-      this.fileInfo = fileInfo;
-      this.isClosed = false;
-    }
-
-    public void open(FileChannel dataFileChannel, FileChannel 
indexFileChannel) throws IOException {
-      this.dataFileChannel = dataFileChannel;
-      this.indexFileChannel = indexFileChannel;
-      long indexFileSize = indexFileChannel.size();
-      // index is (offset,length)
-      long indexRegionSize = fileInfo.getNumReducerPartitions() * (long) 
INDEX_ENTRY_SIZE;
-      this.numRegions = Utils.checkedDownCast(indexFileSize / indexRegionSize);
-
-      updateConsumingOffset();
-    }
-
-    public long getIndexRegionSize() {
-      return fileInfo.getNumReducerPartitions() * (long) INDEX_ENTRY_SIZE;
-    }
+      } catch (Throwable e) {
+        logger.error("Fatal: failed to read partition data. {}", 
e.getMessage(), e);
+        for (DataPartitionReader reader : readers) {
+          reader.recycleOnError(e);
+        }
 
-    private void readHeaderOrIndexBuffer(FileChannel channel, ByteBuffer 
buffer, int length)
-        throws IOException {
-      Utils.checkFileIntegrity(channel, length);
-      buffer.clear();
-      buffer.limit(length);
-      while (buffer.hasRemaining()) {
-        channel.read(buffer);
+        readers.clear();
       }
-      buffer.flip();
     }
 
-    private void readBufferIntoReadBuffer(FileChannel channel, ByteBuf buf, 
int length)
-        throws IOException {
-      Utils.checkFileIntegrity(channel, length);
-      ByteBuffer tmpBuffer = ByteBuffer.allocate(length);
-      while (tmpBuffer.hasRemaining()) {
-        channel.read(tmpBuffer);
+    // for one reader only the associated channel can access
+    public void addReaderCredit(int numCredit, long streamId) {
+      DataPartitionReader streamReader = this.getStreamReader(streamId);
+      if (streamReader != null) {
+        boolean canSendWithCredit = streamReader.sendWithCredit(numCredit);
+        if (canSendWithCredit) {
+          readExecutor.submit(() -> streamReader.sendData());
+        }
       }
-      tmpBuffer.flip();
-      buf.writeBytes(tmpBuffer);
     }
 
-    private int readBuffer(
-        String filename, FileChannel channel, ByteBuffer header, ByteBuf 
buffer, int headerSize)
-        throws IOException {
-      readHeaderOrIndexBuffer(channel, header, headerSize);
-      // header is combined of mapId(4),attemptId(4),nextBatchId(4) and 
totcalCompresszedLength(4)
-      // we need size here,so we read length directly
-      int bufferLenght = header.getInt(12);
-      if (bufferLenght <= 0 || bufferLenght > buffer.capacity()) {
-        logger.error("Incorrect buffer header, buffer length: {}.", 
bufferLenght);
-        throw new RuntimeException("File " + filename + " is corrupted");
-      }
-      buffer.writeBytes(header);
-      readBufferIntoReadBuffer(channel, buffer, bufferLenght);
-      return bufferLenght + headerSize;
+    public void triggerRead() {
+      // Key for IO schedule.
+      readExecutor.submit(() -> readBuffers());
     }
 
-    private void updateConsumingOffset() throws IOException {
-      while (currentPartitionRemainingBytes == 0
-          && (currentDataRegion < numRegions - 1 || numRemainingPartitions > 
0)) {
-        if (numRemainingPartitions <= 0) {
-          ++currentDataRegion;
-          numRemainingPartitions = endPartitionIndex - startPartitionIndex + 1;
-
-          // read the target index entry to the target index buffer
-          indexFileChannel.position(
-              currentDataRegion * getIndexRegionSize()
-                  + (long) startPartitionIndex * INDEX_ENTRY_SIZE);
-          readHeaderOrIndexBuffer(indexFileChannel, indexBuffer, 
indexBuffer.capacity());
-        }
-
-        // get the data file offset and the data size
-        dataConsumingOffset = indexBuffer.getLong();
-        currentPartitionRemainingBytes = indexBuffer.getLong();
-        --numRemainingPartitions;
-
-        logger.debug(
-            "readBuffer updateConsumingOffset, {},  {}, {}, {}",
-            streamId,
-            dataFileChannel.size(),
-            dataConsumingOffset,
-            currentPartitionRemainingBytes);
-
-        // if these checks fail, the partition file must be corrupted
-        if (dataConsumingOffset < 0
-            || dataConsumingOffset + currentPartitionRemainingBytes > 
dataFileChannel.size()
-            || currentPartitionRemainingBytes < 0) {
-          throw new RuntimeException("File " + fileInfo.getFilePath() + " is 
corrupted");
-        }
+    public void addStream(Long streamId) {
+      synchronized (activeStreamIds) {
+        activeStreamIds.add(streamId);
       }
     }
 
-    public boolean readBuffer(ByteBuf buffer) throws IOException {
-      try {
-        dataFileChannel.position(dataConsumingOffset);
-
-        int readSize =
-            readBuffer(
-                fileInfo.getFilePath(),
-                dataFileChannel,
-                headerBuffer,
-                buffer,
-                headerBuffer.capacity());
-        currentPartitionRemainingBytes -= readSize;
-
-        logger.debug(
-            "readBuffer data: {}, {}, {}, {}, {}, {}",
-            streamId,
-            currentPartitionRemainingBytes,
-            readSize,
-            dataConsumingOffset,
-            fileInfo.getFilePath(),
-            System.identityHashCode(buffer));
-
-        // if this check fails, the partition file must be corrupted
-        if (currentPartitionRemainingBytes < 0) {
-          throw new RuntimeException("File is corrupted");
-        } else if (currentPartitionRemainingBytes == 0) {
-          logger.debug(
-              "readBuffer end, {},  {}, {}, {}",
-              streamId,
-              dataFileChannel.size(),
-              dataConsumingOffset,
-              currentPartitionRemainingBytes);
-          int prevDataRegion = currentDataRegion;
-          updateConsumingOffset();
-          return prevDataRegion == currentDataRegion && 
currentPartitionRemainingBytes > 0;
-        }
-
-        dataConsumingOffset = dataFileChannel.position();
-
-        logger.debug(
-            "readBuffer run: {}, {}, {}, {}",
-            streamId,
-            dataFileChannel.size(),
-            dataConsumingOffset,
-            currentPartitionRemainingBytes);
-        return true;
-      } catch (Throwable throwable) {
-        logger.debug("Failed to read partition file.", throwable);
-        isReleased = true;
-        throw throwable;
+    public void removeStream(Long streamId) {
+      synchronized (activeStreamIds) {
+        activeStreamIds.remove(streamId);
+        streamReaders.remove(streamId);
       }
     }
 
-    public boolean hasRemaining() {
-      return currentPartitionRemainingBytes > 0;
+    public DataPartitionReader getStreamReader(long streamId) {
+      return streamReaders.get(streamId);
     }
 
-    public synchronized boolean readAndSend(Queue<ByteBuf> bufferQueue, 
Consumer<ByteBuf> consumer)
-        throws IOException {
-      boolean hasRemaing = hasRemaining();
-      boolean continueReading = hasRemaing;
-      int numDataBuffers = 0;
-      while (continueReading) {
-
-        ByteBuf buffer = bufferQueue.poll();
-        // this is used for control bytebuf manually.
-        if (buffer == null) {
-          break;
-        } else {
-          buffer.retain();
-        }
-
-        try {
-          continueReading = readBuffer(buffer);
-        } catch (Throwable throwable) {
-          memoryManager.recycleReadBuffer(buffer);
-          throw throwable;
-        }
-
-        hasRemaing = hasRemaining();
-        addBuffer(buffer, hasRemaing, consumer);
-        ++numDataBuffers;
-      }
-      if (numDataBuffers > 0) {
-        notifyBacklog(numDataBuffers);
-      }
-
-      if (!hasRemaing) {
-        closeReader();
+    public boolean releaseStream(Long streamId) {
+      DataPartitionReader dataPartitionReader = streamReaders.get(streamId);
+      dataPartitionReader.release();
+      if (dataPartitionReader.isFinished()) {
+        logger.info("release all for stream: {}", streamId);
+        removeStream(streamId);
+        streams.remove(streamId);
+        servingStreams.remove(streamId);
+        return true;
       }
 
-      return hasRemaing;
+      return false;
     }
 
-    protected void addBuffer(ByteBuf buffer, boolean hasRemaining, 
Consumer<ByteBuf> consumer) {
-      if (buffer == null) {
-        return;
-      }
-      final boolean recycleBuffer;
-      boolean notifyDataAvailable = false;
-      final Throwable throwable;
-      synchronized (lock) {
-        recycleBuffer = isReleased || isFinished || isError;
-        throwable = errorCause;
-        isFinished = !hasRemaining;
-
-        if (!recycleBuffer) {
-          notifyDataAvailable = buffersRead.isEmpty();
-          buffersRead.add(new Buffer(buffer, consumer));
-        }
-      }
+    public void close() {
+      logger.info("release map data partition {}", fileInfo);
 
-      if (recycleBuffer) {
-        memoryManager.recycleReadBuffer(buffer);
-        throw new RuntimeException("Partition reader has been failed or 
finished.", throwable);
-      }
-      if (!buffersRead.isEmpty()) {
-        sendData();
-      }
-    }
+      IOUtils.closeQuietly(dataFileChanel);
+      IOUtils.closeQuietly(indexChannel);
 
-    public synchronized void sendData() {
-      while (!buffersRead.isEmpty()) {
-        logger.debug("senddata streamid:{}, {}", streamId, 
streamCredits.get(streamId));
-        if (streamCredits.get(streamId).get() > 0) {
-          Buffer readBuf = buffersRead.poll();
-          logger.debug(
-              "send {} to stream {} ,fileInfo: {}",
-              readBuf.byteBuf.readableBytes(),
-              streamId,
-              fileInfo.getFilePath());
-          ReadData readData = new ReadData(streamId, buffersRead.size(), 0, 
readBuf.byteBuf);
-          streams
-              .get(streamId)
-              .associatedChannel
-              .writeAndFlush(readData)
-              .addListener(
-                  (ChannelFutureListener)
-                      future -> {
-                        if (!future.isSuccess()) {
-                          isError = true;
-                          errorCause = future.cause();
-                          memoryManager.recycleReadBuffer(readBuf.byteBuf);
-                          throw new RuntimeException(future.cause());
-                        } else {
-                          // netty has release the reference
-                        }
-                        logger.debug(
-                            "recycle {} , {} to stream {} ,fileinfo {}",
-                            readBuf.byteBuf.readableBytes(),
-                            System.identityHashCode(readBuf.byteBuf),
-                            streamId,
-                            fileInfo.getFilePath());
-                        readBuf.byteBufferConsumer.accept(readBuf.byteBuf);
-                      });
-          logger.debug(
-              "streamId: {}, decrement credit: {}",
-              streamId,
-              streamCredits.get(streamId).decrementAndGet());
-        } else {
-          logger.debug("streamId: {}, no credit: {}", streamId, 
streamCredits.get(streamId));
-          // no credit
-          break;
+      if (this.buffers != null) {
+        for (ByteBuf buffer : this.buffers) {
+          memoryManager.recycleReadBuffer(buffer);
         }
       }
 
-      logger.debug(
-          "streamId: {}, remaining: {}, bufferSize: {}",
-          streamId,
-          currentPartitionRemainingBytes,
-          buffersRead.size());
-
-      if (isClosed && buffersRead.isEmpty()) {
-        recycleStream(streamId);
-      }
-    }
-
-    public void closeReader() {
-      isClosed = true;
-      logger.debug("Closed read for stream {}", this.streamId);
-    }
-
-    protected void notifyBacklog(int backlog) {
-      logger.debug("stream manager streamid {} backlog:{}", streamId, backlog);
-      StreamState streamState = streams.get(streamId);
-      if (streamState == null) {
-        logger.warn("StreamId :{} already closed", streamId);
-      } else {
-        streamState.associatedChannel.writeAndFlush(new 
BacklogAnnouncement(streamId, backlog));
-      }
-    }
-
-    public long getPriority() {
-      return dataConsumingOffset;
-    }
+      this.buffers = null;
 
-    @Override
-    public int compareTo(DataPartitionReader that) {
-      return Long.compare(getPriority(), that.getPriority());
+      isReleased = true;
     }
   }
 
diff --git 
a/common/src/main/java/org/apache/celeborn/common/network/server/DataPartitionReader.java
 
b/common/src/main/java/org/apache/celeborn/common/network/server/DataPartitionReader.java
new file mode 100644
index 000000000..612712147
--- /dev/null
+++ 
b/common/src/main/java/org/apache/celeborn/common/network/server/DataPartitionReader.java
@@ -0,0 +1,448 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.celeborn.common.network.server;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.ArrayDeque;
+import java.util.Queue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.annotation.concurrent.GuardedBy;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFutureListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.celeborn.common.meta.FileInfo;
+import org.apache.celeborn.common.network.protocol.BacklogAnnouncement;
+import org.apache.celeborn.common.network.protocol.ReadData;
+import org.apache.celeborn.common.network.server.memory.Recycler;
+import org.apache.celeborn.common.network.server.memory.WrappedDataBuffer;
+import org.apache.celeborn.common.util.Utils;
+
+public class DataPartitionReader implements Comparable<DataPartitionReader> {
+  private static final Logger logger = 
LoggerFactory.getLogger(DataPartitionReader.class);
+
+  private final ByteBuffer indexBuffer;
+  private final ByteBuffer headerBuffer;
+  private final int startPartitionIndex;
+  private final int endPartitionIndex;
+  private int numRegions;
+  private int numRemainingPartitions;
+  private int currentDataRegion = -1;
+  private long dataConsumingOffset;
+  private volatile long currentPartitionRemainingBytes;
+  private FileInfo fileInfo;
+  private int INDEX_ENTRY_SIZE = 16;
+  private long streamId;
+  protected final Object lock = new Object();
+
+  private AtomicInteger credits = new AtomicInteger();
+
+  @GuardedBy("lock")
+  protected final Queue<WrappedDataBuffer> buffersRead = new ArrayDeque<>();
+
+  /** Whether all the data has been successfully read or not. */
+  @GuardedBy("lock")
+  private boolean isClosed;
+
+  /** Whether this partition reader has been released or not. */
+  @GuardedBy("lock")
+  protected boolean isReleased;
+
+  /** Exception causing the release of this partition reader. */
+  @GuardedBy("lock")
+  protected Throwable errorCause;
+
+  /** Whether there is any error at the consumer side or not. */
+  @GuardedBy("lock")
+  protected boolean isError;
+
+  private FileChannel dataFileChannel;
+  private FileChannel indexFileChannel;
+
+  private Channel associatedChannel;
+
+  private Runnable recycleStream;
+
+  private AtomicInteger numInFlightRequests = new AtomicInteger(0);
+
+  public DataPartitionReader(
+      int startPartitionIndex,
+      int endPartitionIndex,
+      FileInfo fileInfo,
+      long streamId,
+      Channel associatedChannel,
+      Runnable recycleStream) {
+    this.startPartitionIndex = startPartitionIndex;
+    this.endPartitionIndex = endPartitionIndex;
+
+    int indexBufferSize = 16 * (endPartitionIndex - startPartitionIndex + 1);
+    this.indexBuffer = ByteBuffer.allocateDirect(indexBufferSize);
+
+    this.headerBuffer = ByteBuffer.allocateDirect(16);
+    this.streamId = streamId;
+    this.associatedChannel = associatedChannel;
+    this.recycleStream = recycleStream;
+
+    this.fileInfo = fileInfo;
+    this.isClosed = false;
+  }
+
+  public void open(FileChannel dataFileChannel, FileChannel indexFileChannel) 
throws IOException {
+    this.dataFileChannel = dataFileChannel;
+    this.indexFileChannel = indexFileChannel;
+    long indexFileSize = indexFileChannel.size();
+    // index is (offset,length)
+    long indexRegionSize = fileInfo.getNumReducerPartitions() * (long) 
INDEX_ENTRY_SIZE;
+    this.numRegions = Utils.checkedDownCast(indexFileSize / indexRegionSize);
+
+    updateConsumingOffset();
+  }
+
+  public boolean sendWithCredit(int credit) {
+    int oldCredit = credits.getAndAdd(credit);
+    if (oldCredit == 0) {
+      return true;
+    }
+
+    return false;
+  }
+
+  public synchronized boolean readAndSend(Queue<ByteBuf> bufferQueue, Recycler 
bufferRecycler)
+      throws IOException {
+    boolean hasRemaining = hasRemaining();
+    boolean continueReading = hasRemaining;
+    int numDataBuffers = 0;
+    while (continueReading) {
+
+      ByteBuf buffer = bufferQueue.poll();
+      // this is used for control bytebuf manually.
+      if (buffer == null) {
+        break;
+      } else {
+        buffer.retain();
+      }
+
+      try {
+        continueReading = readBuffer(buffer);
+      } catch (Throwable throwable) {
+        bufferRecycler.release(buffer);
+        throw throwable;
+      }
+
+      hasRemaining = hasRemaining();
+      addBuffer(buffer, hasRemaining, bufferRecycler);
+      ++numDataBuffers;
+    }
+    if (numDataBuffers > 0) {
+      notifyBacklog(numDataBuffers);
+    }
+
+    if (!hasRemaining) {
+      closeReader();
+    }
+
+    return hasRemaining;
+  }
+
+  private void addBuffer(ByteBuf buffer, boolean hasRemaining, Recycler 
bufferRecycler) {
+    if (buffer == null) {
+      return;
+    }
+    final boolean recycleBuffer;
+    boolean notifyDataAvailable = false;
+    final Throwable throwable;
+    synchronized (lock) {
+      recycleBuffer = isReleased || isClosed || isError;
+      throwable = errorCause;
+      isClosed = !hasRemaining;
+
+      if (!recycleBuffer) {
+        notifyDataAvailable = buffersRead.isEmpty();
+        buffersRead.add(new WrappedDataBuffer(buffer, bufferRecycler));
+      }
+    }
+
+    if (recycleBuffer) {
+      bufferRecycler.release(buffer);
+      throw new RuntimeException("Partition reader has been failed or 
finished.", throwable);
+    }
+    if (notifyDataAvailable) {
+      sendData();
+    }
+  }
+
+  public synchronized void sendData() {
+    while (!buffersRead.isEmpty() && credits.get() > 0) {
+      WrappedDataBuffer wrappedBuffer;
+      synchronized (lock) {
+        wrappedBuffer = buffersRead.poll();
+        numInFlightRequests.incrementAndGet();
+      }
+
+      ByteBuf byteBuf = wrappedBuffer.byteBuf;
+      int backlog = buffersRead.size();
+      int readableBytes = byteBuf.readableBytes();
+      logger.debug("send data start: {}, {}, {}", streamId, readableBytes, 
backlog);
+      ReadData readData = new ReadData(streamId, backlog, 0, byteBuf);
+      associatedChannel
+          .writeAndFlush(readData)
+          .addListener(
+              (ChannelFutureListener)
+                  future -> {
+                    try {
+                      if (!future.isSuccess()) {
+                        wrappedBuffer.release();
+                        recycleOnError(future.cause());
+                      } else {
+                        wrappedBuffer.recycle();
+                      }
+                    } finally {
+                      logger.debug("send data start: {}, {}, {}", streamId, 
readableBytes, backlog);
+                      numInFlightRequests.decrementAndGet();
+                    }
+                  });
+
+      credits.decrementAndGet();
+    }
+
+    if (isClosed && buffersRead.isEmpty()) {
+      recycle();
+    }
+  }
+
+  private long getIndexRegionSize() {
+    return fileInfo.getNumReducerPartitions() * (long) INDEX_ENTRY_SIZE;
+  }
+
+  private void readHeaderOrIndexBuffer(FileChannel channel, ByteBuffer buffer, 
int length)
+      throws IOException {
+    Utils.checkFileIntegrity(channel, length);
+    buffer.clear();
+    buffer.limit(length);
+    while (buffer.hasRemaining()) {
+      channel.read(buffer);
+    }
+    buffer.flip();
+  }
+
+  private void readBufferIntoReadBuffer(FileChannel channel, ByteBuf buf, int 
length)
+      throws IOException {
+    Utils.checkFileIntegrity(channel, length);
+    ByteBuffer tmpBuffer = ByteBuffer.allocate(length);
+    while (tmpBuffer.hasRemaining()) {
+      channel.read(tmpBuffer);
+    }
+    tmpBuffer.flip();
+    buf.writeBytes(tmpBuffer);
+  }
+
+  private int readBuffer(
+      String filename, FileChannel channel, ByteBuffer header, ByteBuf buffer, 
int headerSize)
+      throws IOException {
+    readHeaderOrIndexBuffer(channel, header, headerSize);
+    // header is combined of mapId(4),attemptId(4),nextBatchId(4) and total 
Compresszed Length(4)
+    // we need size here,so we read length directly
+    int bufferLength = header.getInt(12);
+    if (bufferLength <= 0 || bufferLength > buffer.capacity()) {
+      logger.error("Incorrect buffer header, buffer length: {}.", 
bufferLength);
+      throw new RuntimeException("File " + filename + " is corrupted");
+    }
+    buffer.writeBytes(header);
+    readBufferIntoReadBuffer(channel, buffer, bufferLength);
+    return bufferLength + headerSize;
+  }
+
+  private void updateConsumingOffset() throws IOException {
+    while (currentPartitionRemainingBytes == 0
+        && (currentDataRegion < numRegions - 1 || numRemainingPartitions > 0)) 
{
+      if (numRemainingPartitions <= 0) {
+        ++currentDataRegion;
+        numRemainingPartitions = endPartitionIndex - startPartitionIndex + 1;
+
+        // read the target index entry to the target index buffer
+        indexFileChannel.position(
+            currentDataRegion * getIndexRegionSize()
+                + (long) startPartitionIndex * INDEX_ENTRY_SIZE);
+        readHeaderOrIndexBuffer(indexFileChannel, indexBuffer, 
indexBuffer.capacity());
+      }
+
+      // get the data file offset and the data size
+      dataConsumingOffset = indexBuffer.getLong();
+      currentPartitionRemainingBytes = indexBuffer.getLong();
+      --numRemainingPartitions;
+
+      logger.debug(
+          "readBuffer updateConsumingOffset, {},  {}, {}, {}",
+          streamId,
+          dataFileChannel.size(),
+          dataConsumingOffset,
+          currentPartitionRemainingBytes);
+
+      // if these checks fail, the partition file must be corrupted
+      if (dataConsumingOffset < 0
+          || dataConsumingOffset + currentPartitionRemainingBytes > 
dataFileChannel.size()
+          || currentPartitionRemainingBytes < 0) {
+        throw new RuntimeException("File " + fileInfo.getFilePath() + " is 
corrupted");
+      }
+    }
+  }
+
+  private boolean readBuffer(ByteBuf buffer) throws IOException {
+    try {
+      dataFileChannel.position(dataConsumingOffset);
+
+      int readSize =
+          readBuffer(
+              fileInfo.getFilePath(),
+              dataFileChannel,
+              headerBuffer,
+              buffer,
+              headerBuffer.capacity());
+      currentPartitionRemainingBytes -= readSize;
+
+      logger.debug(
+          "readBuffer data: {}, {}, {}, {}, {}, {}",
+          streamId,
+          currentPartitionRemainingBytes,
+          readSize,
+          dataConsumingOffset,
+          fileInfo.getFilePath(),
+          System.identityHashCode(buffer));
+
+      // if this check fails, the partition file must be corrupted
+      if (currentPartitionRemainingBytes < 0) {
+        throw new RuntimeException("File is corrupted");
+      } else if (currentPartitionRemainingBytes == 0) {
+        logger.debug(
+            "readBuffer end, {},  {}, {}, {}",
+            streamId,
+            dataFileChannel.size(),
+            dataConsumingOffset,
+            currentPartitionRemainingBytes);
+        int prevDataRegion = currentDataRegion;
+        updateConsumingOffset();
+        return prevDataRegion == currentDataRegion && 
currentPartitionRemainingBytes > 0;
+      }
+
+      dataConsumingOffset = dataFileChannel.position();
+
+      logger.debug(
+          "readBuffer run: {}, {}, {}, {}",
+          streamId,
+          dataFileChannel.size(),
+          dataConsumingOffset,
+          currentPartitionRemainingBytes);
+      return true;
+    } catch (Throwable throwable) {
+      logger.debug("Failed to read partition file.", throwable);
+      isReleased = true;
+      throw throwable;
+    }
+  }
+
+  public boolean hasRemaining() {
+    return currentPartitionRemainingBytes > 0;
+  }
+
+  private void notifyBacklog(int backlog) {
+    logger.debug("stream manager stream id {} backlog:{}", streamId, backlog);
+    associatedChannel.writeAndFlush(new BacklogAnnouncement(streamId, 
backlog));
+  }
+
+  private void notifyError(Throwable throwable) {
+    logger.error("read error stream id {} message:{}", streamId, 
throwable.getMessage(), throwable);
+    // TODO notify client the exception
+  }
+
+  public long getPriority() {
+    return dataConsumingOffset;
+  }
+
+  @Override
+  public int compareTo(DataPartitionReader that) {
+    return Long.compare(getPriority(), that.getPriority());
+  }
+
+  public FileInfo getFileInfo() {
+    return fileInfo;
+  }
+
+  public void closeReader() {
+    synchronized (lock) {
+      isClosed = true;
+    }
+
+    logger.debug("Closed read for stream {}", this.streamId);
+  }
+
+  private void recycle() {
+    synchronized (lock) {
+      if (!isReleased) {
+        release();
+        recycleStream.run();
+      }
+    }
+  }
+
+  public void recycleOnError(Throwable throwable) {
+    synchronized (lock) {
+      if (!isError) {
+        isError = true;
+        errorCause = throwable;
+        notifyError(throwable);
+        recycle();
+      }
+    }
+  }
+
+  public void release() {
+    // we can safely release if reader reaches error or (read/send finished)
+    synchronized (lock) {
+      if (!isReleased) {
+        logger.debug("release reader for stream {}", this.streamId);
+        isReleased = true;
+        if (!buffersRead.isEmpty()) {
+          buffersRead.forEach(WrappedDataBuffer::release);
+        }
+      }
+    }
+  }
+
+  public boolean isFinished() {
+    synchronized (lock) {
+      // ensure every buffer are return to bufferQueue or release in 
buffersRead
+      return numInFlightRequests.get() == 0 && isReleased;
+    }
+  }
+
+  @Override
+  public String toString() {
+    final StringBuilder sb = new StringBuilder("DataPartitionReader{");
+    sb.append("startPartitionIndex=").append(startPartitionIndex);
+    sb.append(", endPartitionIndex=").append(endPartitionIndex);
+    sb.append(", streamId=").append(streamId);
+    sb.append('}');
+    return sb.toString();
+  }
+}
diff --git 
a/common/src/main/java/org/apache/celeborn/common/network/server/memory/BufferRecycler.java
 
b/common/src/main/java/org/apache/celeborn/common/network/server/memory/BufferRecycler.java
new file mode 100644
index 000000000..069bc81e0
--- /dev/null
+++ 
b/common/src/main/java/org/apache/celeborn/common/network/server/memory/BufferRecycler.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.celeborn.common.network.server.memory;
+
+import java.util.function.Consumer;
+
+import io.netty.buffer.ByteBuf;
+
+public class BufferRecycler implements Recycler {
+
+  private MemoryManager memoryManager;
+
+  private Consumer<ByteBuf> recycleConsumer;
+
+  public BufferRecycler(MemoryManager memoryManager, Consumer<ByteBuf> 
recycleConsumer) {
+    this.memoryManager = memoryManager;
+    this.recycleConsumer = recycleConsumer;
+  }
+
+  @Override
+  public void recycle(ByteBuf byteBuf) {
+    recycleConsumer.accept(byteBuf);
+  }
+
+  @Override
+  public void release(ByteBuf byteBuf) {
+    memoryManager.recycleReadBuffer(byteBuf);
+  }
+}
diff --git 
a/common/src/main/java/org/apache/celeborn/common/network/server/memory/ReadBufferDispatcher.java
 
b/common/src/main/java/org/apache/celeborn/common/network/server/memory/ReadBufferDispatcher.java
index ca76a25f8..ecaddb72d 100644
--- 
a/common/src/main/java/org/apache/celeborn/common/network/server/memory/ReadBufferDispatcher.java
+++ 
b/common/src/main/java/org/apache/celeborn/common/network/server/memory/ReadBufferDispatcher.java
@@ -30,7 +30,7 @@ import org.slf4j.LoggerFactory;
 import org.apache.celeborn.common.network.util.NettyUtils;
 
 public class ReadBufferDispatcher extends Thread {
-  private Logger logger = LoggerFactory.getLogger(ReadBufferDispatcher.class);
+  private final Logger logger = 
LoggerFactory.getLogger(ReadBufferDispatcher.class);
   private final LinkedBlockingQueue<ReadBufferRequest> requests = new 
LinkedBlockingQueue<>();
   private final MemoryManager memoryManager;
   private final PooledByteBufAllocator readBufferAllocator;
@@ -48,7 +48,11 @@ public class ReadBufferDispatcher extends Thread {
 
   public void recycle(ByteBuf buf) {
     int bufferSize = buf.capacity();
-    buf.release();
+    if (buf.refCnt() == 0) {
+      logger.warn("recycle encounter: {}", buf.refCnt());
+    } else {
+      buf.release(buf.refCnt());
+    }
     memoryManager.changeReadBufferCounter(-1 * bufferSize);
   }
 
@@ -61,6 +65,7 @@ public class ReadBufferDispatcher extends Thread {
       } catch (InterruptedException e) {
         logger.info("Buffer dispatcher is closing");
       }
+
       if (request != null) {
         List<ByteBuf> buffers = new ArrayList<>();
         int bufferSize = request.getBufferSize();
@@ -68,7 +73,6 @@ public class ReadBufferDispatcher extends Thread {
           if (memoryManager.readBufferAvailable(bufferSize)) {
             memoryManager.incrementDiskBuffer(bufferSize);
             ByteBuf buf = readBufferAllocator.buffer(bufferSize, bufferSize);
-            buf.retain();
             buffers.add(buf);
           } else {
             try {
@@ -86,7 +90,6 @@ public class ReadBufferDispatcher extends Thread {
             && buffers.size() < request.getMax()) {
           memoryManager.changeReadBufferCounter(bufferSize);
           ByteBuf buf = readBufferAllocator.buffer(bufferSize, bufferSize);
-          buf.retain();
           buffers.add(buf);
         }
         request.getBufferListener().notifyBuffers(buffers, null);
diff --git 
a/common/src/main/java/org/apache/celeborn/common/network/server/memory/Recycler.java
 
b/common/src/main/java/org/apache/celeborn/common/network/server/memory/Recycler.java
new file mode 100644
index 000000000..2c8493d7f
--- /dev/null
+++ 
b/common/src/main/java/org/apache/celeborn/common/network/server/memory/Recycler.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.celeborn.common.network.server.memory;
+
+import io.netty.buffer.ByteBuf;
+
+/** manager bytebuf lifecycle */
+public interface Recycler {
+
+  /** recycle bytebuf to buffer pool */
+  void recycle(ByteBuf byteBuf);
+
+  /** release the bytebuf */
+  void release(ByteBuf byteBuf);
+}
diff --git 
a/common/src/main/java/org/apache/celeborn/common/network/server/memory/WrappedDataBuffer.java
 
b/common/src/main/java/org/apache/celeborn/common/network/server/memory/WrappedDataBuffer.java
new file mode 100644
index 000000000..fdbd9d1be
--- /dev/null
+++ 
b/common/src/main/java/org/apache/celeborn/common/network/server/memory/WrappedDataBuffer.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.celeborn.common.network.server.memory;
+
+import io.netty.buffer.ByteBuf;
+
+public class WrappedDataBuffer {
+
+  public final ByteBuf byteBuf;
+
+  public final Recycler bufferRecycler;
+
+  public WrappedDataBuffer(ByteBuf byteBuf, Recycler bufferRecycler) {
+    this.byteBuf = byteBuf;
+    this.bufferRecycler = bufferRecycler;
+  }
+
+  public void recycle() {
+    bufferRecycler.recycle(byteBuf);
+  }
+
+  public void release() {
+    bufferRecycler.release(byteBuf);
+  }
+}

Reply via email to