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

yuqi1129 pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.3 by this push:
     new 4794696325 [Cherry-pick to branch-1.3] [#11939] fix(core,auth): 
tolerate concurrent directory creation instead of failing spuriously (#11940) 
(#11945)
4794696325 is described below

commit 47946963250656c794bf516d7a7e992220366fc5
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Jul 9 11:30:37 2026 +0800

    [Cherry-pick to branch-1.3] [#11939] fix(core,auth): tolerate concurrent 
directory creation instead of failing spuriously (#11940) (#11945)
    
    **Cherry-pick Information:**
    - Original commit: 82fc98bddc70748d9166caff224ed52da3c61436
    - Target branch: `branch-1.3`
    - Status: ✅ **Conflicts resolved**
    
    **Conflict resolution note:**
    
    This backport intentionally keeps only the `JobManager`
    staging-directory fix
    (`core/.../job/JobManager.java` + `TestJobManager.java`).
    
    The keytab (`KerberosAuthUtils`) half of #11940 does **not** apply to
    `branch-1.3`:
    the shared `catalogs/hadoop-auth` module was introduced by #11765, which
    is not
    present on `branch-1.3`. On `branch-1.3` the keytab code still lives in
    the
    per-catalog `KerberosClient` classes and already ignores the `mkdir()`
    return
    value, so the concurrent-creation bug fixed upstream never existed
    there. The
    GitHub auto-cherry-pick had added `KerberosAuthUtils.java` as new files
    under a
    module that isn't in `settings.gradle.kts`; those orphan files were
    removed.
    
    Verified: `./gradlew :core:test --tests TestJobManager` — 17/17 passing,
    including the new concurrent staging-directory test.
    
    Co-authored-by: Qi Yu <[email protected]>
---
 .../java/org/apache/gravitino/job/JobManager.java  | 14 ++++++---
 .../org/apache/gravitino/job/TestJobManager.java   | 35 ++++++++++++++++++++++
 2 files changed, 45 insertions(+), 4 deletions(-)

diff --git a/core/src/main/java/org/apache/gravitino/job/JobManager.java 
b/core/src/main/java/org/apache/gravitino/job/JobManager.java
index 3e9ae66fdd..efa24d70ea 100644
--- a/core/src/main/java/org/apache/gravitino/job/JobManager.java
+++ b/core/src/main/java/org/apache/gravitino/job/JobManager.java
@@ -26,6 +26,7 @@ import com.google.common.base.Preconditions;
 import java.io.File;
 import java.io.IOException;
 import java.net.URI;
+import java.nio.file.Files;
 import java.time.Instant;
 import java.util.Arrays;
 import java.util.List;
@@ -126,9 +127,11 @@ public class JobManager implements JobOperationDispatcher {
             String.format("Staging directory %s is not accessible", 
stagingDirPath));
       }
     } else {
-      if (!stagingDir.mkdirs()) {
+      try {
+        Files.createDirectories(stagingDir.toPath());
+      } catch (IOException e) {
         throw new IllegalArgumentException(
-            String.format("Failed to create staging directory %s", 
stagingDirPath));
+            String.format("Failed to create staging directory %s", 
stagingDirPath), e);
       }
     }
 
@@ -428,9 +431,12 @@ public class JobManager implements JobOperationDispatcher {
         stagingDir.getAbsolutePath()
             + String.format(JOB_STAGING_DIR, metalake, jobTemplateName, jobId);
     File jobStagingDir = new File(jobStagingPath);
-    if (!jobStagingDir.mkdirs()) {
+    try {
+      Files.createDirectories(jobStagingDir.toPath());
+    } catch (IOException e) {
       throw new RuntimeException(
-          String.format("Failed to create staging directory %s for job %s", 
jobStagingDir, jobId));
+          String.format("Failed to create staging directory %s for job %s", 
jobStagingDir, jobId),
+          e);
     }
 
     // Create a JobTemplate by replacing the template parameters with the 
jobConf values, and
diff --git a/core/src/test/java/org/apache/gravitino/job/TestJobManager.java 
b/core/src/test/java/org/apache/gravitino/job/TestJobManager.java
index 32348e789f..5edbbc18ef 100644
--- a/core/src/test/java/org/apache/gravitino/job/TestJobManager.java
+++ b/core/src/test/java/org/apache/gravitino/job/TestJobManager.java
@@ -516,6 +516,41 @@ public class TestJobManager {
         () -> jobManager.runJob(metalake, "shell_job", 
Collections.emptyMap()));
   }
 
+  @Test
+  public void testRunJobSucceedsWhenStagingDirectoryAlreadyExists() throws 
Exception {
+    mockedMetalake
+        .when(() -> MetalakeManager.checkMetalake(metalakeIdent, entityStore))
+        .thenAnswer(a -> null);
+
+    JobTemplateEntity shellJobTemplate =
+        newShellJobTemplateEntity("shell_job", "A shell job template");
+    when(jobExecutor.submitJob(any())).thenReturn("job_execution_id_for_test");
+    doNothing().when(entityStore).put(any(JobEntity.class), anyBoolean());
+
+    // Use a fixed job ID so that both runs resolve to the same staging 
directory.
+    IdGenerator fixedIdGenerator = Mockito.mock(IdGenerator.class);
+    when(fixedIdGenerator.nextId()).thenReturn(12345L);
+    JobManager fixedIdJobManager =
+        Mockito.spy(new JobManager(config, entityStore, fixedIdGenerator, 
jobExecutor));
+    try {
+      // Stop the background schedulers to prevent interference with the test, 
like setUp does.
+      fixedIdJobManager.cleanUpExecutor.shutdownNow();
+      fixedIdJobManager.statusPullExecutor.shutdownNow();
+      when(fixedIdJobManager.getJobTemplate(metalake, shellJobTemplate.name()))
+          .thenReturn(shellJobTemplate);
+
+      JobEntity first = fixedIdJobManager.runJob(metalake, "shell_job", 
Collections.emptyMap());
+      Assertions.assertEquals(12345L, first.id());
+
+      // The staging directory for job 12345 exists now; running the job again 
must not fail on
+      // directory creation.
+      JobEntity second = fixedIdJobManager.runJob(metalake, "shell_job", 
Collections.emptyMap());
+      Assertions.assertEquals(12345L, second.id());
+    } finally {
+      fixedIdJobManager.close();
+    }
+  }
+
   @Test
   public void testCancelJob() throws IOException {
     mockedMetalake

Reply via email to