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

abstractdog pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tez.git


The following commit(s) were added to refs/heads/master by this push:
     new b9d8c0f37 TEZ-4737: Session DAG submission fails with 
FileAlreadyExistsException when the session outlives the TezClient (#523) 
(Denys Kuzmenko reviewed by Laszlo Bodor)
b9d8c0f37 is described below

commit b9d8c0f37b3c3fdc17d2c007297b3b559bd468a4
Author: Denys Kuzmenko <[email protected]>
AuthorDate: Sat Jul 18 15:45:59 2026 +0300

    TEZ-4737: Session DAG submission fails with FileAlreadyExistsException when 
the session outlives the TezClient (#523) (Denys Kuzmenko reviewed by Laszlo 
Bodor)
---
 .../main/java/org/apache/tez/client/TezClient.java |  6 +--
 .../java/org/apache/tez/dag/api/TezConstants.java  |  1 +
 .../java/org/apache/tez/client/TestTezClient.java  | 62 ++++++++++++++++++----
 .../DAGClientAMProtocolBlockingPBServerImpl.java   | 11 ++++
 ...estDAGClientAMProtocolBlockingPBServerImpl.java |  4 ++
 5 files changed, 71 insertions(+), 13 deletions(-)

diff --git a/tez-api/src/main/java/org/apache/tez/client/TezClient.java 
b/tez-api/src/main/java/org/apache/tez/client/TezClient.java
index 74942ac06..474c181a3 100644
--- a/tez-api/src/main/java/org/apache/tez/client/TezClient.java
+++ b/tez-api/src/main/java/org/apache/tez/client/TezClient.java
@@ -688,11 +688,11 @@ public class TezClient {
     SubmitDAGRequestProto request = requestBuilder.build();
     if (request.getSerializedSize() > maxSubmitDAGRequestSizeThroughIPC) {
       Path dagPlanPath = new 
Path(TezCommonUtils.getTezSystemStagingPath(amConfig.getTezConfiguration(),
-          sessionAppId.toString()), TezConstants.TEZ_PB_PLAN_BINARY_NAME +
-          serializedSubmitDAGPlanRequestCounter.incrementAndGet());
+          sessionAppId.toString()), 
TezConstants.TEZ_PB_PLAN_BINARY_NAME_FORMAT.formatted(
+          serializedSubmitDAGPlanRequestCounter.incrementAndGet()));
 
       FileSystem fs = dagPlanPath.getFileSystem(stagingFs.getConf());
-      try (FSDataOutputStream fsDataOutputStream = fs.create(dagPlanPath, 
false)) {
+      try (FSDataOutputStream fsDataOutputStream = fs.create(dagPlanPath, 
true)) {
         LOG.info("Send dag plan using YARN local resources since it's too 
large"
             + ", dag plan size=" + request.getSerializedSize()
             + ", max dag plan size through IPC=" + 
maxSubmitDAGRequestSizeThroughIPC
diff --git a/tez-api/src/main/java/org/apache/tez/dag/api/TezConstants.java 
b/tez-api/src/main/java/org/apache/tez/dag/api/TezConstants.java
index def923c9b..2031a2000 100644
--- a/tez-api/src/main/java/org/apache/tez/dag/api/TezConstants.java
+++ b/tez-api/src/main/java/org/apache/tez/dag/api/TezConstants.java
@@ -48,6 +48,7 @@ public final class TezConstants {
   public static final String SERVICE_PLUGINS_DESCRIPTOR_JSON = 
"service_plugins_descriptor.json";
   public static final String TEZ_PB_BINARY_CONF_NAME = "tez-conf.pb";
   public static final String TEZ_PB_PLAN_BINARY_NAME = "tez-dag.pb";
+  public static final String TEZ_PB_PLAN_BINARY_NAME_FORMAT = "tez-dag-%d.pb";
   public static final String TEZ_PB_PLAN_TEXT_NAME = "tez-dag.pb.txt";
 
   /*
diff --git a/tez-api/src/test/java/org/apache/tez/client/TestTezClient.java 
b/tez-api/src/test/java/org/apache/tez/client/TestTezClient.java
index 19d066817..402c7a545 100644
--- a/tez-api/src/test/java/org/apache/tez/client/TestTezClient.java
+++ b/tez-api/src/test/java/org/apache/tez/client/TestTezClient.java
@@ -70,6 +70,7 @@ import 
org.apache.hadoop.yarn.api.records.YarnApplicationState;
 import org.apache.hadoop.yarn.client.api.YarnClient;
 import org.apache.hadoop.yarn.exceptions.ApplicationNotFoundException;
 import org.apache.hadoop.yarn.exceptions.YarnException;
+import org.apache.tez.common.TezCommonUtils;
 import org.apache.tez.common.counters.LimitExceededException;
 import org.apache.tez.common.counters.Limits;
 import org.apache.tez.common.counters.TezCounters;
@@ -269,20 +270,12 @@ public class TestTezClient {
     localResourceMap.put(lrName, 
LocalResource.newInstance(URL.newInstance("file", "localhost", 0, "/test"),
         LocalResourceType.FILE, LocalResourceVisibility.PUBLIC, 1, 1));
 
-    ProcessorDescriptor processorDescriptor = ProcessorDescriptor.create("P");
-    
processorDescriptor.setUserPayload(UserPayload.create(ByteBuffer.allocate(payloadSize)));
-    Vertex vertex = Vertex.create("Vertex", processorDescriptor, 1, 
Resource.newInstance(1, 1));
-    DAG dag = 
DAG.create("DAG-testTezClientSessionLargeDAGPlan").addVertex(vertex);
-
     client.start();
     client.addAppMasterLocalFiles(localResourceMap);
-    client.submitDAG(dag);
+    SubmitDAGRequestProto request =
+        submitDAGAndCaptureRequest(client, 
largeDAG("DAG-testTezClientSessionLargeDAGPlan", payloadSize));
     client.stop();
 
-    ArgumentCaptor<SubmitDAGRequestProto> captor = 
ArgumentCaptor.forClass(SubmitDAGRequestProto.class);
-    verify(client.sessionAmProxy).submitDAG(any(), captor.capture());
-    SubmitDAGRequestProto request = captor.getValue();
-
     if (shouldSerialize) {
       /* we need manually delete the serialized dagplan since staging path 
here won't be destroyed */
       Path dagPlanPath = new Path(request.getSerializedRequestPath());
@@ -300,6 +293,55 @@ public class TestTezClient {
     }
   }
 
+  @Test
+  @Timeout(value = 10000, unit = TimeUnit.MILLISECONDS)
+  public void testSessionLargeDAGPlanWithLeftoverPlanFile() throws Exception {
+    // The session outlives its TezClient instances: a plan file left behind 
by a previous
+    // client must not fail subsequent submissions.
+    TezConfiguration conf = new TezConfiguration();
+    conf.setInt(CommonConfigurationKeys.IPC_MAXIMUM_DATA_LENGTH, 1024 * 1024);
+    conf.set(TezConfiguration.TEZ_AM_STAGING_DIR, 
STAGING_DIR.getAbsolutePath());
+
+    Path baseStagingPath = TezCommonUtils.getTezBaseStagingPath(conf);
+    FileSystem localFs = FileSystem.getLocal(conf);
+    localFs.delete(baseStagingPath, true);
+
+    // client1 writes tez-dag-1.pb and is abandoned without stop()
+    TezClientForTest client1 = configureAndCreateTezClient(null, true, conf);
+    client1.start();
+    SubmitDAGRequestProto request1 = submitDAGAndCaptureRequest(client1, 
largeDAG("DAG-client1", 2 * 1024 * 1024));
+    assertTrue(request1.hasSerializedRequestPath());
+    // the mocked AM never consumes the plan file, leaving it behind in the 
staging dir
+    Path leftoverPlanPath = new Path(request1.getSerializedRequestPath());
+    assertTrue(localFs.exists(leftoverPlanPath));
+
+    // client2 reuses the same session and computes the same tez-dag-1.pb path
+    TezClientForTest client2 = configureAndCreateTezClient(null, true, conf);
+    client2.start();
+    SubmitDAGRequestProto request2 = submitDAGAndCaptureRequest(client2, 
largeDAG("DAG-client2", 2 * 1024 * 1024));
+    assertTrue(request2.hasSerializedRequestPath());
+    // path collision: client2 overwrote client1's leftover instead of failing
+    assertEquals(leftoverPlanPath, new 
Path(request2.getSerializedRequestPath()));
+    assertTrue(localFs.exists(leftoverPlanPath));
+    client2.stop();
+
+    localFs.delete(baseStagingPath, true);
+  }
+
+  private DAG largeDAG(String name, int payloadSize) {
+    ProcessorDescriptor processorDescriptor = ProcessorDescriptor.create("P");
+    
processorDescriptor.setUserPayload(UserPayload.create(ByteBuffer.allocate(payloadSize)));
+    Vertex vertex = Vertex.create("Vertex", processorDescriptor, 1, 
Resource.newInstance(1, 1));
+    return DAG.create(name).addVertex(vertex);
+  }
+
+  private SubmitDAGRequestProto submitDAGAndCaptureRequest(TezClientForTest 
client, DAG dag) throws Exception {
+    client.submitDAG(dag);
+    ArgumentCaptor<SubmitDAGRequestProto> captor = 
ArgumentCaptor.forClass(SubmitDAGRequestProto.class);
+    verify(client.sessionAmProxy).submitDAG(any(), captor.capture());
+    return captor.getValue();
+  }
+
   @Test
   @Timeout(value = 5000, unit = TimeUnit.MILLISECONDS)
   public void testGetClient() throws Exception {
diff --git 
a/tez-dag/src/main/java/org/apache/tez/dag/api/client/rpc/DAGClientAMProtocolBlockingPBServerImpl.java
 
b/tez-dag/src/main/java/org/apache/tez/dag/api/client/rpc/DAGClientAMProtocolBlockingPBServerImpl.java
index 1b7601cb6..c5da19477 100644
--- 
a/tez-dag/src/main/java/org/apache/tez/dag/api/client/rpc/DAGClientAMProtocolBlockingPBServerImpl.java
+++ 
b/tez-dag/src/main/java/org/apache/tez/dag/api/client/rpc/DAGClientAMProtocolBlockingPBServerImpl.java
@@ -58,8 +58,13 @@ import com.google.protobuf.CodedInputStream;
 import com.google.protobuf.RpcController;
 import com.google.protobuf.ServiceException;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 public class DAGClientAMProtocolBlockingPBServerImpl implements 
DAGClientAMProtocolBlockingPB {
 
+  private static final Logger LOG = 
LoggerFactory.getLogger(DAGClientAMProtocolBlockingPBServerImpl.class);
+
   DAGClientHandler real;
   final FileSystem stagingFs;
 
@@ -176,6 +181,12 @@ public class DAGClientAMProtocolBlockingPBServerImpl 
implements DAGClientAMProto
           request = SubmitDAGRequestProto.parseFrom(in);
         } catch (IOException e) {
           throw wrapException(e);
+        } finally {
+          try {
+            fs.delete(requestPath, false);
+          } catch (IOException e) {
+            LOG.warn("Failed to delete the serialized DAG plan file {}", 
requestPath, e);
+          }
         }
       }
       DAGPlan dagPlan = request.getDAGPlan();
diff --git 
a/tez-dag/src/test/java/org/apache/tez/dag/api/client/rpc/TestDAGClientAMProtocolBlockingPBServerImpl.java
 
b/tez-dag/src/test/java/org/apache/tez/dag/api/client/rpc/TestDAGClientAMProtocolBlockingPBServerImpl.java
index 360d5ac13..9878d321b 100644
--- 
a/tez-dag/src/test/java/org/apache/tez/dag/api/client/rpc/TestDAGClientAMProtocolBlockingPBServerImpl.java
+++ 
b/tez-dag/src/test/java/org/apache/tez/dag/api/client/rpc/TestDAGClientAMProtocolBlockingPBServerImpl.java
@@ -20,6 +20,7 @@ package org.apache.tez.dag.api.client.rpc;
 
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
@@ -118,6 +119,9 @@ public class TestDAGClientAMProtocolBlockingPBServerImpl {
     
requestBuilder.clear().setSerializedRequestPath(requestFile.getAbsolutePath());
     serverImpl.submitDAG(null, requestBuilder.build());
 
+    assertFalse(requestFile.exists(),
+        "The serialized DAG plan file should be deleted once the AM has 
consumed it");
+
     ArgumentCaptor<DAGPlan> dagPlanCaptor = 
ArgumentCaptor.forClass(DAGPlan.class);
     verify(dagClientHandler).submitDAG(dagPlanCaptor.capture(), 
localResourcesCaptor.capture());
     dagPlan = dagPlanCaptor.getValue();

Reply via email to