github-actions[bot] commented on code in PR #67114:
URL: https://github.com/apache/doris/pull/67114#discussion_r3852019683


##########
be/src/exec/sink/writer/paimon/jni_paimon_write_backend.cpp:
##########
@@ -46,31 +46,86 @@ namespace doris {
 namespace {
 constexpr std::string_view PAIMON_JNI_WRITER_IO_TMP_DIR = 
"paimon_jni_writer_io_tmp";
 
-std::atomic<bool>& paimon_jni_close_failed() {
-    static std::atomic<bool> failed {false};
-    return failed;
-}
+struct PaimonJniCloseState {
+    // The global writer reference keeps both the Java object and its defining 
class alive. The
+    // cached method ID therefore remains valid until release_writer_ref() 
deletes this reference.
+    jobject writer_obj = nullptr;
+    jmethodID close_id = nullptr;
+    std::unique_ptr<PaimonJniMemoryManager> memory_manager;
+
+    Status retry_close(JNIEnv* env) const {
+        if (writer_obj == nullptr) {
+            return Status::OK();
+        }
+        if (close_id == nullptr) {
+            return Status::InternalError("PaimonJniWriter.close method is 
unavailable");
+        }
+        env->CallVoidMethod(writer_obj, close_id);
+        return Jni::Env::GetJniExceptionMsg(env, true,

Review Comment:
   [P2] Bound JNI locals across repeated close failures.
   
   This call is reached on every later writer open while the quarantined close 
keeps failing. `GetJniExceptionMsg` creates `msg` and, with `log_stack=true`, 
`stack` local jstrings but deletes only the original throwable. These opens run 
on attached long-lived native workers with a TLS `JNIEnv`, so there is no 
Java-to-native return or local frame that reclaims those references; they 
accumulate on every retry. The old atomic fence avoided all later JNI calls. 
Please bracket the retry with `PushLocalFrame`/`PopLocalFrame` or release both 
temporaries in the helper.



##########
fe/be-java-extensions/paimon-connector/src/main/java/org/apache/doris/paimon/PaimonJniWriter.java:
##########
@@ -265,8 +264,12 @@ public byte[][] prepareCommit() throws Exception {
     }
 
     /**
-     * Abort: discard all written data files and close the SDK writer.
+     * Abort an already prepared commit, if present, and close the SDK writer.
      * Called from C++ when write or prepareCommit fails.
+     *
+     * <p>Do not call prepareCommit from this error path: preparing may flush 
and compact data,
+     * which needs more heap precisely when the caller is recovering from an 
OOM. Files without a
+     * prepared commit message remain uncommitted and are handled by Paimon's 
orphan cleanup.

Review Comment:
   [P1] Preserve cleanup for non-OOM aborts.
   
   C++ calls the same `abort()` for every write or prepare failure, so this 
no-prepare rule is not limited to OOM. Paimon append-only and postpone writers 
can already have completed files in their pending `newFiles` lists after an 
intermediate flush; `close()` does not delete them. Previously `prepareCommit` 
exposed those files to `InnerTableCommit.abort` for deletion. Now the 
empty-message path loses their metadata, and Paimon orphan removal is a 
separately submitted maintenance job that Doris never runs. Avoid prepare only 
for the OOM case or add a no-flush path that explicitly deletes already 
materialized files.



##########
fe/be-java-extensions/paimon-connector/src/main/java/org/apache/doris/paimon/PaimonJniWriter.java:
##########
@@ -526,16 +529,24 @@ private void trackFullCompactionBucket(SinkRecord 
sinkRecord) {
     // ────────────────────────────────────────────────────────────
 
     private void closeResources() throws Exception {
+        Throwable failure = null;
         try {
             closeWriter();
-        } finally {
-            writeSchema = null;
-            arrowAdapter = null;
-            if (allocator != null) {
-                allocator.close();
+        } catch (Throwable closeFailure) {
+            failure = closeFailure;
+        }
+
+        writeSchema = null;
+        arrowAdapter = null;
+        if (allocator != null) {
+            Throwable allocatorFailure = closeResource(allocator);
+            if (allocatorFailure == null) {

Review Comment:
   [P1] Do not treat a second Arrow allocator close as successful recovery.
   
   In pinned Arrow 19, `BaseAllocator.close` sets `isClosed` before checking 
outstanding buffers, so after the first close throws the next call returns 
immediately. A partial `Data.importVectorSchemaRoot` can create and partly load 
a root, then throw before the initializer returns; this try-with-resources 
never acquires `root` and cannot close its foreign buffers. This branch retains 
the allocator on the real failure, then nulls it after the no-op retry, 
allowing C++ to remove the admission fence while moved C Data resources remain. 
Handle partial imports or the allocator terminal state explicitly, and test 
with a real `RootAllocator`.



##########
fe/be-java-extensions/paimon-connector/src/main/java/org/apache/doris/paimon/PaimonJniWriter.java:
##########
@@ -572,35 +583,76 @@ private void submitFullCompaction() throws Exception {
     }
 
     private void closeWriter() throws Exception {
-        if (sdkCloseFailed) {
-            throw new IllegalStateException(
-                    "A previous Paimon SDK close failed; native memory cannot 
be released safely");
+        Throwable failure = null;
+
+        Throwable writerFailure = closeResource(writer);
+        if (writerFailure == null) {
+            writer = null;
+        } else {
+            failure = writerFailure;
+        }
+
+        Throwable indexAssignerFailure = closeResource(globalIndexAssigner);
+        if (indexAssignerFailure == null) {
+            globalIndexAssigner = null;
+        } else {
+            failure = mergeFailure(failure, indexAssignerFailure);
         }
-        Exception failure = closeResource(writer, null);
-        failure = closeResource(globalIndexAssigner, failure);
-        failure = closeResource(ioManager, failure);
-        clearWriterState();
-        if (failure != null) {
-            sdkCloseFailed = true;
-            throw failure;
+
+        Throwable ioManagerFailure = closeResource(ioManager);

Review Comment:
   [P1] Keep the IOManager until the retained writer has closed.
   
   In Paimon 1.4.2, `AbstractFileStoreWrite.close` exits on the first 
bucket-writer exception and does not clear the remaining writers. This code 
retains that writer graph, but still calls `IOManager.close`, whose 
implementation deletes the shared spill directories. A later quarantine retry 
therefore re-enters live bucket writers after their spill backing was removed, 
turning a transient OOM/close failure into a permanent one. Only close and null 
the IOManager after `writer.close` succeeds, and cover a multi-bucket partial 
close.



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


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to