hudi-agent commented on code in PR #19692:
URL: https://github.com/apache/hudi/pull/19692#discussion_r3845826933


##########
hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/StreamWriteFunction.java:
##########
@@ -382,8 +368,103 @@ protected void bufferRecord(HoodieFlinkInternalRow 
record) throws IOException {
    * RowData data bucket can not be used after disposing.
    */
   private void disposeBucket(RowDataBucket rowDataBucket) {
-    rowDataBucket.dispose();
-    this.buckets.remove(rowDataBucket.getBucketId());
+    try {
+      rowDataBucket.dispose();
+    } finally {
+      this.buckets.remove(rowDataBucket.getBucketId());
+    }
+  }

Review Comment:
   🤖 nit: the new private methods use `bucketID` (all-caps ID) while the 
existing getter is `getBucketId()` — could you align to `bucketId` across 
`reclaimMemoryAfterFailedWrite`, `retryBufferRecord`, and 
`disposeFailedRetryBucket` to stay consistent with the rest of the class?
   
   <sub><i>⚠️ AI-generated; verify before applying. React 👍/👎 to flag 
quality.</i></sub>



##########
hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/sink/StreamWriteFunction.java:
##########
@@ -382,8 +368,103 @@ protected void bufferRecord(HoodieFlinkInternalRow 
record) throws IOException {
    * RowData data bucket can not be used after disposing.
    */
   private void disposeBucket(RowDataBucket rowDataBucket) {
-    rowDataBucket.dispose();
-    this.buckets.remove(rowDataBucket.getBucketId());
+    try {
+      rowDataBucket.dispose();
+    } finally {
+      this.buckets.remove(rowDataBucket.getBucketId());
+    }
+  }
+
+  private void reclaimMemoryAfterFailedWrite(String bucketID) {
+    // A creation failure leaves no bucket in the map, while a write failure 
leaves the
+    // diverged bucket in the map so that its committed records can be flushed 
and disposed.
+    RowDataBucket failedBucket = this.buckets.get(bucketID);
+    RowDataBucket bucketToFlush = this.buckets.values().stream()
+        .filter(bucket -> !bucketID.equals(bucket.getBucketId()) && 
!bucket.isEmpty())
+        .max(Comparator.comparingLong(RowDataBucket::getBufferSize))
+        .orElse(null);
+
+    if (failedBucket == null) {
+      if (bucketToFlush == null) {
+        throw new HoodieException(
+            "Not enough memory pages to create a RowData buffer and no 
non-empty bucket can be flushed");
+      }
+      flushAndDisposeBucket(bucketToFlush);
+      return;
+    }
+
+    ValidationUtils.checkState(
+        failedBucket.isDiverged(), "The failed RowData bucket has not 
diverged");
+
+    RuntimeException failure = null;
+    if (bucketToFlush != null) {
+      try {
+        flushAndDisposeBucket(bucketToFlush);
+      } catch (RuntimeException e) {
+        failure = e;
+      }
+    }
+
+    try {
+      flushAndDisposeBucket(failedBucket);
+    } catch (RuntimeException e) {
+      if (failure == null) {
+        failure = e;
+      } else {
+        failure.addSuppressed(e);
+      }
+    }
+
+    if (failure != null) {
+      throw failure;
+    }
+  }
+
+  private void retryBufferRecord(
+      String bucketID, HoodieFlinkInternalRow record) throws IOException {
+    final boolean success;
+    try {
+      success = doBufferRecord(bucketID, record);
+    } catch (IOException | RuntimeException e) {
+      disposeFailedRetryBucket(bucketID, e);
+      throw e;
+    }
+
+    if (!success) {
+      HoodieException exception = new HoodieException(
+          this.buckets.get(bucketID) == null
+              ? "Not enough memory pages to create a RowData buffer after 
flushing"
+              : "The write buffer is too small to hold a single record");
+      disposeFailedRetryBucket(bucketID, exception);
+      throw exception;
+    }
+  }
+
+  private void disposeFailedRetryBucket(String bucketID, Throwable failure) {
+    RowDataBucket bucket = this.buckets.get(bucketID);
+    if (bucket == null) {
+      return;
+    }
+    try {
+      disposeBucket(bucket);
+    } catch (RuntimeException cleanupFailure) {
+      failure.addSuppressed(cleanupFailure);
+    }
+  }
+
+  private void flushAndDisposeBucket(RowDataBucket bucket) {
+    long bufferSize = bucket.getBufferSize();
+    try {
+      if (!bucket.isEmpty()) {
+        flushBucket(bucket);
+      }

Review Comment:
   🤖 nit: the nested `try { countDown } finally { disposeBucket }` inside the 
outer `finally` block is a bit surprising — if `tracer.countDown` is 
non-throwing, a flat `finally { countDown; disposeBucket; }` would be clearer; 
if it can throw unchecked, a short comment here explaining why would help the 
next reader.
   
   <sub><i>⚠️ AI-generated; verify before applying. React 👍/👎 to flag 
quality.</i></sub>



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