[AURON #2118] make native environment initialization retriable after failure (#2151)

# Which issue does this PR close?

Closes #2118

# Rationale for this change

avoid initialize in static block, make native environment initialization
retriable after failure

# What changes are included in this PR?

# Are there any user-facing changes?

# How was this patch tested?
diff --git a/auron-core/src/main/java/org/apache/auron/jni/AuronCallNativeWrapper.java b/auron-core/src/main/java/org/apache/auron/jni/AuronCallNativeWrapper.java
index 661d807..3fbcdc6 100644
--- a/auron-core/src/main/java/org/apache/auron/jni/AuronCallNativeWrapper.java
+++ b/auron-core/src/main/java/org/apache/auron/jni/AuronCallNativeWrapper.java
@@ -53,27 +53,7 @@
     private Schema arrowSchema;
     private long nativeRuntimePtr;
     private Consumer<VectorSchemaRoot> batchConsumer;
-
-    // initialize native environment
-    static {
-        LOG.info("Initializing native environment (batchSize="
-                + AuronAdaptor.getInstance().getAuronConfiguration().get(AuronConfiguration.BATCH_SIZE) + ", "
-                + "memoryFraction="
-                + AuronAdaptor.getInstance().getAuronConfiguration().get(AuronConfiguration.MEMORY_FRACTION) + ")");
-
-        // arrow configuration
-        System.setProperty("arrow.struct.conflict.policy", "CONFLICT_APPEND");
-
-        // preload JNI bridge classes
-        try {
-            Class.forName("org.apache.auron.jni.JniBridge");
-        } catch (ClassNotFoundException e) {
-            throw new RuntimeException("Cannot load JniBridge class", e);
-        }
-
-        AuronAdaptor.getInstance().loadAuronLib();
-        Runtime.getRuntime().addShutdownHook(new Thread(JniBridge::onExit));
-    }
+    private static volatile boolean initialized = false;
 
     public AuronCallNativeWrapper(
             BufferAllocator arrowAllocator,
@@ -90,6 +70,7 @@
         this.stageId = stageId;
         this.taskId = taskId;
 
+        init();
         LOG.warn("Start executing native plan");
         this.nativeRuntimePtr = JniBridge.callNative(
                 nativeMemory,
@@ -97,6 +78,41 @@
                 this);
     }
 
+    private static void init() {
+        if (!initialized) {
+            synchronized (AuronCallNativeWrapper.class) {
+                if (!initialized) {
+                    // Retry-safe: on failure `initialized` stays false so the next call retries.
+                    // Every step below must be idempotent, or must not run on the failure path.
+                    // In particular, loadAuronLib() must throw before System.load — a retried
+                    // System.load would load the native library twice.
+                    // initialize native environment
+                    LOG.info("Initializing native environment (batchSize="
+                            + AuronAdaptor.getInstance().getAuronConfiguration().get(AuronConfiguration.BATCH_SIZE)
+                            + ", "
+                            + "memoryFraction="
+                            + AuronAdaptor.getInstance().getAuronConfiguration().get(AuronConfiguration.MEMORY_FRACTION)
+                            + ")");
+
+                    // arrow configuration
+                    System.setProperty("arrow.struct.conflict.policy", "CONFLICT_APPEND");
+
+                    // preload JNI bridge classes
+                    try {
+                        Class.forName("org.apache.auron.jni.JniBridge");
+                    } catch (ClassNotFoundException e) {
+                        throw new RuntimeException("Cannot load JniBridge class", e);
+                    }
+
+                    AuronAdaptor.getInstance().loadAuronLib();
+                    Runtime.getRuntime().addShutdownHook(new Thread(JniBridge::onExit));
+
+                    initialized = true;
+                }
+            }
+        }
+    }
+
     /**
      * Loads and processes the next batch of data from the native plan.
      * <p>