Copilot commented on code in PR #3809:
URL: https://github.com/apache/celeborn/pull/3809#discussion_r3845253285


##########
worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/TierWriter.scala:
##########
@@ -88,6 +88,32 @@ abstract class TierWriterBase(
 
   protected def writeInternal(buf: ByteBuf): Unit
 
+  protected def accountAddedBytes(numBytes: Int): Unit
+
+  protected def appendToFlushBuffer(buf: ByteBuf): Unit = {
+    val numBytes = buf.readableBytes()
+    val writerIndexBefore = flushBuffer.writerIndex()
+    val numComponentsBefore = flushBuffer.numComponents()
+    buf.retain()
+    try {
+      flushBuffer.addComponent(true, buf)
+    } catch {
+      case t: Throwable =>
+        // addComponent can fail after taking ownership while consolidating 
components.
+        val componentAdded =
+          flushBuffer.writerIndex() != writerIndexBefore ||
+            flushBuffer.numComponents() != numComponentsBefore
+        if (componentAdded) {
+          accountAddedBytes(numBytes)
+        }
+        logError(
+          s"Failed to add $numBytes bytes to flush buffer for shuffle 
$shuffleKey file $filename.",
+          t)
+        throw t
+    }
+    accountAddedBytes(numBytes)
+  }

Review Comment:
   `buf.retain()` is performed before `addComponent`, but on failures that 
happen *before* the component is inserted/owned (`componentAdded == false`), 
the extra retained reference is never released. This can leak a reference-count 
on `buf` in the pre-insertion failure path. Fix by releasing the retained 
reference when `componentAdded` is false (e.g., in the catch block or via a 
finally-style cleanup), while still not releasing it when the composite 
actually took ownership.



##########
worker/src/test/scala/org/apache/celeborn/service/deploy/worker/storage/TierWriterSuite.scala:
##########
@@ -37,6 +37,58 @@ import org.apache.celeborn.service.deploy.worker.WorkerSource
 import org.apache.celeborn.service.deploy.worker.memory.MemoryManager
 
 class TierWriterSuite extends AnyFunSuite with BeforeAndAfterEach {
+  private class LargeReadableByteBuf(buffer: ByteBuf, virtualSize: Int)
+    extends DuplicatedByteBuf(buffer) {
+    override def capacity(): Int = virtualSize
+
+    override def readableBytes(): Int = virtualSize
+  }
+
+  private class OomByteBufAllocator(failure: OutOfMemoryError)
+    extends AbstractByteBufAllocator(false) {
+    override protected def newHeapBuffer(initialCapacity: Int, maxCapacity: 
Int): ByteBuf =
+      throw failure
+
+    override protected def newDirectBuffer(initialCapacity: Int, maxCapacity: 
Int): ByteBuf =
+      throw failure
+
+    override def isDirectBufferPooled(): Boolean = false
+  }
+
+  private def capacityOverflowFlushBuffer(): CompositeByteBuf = {
+    val flushBuffer = new CompositeByteBuf(
+      UnpooledByteBufAllocator.DEFAULT,
+      false,
+      Int.MaxValue)
+    val component = UnpooledByteBufAllocator.DEFAULT.buffer(1, 
Int.MaxValue).writeByte(0)
+    flushBuffer.addComponent(
+      true,
+      new LargeReadableByteBuf(component, Int.MaxValue - 512))
+    flushBuffer
+  }
+
+  private def consolidationOomFlushBuffer(failure: OutOfMemoryError): 
CompositeByteBuf = {
+    val flushBuffer = new CompositeByteBuf(new OomByteBufAllocator(failure), 
false, 1)
+    flushBuffer.addComponent(true, UnpooledByteBufAllocator.DEFAULT.buffer(0, 
0))
+    flushBuffer
+  }
+
+  private def restoreCounters(memoryCounterBefore: Long, diskCounterBefore: 
Long): Unit = {
+    val memoryManager = MemoryManager.instance()
+    val memoryCounterDelta = memoryManager.getMemoryFileStorageCounter - 
memoryCounterBefore
+    if (memoryCounterDelta > 0) {
+      memoryManager.releaseMemoryFileStorage(memoryCounterDelta.toInt)
+    } else if (memoryCounterDelta < 0) {
+      memoryManager.incrementMemoryFileStorage((-memoryCounterDelta).toInt)
+    }
+    val diskCounterDelta = memoryManager.getDiskBufferCounter.get() - 
diskCounterBefore
+    if (diskCounterDelta > 0) {
+      memoryManager.releaseDiskBuffer(diskCounterDelta.toInt)
+    } else if (diskCounterDelta < 0) {
+      memoryManager.incrementDiskBuffer((-diskCounterDelta).toInt)

Review Comment:
   `memoryCounterDelta.toInt` / `diskCounterDelta.toInt` will silently overflow 
if the delta exceeds `Int.MaxValue`, which can make cleanup incorrect and hide 
test pollution. Consider guarding with a range check (fail fast in tests) or 
releasing/incrementing in bounded chunks to keep the cleanup logic correct even 
if a future test causes a large delta.



##########
worker/src/main/scala/org/apache/celeborn/service/deploy/worker/storage/TierWriter.scala:
##########
@@ -88,6 +88,32 @@ abstract class TierWriterBase(
 
   protected def writeInternal(buf: ByteBuf): Unit
 
+  protected def accountAddedBytes(numBytes: Int): Unit
+
+  protected def appendToFlushBuffer(buf: ByteBuf): Unit = {
+    val numBytes = buf.readableBytes()
+    val writerIndexBefore = flushBuffer.writerIndex()
+    val numComponentsBefore = flushBuffer.numComponents()
+    buf.retain()
+    try {
+      flushBuffer.addComponent(true, buf)
+    } catch {
+      case t: Throwable =>
+        // addComponent can fail after taking ownership while consolidating 
components.
+        val componentAdded =
+          flushBuffer.writerIndex() != writerIndexBefore ||
+            flushBuffer.numComponents() != numComponentsBefore
+        if (componentAdded) {
+          accountAddedBytes(numBytes)
+        }
+        logError(
+          s"Failed to add $numBytes bytes to flush buffer for shuffle 
$shuffleKey file $filename.",
+          t)
+        throw t
+    }

Review Comment:
   Catching `Throwable` here will also run this logic for fatal errors (e.g., 
`InterruptedException`, `VirtualMachineError` subclasses aside from the 
intended OOM/consolidation cases). It’s safer to catch 
`scala.util.control.NonFatal` (and optionally handle `OutOfMemoryError` 
explicitly) to avoid doing extra work (accounting + logging) during fatal 
conditions.



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

Reply via email to