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

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


The following commit(s) were added to refs/heads/master by this push:
     new fa194d32bb4 [Pipe] Improve pipe control-flow lock responsiveness 
(#18076)
fa194d32bb4 is described below

commit fa194d32bb4f7c44d70a7ded3215713415311403
Author: Caideyipi <[email protected]>
AuthorDate: Wed Jul 1 18:34:53 2026 +0800

    [Pipe] Improve pipe control-flow lock responsiveness (#18076)
    
    * Improve pipe control flow lock responsiveness
    
    * Fix pipe meta helper test initialization
---
 .../impl/pipe/AbstractOperatePipeProcedureV2.java  |  24 +-
 .../pipe/AbstractOperatePipeProcedureV2Test.java   | 168 ++++++++++++++
 .../impl/DataNodeInternalRPCServiceImpl.java       |  53 +----
 .../thrift/impl/PushMultiPipeMetaHelper.java       |  86 +++++++
 ...nternalRPCServiceImplPushMultiPipeMetaTest.java | 246 +++++++++++++++++++++
 5 files changed, 529 insertions(+), 48 deletions(-)

diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/pipe/AbstractOperatePipeProcedureV2.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/pipe/AbstractOperatePipeProcedureV2.java
index d2bcd4e9539..26e84e3ebe4 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/pipe/AbstractOperatePipeProcedureV2.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/pipe/AbstractOperatePipeProcedureV2.java
@@ -53,7 +53,6 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -100,6 +99,10 @@ public abstract class AbstractOperatePipeProcedureV2
   // putting it here is just for convenience
   protected AtomicReference<PipeTaskInfo> pipeTaskInfo;
 
+  // Only used to release global locks before retrying the same state. Do not 
serialize it because a
+  // recovered procedure is already re-scheduled by the procedure framework.
+  private transient boolean shouldYieldAfterExecution;
+
   private static final String SKIP_PIPE_PROCEDURE_MESSAGE =
       "Try to start a RUNNING pipe or stop a STOPPED pipe, do nothing.";
 
@@ -177,15 +180,17 @@ public abstract class AbstractOperatePipeProcedureV2
     } else {
       LOGGER.debug(
           
ProcedureMessages.PROCEDUREID_RELEASE_LOCK_PIPE_LOCK_WILL_BE_RELEASED, 
getProcId());
-      if (this instanceof PipeMetaSyncProcedure) {
+      if (isSuccess() && this instanceof PipeMetaSyncProcedure) {
         configNodeProcedureEnv
             .getConfigManager()
             .getPipeManager()
             .getPipeTaskCoordinator()
             .updateLastSyncedVersion();
       }
-      PipeProcedureMetrics.getInstance()
-          .updateTimer(this.getOperation().getName(), this.elapsedTime());
+      if (isFinished()) {
+        PipeProcedureMetrics.getInstance()
+            .updateTimer(this.getOperation().getName(), this.elapsedTime());
+      }
       releasePipeTaskCoordinatorLock(configNodeProcedureEnv);
     }
   }
@@ -211,7 +216,7 @@ public abstract class AbstractOperatePipeProcedureV2
   public abstract void executeFromCalculateInfoForTask(ConfigNodeProcedureEnv 
env);
 
   /**
-   * Execute at state {@link 
OperatePipeTaskState#WRITE_CONFIG_NODE_CONSENSUS}.‘
+   * Execute at state {@link OperatePipeTaskState#WRITE_CONFIG_NODE_CONSENSUS}.
    *
    * @throws PipeException if configNode consensus write failed
    */
@@ -230,6 +235,7 @@ public abstract class AbstractOperatePipeProcedureV2
   @Override
   protected Flow executeFromState(ConfigNodeProcedureEnv env, 
OperatePipeTaskState state)
       throws InterruptedException {
+    shouldYieldAfterExecution = false;
     if (pipeTaskInfo == null) {
       LOGGER.warn(
           
ProcedureMessages.PROCEDUREID_PIPE_LOCK_IS_NOT_ACQUIRED_EXECUTEFROMSTATE_S_EXECUTION_WILL,
@@ -278,8 +284,7 @@ public abstract class AbstractOperatePipeProcedureV2
             RETRY_THRESHOLD,
             e);
         setNextState(getCurrentState());
-        // Wait 3s for next retry
-        TimeUnit.MILLISECONDS.sleep(3000L);
+        shouldYieldAfterExecution = true;
       } else {
         LOGGER.warn(
             
ProcedureMessages.PROCEDUREID_ALL_RETRIES_FAILED_WHEN_TRYING_TO_AT_STATE_WILL,
@@ -301,6 +306,11 @@ public abstract class AbstractOperatePipeProcedureV2
     return Flow.HAS_MORE_STATE;
   }
 
+  @Override
+  protected boolean isYieldAfterExecution(final ConfigNodeProcedureEnv env) {
+    return shouldYieldAfterExecution;
+  }
+
   @Override
   protected boolean isRollbackSupported(OperatePipeTaskState state) {
     return true;
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/pipe/AbstractOperatePipeProcedureV2Test.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/pipe/AbstractOperatePipeProcedureV2Test.java
new file mode 100644
index 00000000000..be6b6374e85
--- /dev/null
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/pipe/AbstractOperatePipeProcedureV2Test.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.confignode.procedure.impl.pipe;
+
+import org.apache.iotdb.confignode.persistence.pipe.PipeTaskInfo;
+import org.apache.iotdb.confignode.procedure.Procedure;
+import org.apache.iotdb.confignode.procedure.impl.StateMachineProcedure;
+import 
org.apache.iotdb.confignode.procedure.state.pipe.task.OperatePipeTaskState;
+import org.apache.iotdb.pipe.api.exception.PipeException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class AbstractOperatePipeProcedureV2Test {
+
+  @Test
+  public void testSuccessfulStateDoesNotYield() throws Exception {
+    final TestOperatePipeProcedure procedure = new TestOperatePipeProcedure();
+
+    Assert.assertEquals(
+        StateMachineProcedure.Flow.HAS_MORE_STATE,
+        procedure.executeFromState(null, OperatePipeTaskState.VALIDATE_TASK));
+
+    Assert.assertFalse(procedure.isYieldAfterExecution(null));
+    Assert.assertEquals(1, procedure.validateExecutionCount);
+  }
+
+  @Test
+  public void testRetryStateYieldsAndResetsAfterNextExecution() throws 
Exception {
+    final TestOperatePipeProcedure procedure = new TestOperatePipeProcedure();
+    procedure.failValidation = true;
+
+    Assert.assertEquals(
+        StateMachineProcedure.Flow.HAS_MORE_STATE,
+        procedure.executeFromState(null, OperatePipeTaskState.VALIDATE_TASK));
+
+    Assert.assertTrue(procedure.isYieldAfterExecution(null));
+    Assert.assertEquals(1, procedure.validateExecutionCount);
+
+    procedure.failValidation = false;
+    Assert.assertEquals(
+        StateMachineProcedure.Flow.HAS_MORE_STATE,
+        procedure.executeFromState(null, OperatePipeTaskState.VALIDATE_TASK));
+
+    Assert.assertFalse(procedure.isYieldAfterExecution(null));
+    Assert.assertEquals(2, procedure.validateExecutionCount);
+  }
+
+  @Test
+  public void testRetryStateYieldsOnlyBeforeRetryThreshold() throws Exception {
+    final TestOperatePipeProcedure procedure = new TestOperatePipeProcedure();
+
+    final Procedure<?>[] validateSubProcedures = procedure.runOnce();
+    Assert.assertEquals(1, validateSubProcedures.length);
+    Assert.assertSame(procedure, validateSubProcedures[0]);
+    Assert.assertFalse(procedure.isYieldAfterExecution(null));
+
+    procedure.failCalculation = true;
+    final Procedure<?>[] calculateSubProcedures = procedure.runOnce();
+    Assert.assertEquals(1, calculateSubProcedures.length);
+    Assert.assertSame(procedure, calculateSubProcedures[0]);
+    Assert.assertTrue(procedure.isYieldAfterExecution(null));
+    Assert.assertEquals(1, procedure.calculateExecutionCount);
+
+    Assert.assertNull(procedure.runOnce());
+    Assert.assertTrue(procedure.hasException());
+    Assert.assertFalse(procedure.isYieldAfterExecution(null));
+    Assert.assertEquals(2, procedure.calculateExecutionCount);
+  }
+
+  private static class TestOperatePipeProcedure extends 
AbstractOperatePipeProcedureV2 {
+
+    private int validateExecutionCount;
+    private int calculateExecutionCount;
+    private boolean failValidation;
+    private boolean failCalculation;
+
+    private TestOperatePipeProcedure() {
+      pipeTaskInfo = new AtomicReference<>(new PipeTaskInfo());
+    }
+
+    private Procedure<?>[] runOnce() throws InterruptedException {
+      return execute(null);
+    }
+
+    @Override
+    protected PipeTaskOperation getOperation() {
+      return PipeTaskOperation.START_PIPE;
+    }
+
+    @Override
+    public boolean executeFromValidateTask(
+        final org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv 
env)
+        throws PipeException {
+      validateExecutionCount++;
+      if (failValidation) {
+        throw new PipeException("retry");
+      }
+      return true;
+    }
+
+    @Override
+    public void executeFromCalculateInfoForTask(
+        final org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv 
env) {
+      calculateExecutionCount++;
+      if (failCalculation) {
+        throw new RuntimeException("retry");
+      }
+    }
+
+    @Override
+    public void executeFromWriteConfigNodeConsensus(
+        final org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv 
env) {
+      // Do nothing
+    }
+
+    @Override
+    public void executeFromOperateOnDataNodes(
+        final org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv 
env) {
+      // Do nothing
+    }
+
+    @Override
+    public void rollbackFromValidateTask(
+        final org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv 
env) {
+      // Do nothing
+    }
+
+    @Override
+    public void rollbackFromCalculateInfoForTask(
+        final org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv 
env) {
+      // Do nothing
+    }
+
+    @Override
+    public void rollbackFromWriteConfigNodeConsensus(
+        final org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv 
env) {
+      // Do nothing
+    }
+
+    @Override
+    public void rollbackFromOperateOnDataNodes(
+        final org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv 
env)
+        throws IOException {
+      // Do nothing
+    }
+  }
+}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
index 08fe54cf10d..ec3bbff0bb7 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
@@ -1392,49 +1392,20 @@ public class DataNodeInternalRPCServiceImpl implements 
IDataNodeRPCService.Iface
 
   @Override
   public TPushPipeMetaResp pushMultiPipeMeta(TPushMultiPipeMetaReq req) {
-    boolean hasException = false;
-    // If there is any exception, we use the size of exceptionMessages to 
record the fail index
-    List<TPushPipeMetaRespExceptionMessage> exceptionMessages = new 
ArrayList<>();
-    try {
-      if (req.isSetPipeNamesToDrop()) {
-        for (String pipeNameToDrop : req.getPipeNamesToDrop()) {
-          TPushPipeMetaRespExceptionMessage message =
-              PipeDataNodeAgent.task().handleDropPipe(pipeNameToDrop);
-          exceptionMessages.add(message);
-          if (message != null) {
-            // If there is any exception, skip the remaining pipes
-            hasException = true;
-            break;
-          }
-        }
-      } else if (req.isSetPipeMetas()) {
-        for (ByteBuffer byteBuffer : req.getPipeMetas()) {
-          final PipeMeta pipeMeta = PipeMeta.deserialize4TaskAgent(byteBuffer);
-          TPushPipeMetaRespExceptionMessage message =
-              PipeDataNodeAgent.task().handleSinglePipeMetaChanges(pipeMeta);
-          exceptionMessages.add(message);
-          if (message != null) {
-            // If there is any exception, skip the remaining pipes
-            hasException = true;
-            break;
+    return PushMultiPipeMetaHelper.pushMultiPipeMeta(
+        req,
+        new PushMultiPipeMetaHelper.Handler() {
+          @Override
+          public TPushPipeMetaRespExceptionMessage handleDropPipe(final String 
pipeName) {
+            return PipeDataNodeAgent.task().handleDropPipe(pipeName);
           }
-        }
-      } else {
-        throw new 
Exception(DataNodeMiscMessages.INVALID_PUSH_MULTI_PIPE_META_REQ);
-      }
 
-      return hasException
-          ? new TPushPipeMetaResp()
-              .setStatus(new 
TSStatus(TSStatusCode.PIPE_PUSH_META_ERROR.getStatusCode()))
-              .setExceptionMessages(exceptionMessages)
-          : new TPushPipeMetaResp()
-              .setStatus(new 
TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode()));
-    } catch (Exception e) {
-      LOGGER.warn(DataNodeMiscMessages.ERROR_PUSHING_MULTI_PIPE_META, e);
-      return new TPushPipeMetaResp()
-          .setStatus(new 
TSStatus(TSStatusCode.PIPE_PUSH_META_ERROR.getStatusCode()))
-          .setExceptionMessages(exceptionMessages);
-    }
+          @Override
+          public TPushPipeMetaRespExceptionMessage handleSinglePipeMeta(final 
ByteBuffer pipeMeta) {
+            return PipeDataNodeAgent.task()
+                
.handleSinglePipeMetaChanges(PipeMeta.deserialize4TaskAgent(pipeMeta));
+          }
+        });
   }
 
   @Override
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/PushMultiPipeMetaHelper.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/PushMultiPipeMetaHelper.java
new file mode 100644
index 00000000000..b96960f3e01
--- /dev/null
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/PushMultiPipeMetaHelper.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.protocol.thrift.impl;
+
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.db.i18n.DataNodeMiscMessages;
+import org.apache.iotdb.mpp.rpc.thrift.TPushMultiPipeMetaReq;
+import org.apache.iotdb.mpp.rpc.thrift.TPushPipeMetaResp;
+import org.apache.iotdb.mpp.rpc.thrift.TPushPipeMetaRespExceptionMessage;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+final class PushMultiPipeMetaHelper {
+
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(PushMultiPipeMetaHelper.class);
+
+  private PushMultiPipeMetaHelper() {
+    // Utility class
+  }
+
+  interface Handler {
+
+    TPushPipeMetaRespExceptionMessage handleDropPipe(String pipeName) throws 
Exception;
+
+    TPushPipeMetaRespExceptionMessage handleSinglePipeMeta(ByteBuffer 
pipeMeta) throws Exception;
+  }
+
+  static TPushPipeMetaResp pushMultiPipeMeta(
+      final TPushMultiPipeMetaReq req, final Handler handler) {
+    final List<TPushPipeMetaRespExceptionMessage> exceptionMessages = new 
ArrayList<>();
+    try {
+      if (req.isSetPipeNamesToDrop()) {
+        for (final String pipeNameToDrop : req.getPipeNamesToDrop()) {
+          final TPushPipeMetaRespExceptionMessage message = 
handler.handleDropPipe(pipeNameToDrop);
+          if (message != null) {
+            exceptionMessages.add(message);
+          }
+        }
+      } else if (req.isSetPipeMetas()) {
+        for (final ByteBuffer pipeMeta : req.getPipeMetas()) {
+          final TPushPipeMetaRespExceptionMessage message = 
handler.handleSinglePipeMeta(pipeMeta);
+          if (message != null) {
+            exceptionMessages.add(message);
+          }
+        }
+      } else {
+        throw new 
Exception(DataNodeMiscMessages.INVALID_PUSH_MULTI_PIPE_META_REQ);
+      }
+
+      return exceptionMessages.isEmpty()
+          ? new TPushPipeMetaResp()
+              .setStatus(new 
TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode()))
+          : new TPushPipeMetaResp()
+              .setStatus(new 
TSStatus(TSStatusCode.PIPE_PUSH_META_ERROR.getStatusCode()))
+              .setExceptionMessages(exceptionMessages);
+    } catch (final Exception e) {
+      LOGGER.warn(DataNodeMiscMessages.ERROR_PUSHING_MULTI_PIPE_META, e);
+      return new TPushPipeMetaResp()
+          .setStatus(new 
TSStatus(TSStatusCode.PIPE_PUSH_META_ERROR.getStatusCode()))
+          .setExceptionMessages(exceptionMessages);
+    }
+  }
+}
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImplPushMultiPipeMetaTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImplPushMultiPipeMetaTest.java
new file mode 100644
index 00000000000..0ae284bce9d
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImplPushMultiPipeMetaTest.java
@@ -0,0 +1,246 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.protocol.thrift.impl;
+
+import org.apache.iotdb.mpp.rpc.thrift.TPushMultiPipeMetaReq;
+import org.apache.iotdb.mpp.rpc.thrift.TPushPipeMetaResp;
+import org.apache.iotdb.mpp.rpc.thrift.TPushPipeMetaRespExceptionMessage;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class DataNodeInternalRPCServiceImplPushMultiPipeMetaTest {
+
+  @Test
+  public void testPushMultiPipeMetaContinuesAfterSinglePipeFailure() {
+    final AtomicInteger pushedPipeCount = new AtomicInteger(0);
+    final TPushPipeMetaResp resp =
+        PushMultiPipeMetaHelper.pushMultiPipeMeta(
+            new TPushMultiPipeMetaReq()
+                .setPipeMetas(
+                    Arrays.asList(
+                        ByteBuffer.wrap(new byte[] {1}),
+                        ByteBuffer.wrap(new byte[] {2}),
+                        ByteBuffer.wrap(new byte[] {3}))),
+            new PushMultiPipeMetaHelper.Handler() {
+              @Override
+              public TPushPipeMetaRespExceptionMessage handleDropPipe(final 
String pipeName) {
+                Assert.fail("Unexpected drop pipe request");
+                return null;
+              }
+
+              @Override
+              public TPushPipeMetaRespExceptionMessage handleSinglePipeMeta(
+                  final ByteBuffer pipeMeta) {
+                final int index = pushedPipeCount.incrementAndGet();
+                return index == 2 ? newExceptionMessage("pipe-2") : null;
+              }
+            });
+
+    Assert.assertEquals(3, pushedPipeCount.get());
+    Assert.assertEquals(
+        TSStatusCode.PIPE_PUSH_META_ERROR.getStatusCode(), 
resp.getStatus().getCode());
+    Assert.assertEquals(1, resp.getExceptionMessagesSize());
+    Assert.assertEquals("pipe-2", 
resp.getExceptionMessages().get(0).getPipeName());
+  }
+
+  @Test
+  public void testDropMultiPipeContinuesAfterSinglePipeFailure() {
+    final List<String> droppedPipeNames = new ArrayList<>();
+    final TPushPipeMetaResp resp =
+        PushMultiPipeMetaHelper.pushMultiPipeMeta(
+            new 
TPushMultiPipeMetaReq().setPipeNamesToDrop(Arrays.asList("pipe-1", "pipe-2")),
+            new PushMultiPipeMetaHelper.Handler() {
+              @Override
+              public TPushPipeMetaRespExceptionMessage handleDropPipe(final 
String pipeName) {
+                droppedPipeNames.add(pipeName);
+                return "pipe-1".equals(pipeName) ? 
newExceptionMessage(pipeName) : null;
+              }
+
+              @Override
+              public TPushPipeMetaRespExceptionMessage handleSinglePipeMeta(
+                  final ByteBuffer pipeMeta) {
+                Assert.fail("Unexpected pipe meta request");
+                return null;
+              }
+            });
+
+    Assert.assertEquals(Arrays.asList("pipe-1", "pipe-2"), droppedPipeNames);
+    Assert.assertEquals(
+        TSStatusCode.PIPE_PUSH_META_ERROR.getStatusCode(), 
resp.getStatus().getCode());
+    Assert.assertEquals(1, resp.getExceptionMessagesSize());
+    Assert.assertEquals("pipe-1", 
resp.getExceptionMessages().get(0).getPipeName());
+  }
+
+  @Test
+  public void testPushMultiPipeMetaReturnsSuccessWithoutExceptionMessages() {
+    final AtomicInteger pushedPipeCount = new AtomicInteger(0);
+    final TPushPipeMetaResp resp =
+        PushMultiPipeMetaHelper.pushMultiPipeMeta(
+            new TPushMultiPipeMetaReq()
+                .setPipeMetas(
+                    Arrays.asList(
+                        ByteBuffer.wrap(new byte[] {1}), ByteBuffer.wrap(new 
byte[] {2}))),
+            new PushMultiPipeMetaHelper.Handler() {
+              @Override
+              public TPushPipeMetaRespExceptionMessage handleDropPipe(final 
String pipeName) {
+                Assert.fail("Unexpected drop pipe request");
+                return null;
+              }
+
+              @Override
+              public TPushPipeMetaRespExceptionMessage handleSinglePipeMeta(
+                  final ByteBuffer pipeMeta) {
+                pushedPipeCount.incrementAndGet();
+                return null;
+              }
+            });
+
+    Assert.assertEquals(2, pushedPipeCount.get());
+    Assert.assertEquals(TSStatusCode.SUCCESS_STATUS.getStatusCode(), 
resp.getStatus().getCode());
+    Assert.assertFalse(resp.isSetExceptionMessages());
+  }
+
+  @Test
+  public void 
testPushMultiPipeMetaKeepsCollectedExceptionMessagesWhenLaterPipeThrows() {
+    final AtomicInteger pushedPipeCount = new AtomicInteger(0);
+    final TPushPipeMetaResp resp =
+        PushMultiPipeMetaHelper.pushMultiPipeMeta(
+            new TPushMultiPipeMetaReq()
+                .setPipeMetas(
+                    Arrays.asList(
+                        ByteBuffer.wrap(new byte[] {1}), ByteBuffer.wrap(new 
byte[] {2}))),
+            new PushMultiPipeMetaHelper.Handler() {
+              @Override
+              public TPushPipeMetaRespExceptionMessage handleDropPipe(final 
String pipeName) {
+                Assert.fail("Unexpected drop pipe request");
+                return null;
+              }
+
+              @Override
+              public TPushPipeMetaRespExceptionMessage handleSinglePipeMeta(
+                  final ByteBuffer pipeMeta) {
+                final int index = pushedPipeCount.incrementAndGet();
+                if (index == 1) {
+                  return newExceptionMessage("pipe-1");
+                }
+                throw new RuntimeException("boom");
+              }
+            });
+
+    Assert.assertEquals(2, pushedPipeCount.get());
+    Assert.assertEquals(
+        TSStatusCode.PIPE_PUSH_META_ERROR.getStatusCode(), 
resp.getStatus().getCode());
+    Assert.assertEquals(1, resp.getExceptionMessagesSize());
+    Assert.assertEquals("pipe-1", 
resp.getExceptionMessages().get(0).getPipeName());
+  }
+
+  @Test
+  public void testDropMultiPipeMetaReturnsSuccessWithoutExceptionMessages() {
+    final List<String> droppedPipeNames = new ArrayList<>();
+    final TPushPipeMetaResp resp =
+        PushMultiPipeMetaHelper.pushMultiPipeMeta(
+            new 
TPushMultiPipeMetaReq().setPipeNamesToDrop(Arrays.asList("pipe-1", "pipe-2")),
+            new PushMultiPipeMetaHelper.Handler() {
+              @Override
+              public TPushPipeMetaRespExceptionMessage handleDropPipe(final 
String pipeName) {
+                droppedPipeNames.add(pipeName);
+                return null;
+              }
+
+              @Override
+              public TPushPipeMetaRespExceptionMessage handleSinglePipeMeta(
+                  final ByteBuffer pipeMeta) {
+                Assert.fail("Unexpected pipe meta request");
+                return null;
+              }
+            });
+
+    Assert.assertEquals(Arrays.asList("pipe-1", "pipe-2"), droppedPipeNames);
+    Assert.assertEquals(TSStatusCode.SUCCESS_STATUS.getStatusCode(), 
resp.getStatus().getCode());
+    Assert.assertFalse(resp.isSetExceptionMessages());
+  }
+
+  @Test
+  public void 
testInvalidPushMultiPipeMetaRequestReturnsErrorWithoutCallingHandler() {
+    final TPushPipeMetaResp resp =
+        PushMultiPipeMetaHelper.pushMultiPipeMeta(
+            new TPushMultiPipeMetaReq(),
+            new PushMultiPipeMetaHelper.Handler() {
+              @Override
+              public TPushPipeMetaRespExceptionMessage handleDropPipe(final 
String pipeName) {
+                Assert.fail("Unexpected drop pipe request");
+                return null;
+              }
+
+              @Override
+              public TPushPipeMetaRespExceptionMessage handleSinglePipeMeta(
+                  final ByteBuffer pipeMeta) {
+                Assert.fail("Unexpected pipe meta request");
+                return null;
+              }
+            });
+
+    Assert.assertEquals(
+        TSStatusCode.PIPE_PUSH_META_ERROR.getStatusCode(), 
resp.getStatus().getCode());
+    Assert.assertEquals(0, resp.getExceptionMessagesSize());
+  }
+
+  @Test
+  public void testPushMultiPipeMetaStopsOnUnexpectedException() {
+    final AtomicInteger pushedPipeCount = new AtomicInteger(0);
+    final TPushPipeMetaResp resp =
+        PushMultiPipeMetaHelper.pushMultiPipeMeta(
+            new TPushMultiPipeMetaReq()
+                .setPipeMetas(
+                    Arrays.asList(
+                        ByteBuffer.wrap(new byte[] {1}), ByteBuffer.wrap(new 
byte[] {2}))),
+            new PushMultiPipeMetaHelper.Handler() {
+              @Override
+              public TPushPipeMetaRespExceptionMessage handleDropPipe(final 
String pipeName) {
+                Assert.fail("Unexpected drop pipe request");
+                return null;
+              }
+
+              @Override
+              public TPushPipeMetaRespExceptionMessage handleSinglePipeMeta(
+                  final ByteBuffer pipeMeta) {
+                pushedPipeCount.incrementAndGet();
+                throw new RuntimeException("boom");
+              }
+            });
+
+    Assert.assertEquals(1, pushedPipeCount.get());
+    Assert.assertEquals(
+        TSStatusCode.PIPE_PUSH_META_ERROR.getStatusCode(), 
resp.getStatus().getCode());
+    Assert.assertEquals(0, resp.getExceptionMessagesSize());
+  }
+
+  private static TPushPipeMetaRespExceptionMessage newExceptionMessage(final 
String pipeName) {
+    return new TPushPipeMetaRespExceptionMessage(pipeName, "failed", 1L);
+  }
+}

Reply via email to