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 4482e65468a Fix pipe drop stuck on blocking sink reconnect (#18145)
4482e65468a is described below

commit 4482e65468a0d3699bfed927c8afa293551ae5e2
Author: Caideyipi <[email protected]>
AuthorDate: Fri Jul 10 10:30:00 2026 +0800

    Fix pipe drop stuck on blocking sink reconnect (#18145)
    
    * Fix pipe drop stuck on blocking sink reconnect
    
    * Address pipe sink subtask review comments
---
 .../apache/iotdb/db/i18n/DataNodePipeMessages.java |  10 +
 .../apache/iotdb/db/i18n/DataNodePipeMessages.java |   8 +
 .../agent/task/subtask/sink/PipeSinkSubtask.java   | 198 ++++++++++++++--
 .../task/subtask/sink/PipeSinkSubtaskTest.java     | 254 +++++++++++++++++++++
 .../commons/pipe/agent/task/PipeTaskAgent.java     |  21 +-
 .../task/subtask/PipeAbstractSinkSubtask.java      | 103 ++++++---
 .../pipe/agent/task/subtask/PipeSubtask.java       |   2 +-
 7 files changed, 533 insertions(+), 63 deletions(-)

diff --git 
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
 
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
index a6d6abbefdb..e7c6c85ca5c 100644
--- 
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
+++ 
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
@@ -228,10 +228,20 @@ public final class DataNodePipeMessages {
       "PipeEventCollector: The event {} is already released, skipping it.";
   public static final String PIPE_CONNECTOR_SUBTASK_WAS_CLOSED_WITHIN_MS =
       "Pipe: connector subtask {} ({}) was closed within {} ms";
+  public static final String 
FAILED_TO_DISCARD_EVENTS_OF_PIPE_IN_CONNECTOR_SUBTASK =
+      "Failed to discard events of pipe {} in connector subtask {}.";
   public static final String PIPE_META_NOT_FOUND = "Pipe meta not found: ";
   public static final String PIPE_SINK_SUBTASKS_WITH_ATTRIBUTES_IS_BOUNDED =
       "Pipe sink subtasks with attributes {} is bounded with sinkExecutor {} 
and "
           + "callbackExecutor {}.";
+  public static final String PIPE_SINK_SUBTASK_CLOSE_OPERATION_STILL_RUNNING =
+      "is still running";
+  public static final String
+      
PIPE_SINK_SUBTASK_CLOSE_OPERATION_WILL_RUN_AFTER_CURRENT_CONNECTOR_OPERATION =
+          "will run after the current connector operation finishes";
+  public static final String PIPE_SINK_SUBTASK_CLOSE_TIMED_OUT =
+      "Timed out after {} ms when closing pipe connector subtask {}. Continue 
dropping it. "
+          + "The close operation {}.";
   public static final String 
PIPE_SINK_SUBTASK_DELAYED_TO_AVOID_FREQUENT_HANDSHAKES =
       "Pipe sink subtask {} is delayed for {} ms before polling events to 
avoid frequent "
           + "handshakes after client borrow failures.";
diff --git 
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
 
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
index ba2fd769273..54b68d18e0d 100644
--- 
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
+++ 
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
@@ -217,9 +217,17 @@ public final class DataNodePipeMessages {
       "PipeEventCollector:事件 {} 已被释放,跳过处理。";
   public static final String PIPE_CONNECTOR_SUBTASK_WAS_CLOSED_WITHIN_MS =
       "Pipe:connector 子任务 {} ({}) 在 {} ms 内已关闭";
+  public static final String 
FAILED_TO_DISCARD_EVENTS_OF_PIPE_IN_CONNECTOR_SUBTASK =
+      "Pipe {} 在 connector 子任务 {} 中的事件丢弃失败。";
   public static final String PIPE_META_NOT_FOUND = "未找到 pipe 元数据:";
   public static final String PIPE_SINK_SUBTASKS_WITH_ATTRIBUTES_IS_BOUNDED =
       "带属性 {} 的 Pipe sink 子任务绑定到 sinkExecutor {} 和 callbackExecutor {}。";
+  public static final String PIPE_SINK_SUBTASK_CLOSE_OPERATION_STILL_RUNNING = 
"仍在运行";
+  public static final String
+      
PIPE_SINK_SUBTASK_CLOSE_OPERATION_WILL_RUN_AFTER_CURRENT_CONNECTOR_OPERATION =
+          "将在当前 connector 操作完成后运行";
+  public static final String PIPE_SINK_SUBTASK_CLOSE_TIMED_OUT =
+      "关闭 pipe connector 子任务超时 {} ms:{}。继续丢弃该子任务。关闭操作{}。";
   public static final String 
PIPE_SINK_SUBTASK_DELAYED_TO_AVOID_FREQUENT_HANDSHAKES =
       "Pipe sink 子任务 {} 在拉取事件前延迟 {} ms,以避免客户端借用失败后频繁握手。";
   public static final String PIPE_SKIPPING_TEMPORARY_TSFILE_WHICH_SHOULDN_T =
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtask.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtask.java
index 94e20600909..ce0b2c2233d 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtask.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtask.java
@@ -19,6 +19,7 @@
 
 package org.apache.iotdb.db.pipe.agent.task.subtask.sink;
 
+import org.apache.iotdb.commons.conf.CommonDescriptor;
 import org.apache.iotdb.commons.exception.pipe.PipeRuntimeException;
 import 
org.apache.iotdb.commons.pipe.agent.task.connection.UnboundedBlockingPendingQueue;
 import org.apache.iotdb.commons.pipe.agent.task.progress.CommitterKey;
@@ -51,6 +52,11 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantLock;
 
 public class PipeSinkSubtask extends PipeAbstractSinkSubtask {
 
@@ -69,6 +75,8 @@ public class PipeSinkSubtask extends PipeAbstractSinkSubtask {
   // the random delay of the batch transmission. Therefore, here we inject 
cron events
   // when no event can be pulled.
   public static final PipeHeartbeatEvent CRON_HEARTBEAT_EVENT = new 
PipeHeartbeatEvent(-1, false);
+  private final ReentrantLock outputPipeSinkOperationLock = new 
ReentrantLock();
+  private final Queue<CommitterKey> pendingDiscardCommitterKeys = new 
ConcurrentLinkedQueue<>();
 
   public PipeSinkSubtask(
       final String taskID,
@@ -132,15 +140,19 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
       }
 
       if (event instanceof TabletInsertionEvent) {
-        outputPipeSink.transfer((TabletInsertionEvent) event);
-        PipeDataRegionSinkMetrics.getInstance().markTabletEvent(taskID);
+        if (executeOutputPipeSinkOperation(
+            () -> outputPipeSink.transfer((TabletInsertionEvent) event))) {
+          PipeDataRegionSinkMetrics.getInstance().markTabletEvent(taskID);
+        }
       } else if (event instanceof TsFileInsertionEvent) {
-        outputPipeSink.transfer((TsFileInsertionEvent) event);
-        PipeDataRegionSinkMetrics.getInstance().markTsFileEvent(taskID);
+        if (executeOutputPipeSinkOperation(
+            () -> outputPipeSink.transfer((TsFileInsertionEvent) event))) {
+          PipeDataRegionSinkMetrics.getInstance().markTsFileEvent(taskID);
+        }
       } else if (event instanceof PipeSchemaRegionWritePlanEvent) {
-        outputPipeSink.transfer(event);
-        if (((PipeSchemaRegionWritePlanEvent) event).getPlanNode().getType()
-            != PlanNodeType.DELETE_DATA) {
+        if (executeOutputPipeSinkOperation(() -> 
outputPipeSink.transfer(event))
+            && ((PipeSchemaRegionWritePlanEvent) event).getPlanNode().getType()
+                != PlanNodeType.DELETE_DATA) {
           // Only plan nodes in schema region will be marked, delete data node 
is currently not
           // taken into account
           PipeSchemaRegionSinkMetrics.getInstance().markSchemaEvent(taskID);
@@ -148,10 +160,12 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
       } else if (event instanceof PipeHeartbeatEvent) {
         transferHeartbeatEvent((PipeHeartbeatEvent) event);
       } else {
-        outputPipeSink.transfer(
-            event instanceof UserDefinedEnrichedEvent
-                ? ((UserDefinedEnrichedEvent) event).getUserDefinedEvent()
-                : event);
+        executeOutputPipeSinkOperation(
+            () ->
+                outputPipeSink.transfer(
+                    event instanceof UserDefinedEnrichedEvent
+                        ? ((UserDefinedEnrichedEvent) 
event).getUserDefinedEvent()
+                        : event));
       }
 
       decreaseReferenceCountAndReleaseLastEvent(event, true);
@@ -169,7 +183,15 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
       return 0;
     }
 
-    return ((PipeSinkWithSchedulingDelay) 
outputPipeSink).peekSchedulingDelayMs();
+    outputPipeSinkOperationLock.lock();
+    try {
+      discardPendingEventsOfPipeUnderLock();
+      return isClosed.get()
+          ? 0
+          : ((PipeSinkWithSchedulingDelay) 
outputPipeSink).peekSchedulingDelayMs();
+    } finally {
+      outputPipeSinkOperationLock.unlock();
+    }
   }
 
   @Override
@@ -178,8 +200,17 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
       return 0;
     }
 
-    final long remainingSchedulingDelayMs =
-        ((PipeSinkWithSchedulingDelay) 
outputPipeSink).consumeSchedulingDelayMs();
+    final long remainingSchedulingDelayMs;
+    outputPipeSinkOperationLock.lock();
+    try {
+      discardPendingEventsOfPipeUnderLock();
+      remainingSchedulingDelayMs =
+          isClosed.get()
+              ? 0
+              : ((PipeSinkWithSchedulingDelay) 
outputPipeSink).consumeSchedulingDelayMs();
+    } finally {
+      outputPipeSinkOperationLock.unlock();
+    }
     if (remainingSchedulingDelayMs <= 0) {
       return 0;
     }
@@ -201,8 +232,13 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
     }
 
     try {
-      outputPipeSink.heartbeat();
-      outputPipeSink.transfer(event);
+      if (!executeOutputPipeSinkOperation(
+          () -> {
+            outputPipeSink.heartbeat();
+            outputPipeSink.transfer(event);
+          })) {
+        return;
+      }
     } catch (final Exception e) {
       throw new PipeConnectionException(
           String.format(
@@ -218,6 +254,54 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
     PipeDataRegionSinkMetrics.getInstance().markPipeHeartbeatEvent(taskID);
   }
 
+  @Override
+  protected boolean handshakeOutputPipeSink() throws Exception {
+    return executeOutputPipeSinkOperation(() -> outputPipeSink.handshake());
+  }
+
+  private boolean executeOutputPipeSinkOperation(final OutputPipeSinkOperation 
operation)
+      throws Exception {
+    outputPipeSinkOperationLock.lock();
+    try {
+      discardPendingEventsOfPipeUnderLock();
+      if (isClosed.get()) {
+        return false;
+      }
+
+      operation.execute();
+      discardPendingEventsOfPipeUnderLock();
+      return true;
+    } finally {
+      outputPipeSinkOperationLock.unlock();
+    }
+  }
+
+  private void discardPendingEventsOfPipeUnderLock() {
+    if (!(outputPipeSink instanceof PipeConnectorWithEventDiscard)) {
+      pendingDiscardCommitterKeys.clear();
+      return;
+    }
+
+    CommitterKey committerKey;
+    while ((committerKey = pendingDiscardCommitterKeys.poll()) != null) {
+      try {
+        ((PipeConnectorWithEventDiscard) 
outputPipeSink).discardEventsOfPipe(committerKey);
+      } catch (final Exception e) {
+        LOGGER.warn(
+            
DataNodePipeMessages.FAILED_TO_DISCARD_EVENTS_OF_PIPE_IN_CONNECTOR_SUBTASK,
+            committerKey.getPipeName(),
+            getDisplayTaskID(),
+            e);
+      }
+    }
+  }
+
+  @FunctionalInterface
+  private interface OutputPipeSinkOperation {
+
+    void execute() throws Exception;
+  }
+
   @Override
   public void close() {
     if (!attributeSortedString.startsWith("schema_")) {
@@ -229,12 +313,13 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
     isClosed.set(true);
     try {
       final long startTime = System.currentTimeMillis();
-      outputPipeSink.close();
-      LOGGER.info(
-          DataNodePipeMessages.PIPE_CONNECTOR_SUBTASK_WAS_CLOSED_WITHIN_MS,
-          getDisplayTaskID(),
-          outputPipeSink,
-          System.currentTimeMillis() - startTime);
+      if (closeOutputPipeSink()) {
+        LOGGER.info(
+            DataNodePipeMessages.PIPE_CONNECTOR_SUBTASK_WAS_CLOSED_WITHIN_MS,
+            getDisplayTaskID(),
+            outputPipeSink,
+            System.currentTimeMillis() - startTime);
+      }
     } catch (final Exception e) {
       LOGGER.info(
           
DataNodePipeMessages.EXCEPTION_OCCURRED_WHEN_CLOSING_PIPE_CONNECTOR_SUBTASK,
@@ -249,6 +334,58 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
     }
   }
 
+  private boolean closeOutputPipeSink() throws Exception {
+    final AtomicReference<Exception> exception = new AtomicReference<>();
+    final AtomicBoolean closeStarted = new AtomicBoolean(false);
+    final Thread closeThread =
+        new Thread(
+            () -> {
+              outputPipeSinkOperationLock.lock();
+              try {
+                discardPendingEventsOfPipeUnderLock();
+                closeStarted.set(true);
+                outputPipeSink.close();
+              } catch (final Exception e) {
+                exception.set(e);
+              } finally {
+                outputPipeSinkOperationLock.unlock();
+              }
+            },
+            "PipeSinkSubtaskClose-" + getDisplayTaskID());
+    closeThread.setDaemon(true);
+    closeThread.start();
+
+    final long timeoutInMs =
+        Math.max(
+            1L, 
CommonDescriptor.getInstance().getConfig().getDnConnectionTimeoutInMS() * 2L / 
3);
+    try {
+      closeThread.join(timeoutInMs);
+    } catch (final InterruptedException e) {
+      closeThread.interrupt();
+      Thread.currentThread().interrupt();
+      throw e;
+    }
+    if (closeThread.isAlive()) {
+      if (closeStarted.get()) {
+        closeThread.interrupt();
+      }
+      LOGGER.warn(
+          DataNodePipeMessages.PIPE_SINK_SUBTASK_CLOSE_TIMED_OUT,
+          timeoutInMs,
+          getDisplayTaskID(),
+          closeStarted.get()
+              ? 
DataNodePipeMessages.PIPE_SINK_SUBTASK_CLOSE_OPERATION_STILL_RUNNING
+              : DataNodePipeMessages
+                  
.PIPE_SINK_SUBTASK_CLOSE_OPERATION_WILL_RUN_AFTER_CURRENT_CONNECTOR_OPERATION);
+      return false;
+    }
+
+    if (exception.get() != null) {
+      throw exception.get();
+    }
+    return true;
+  }
+
   /**
    * When a pipe is dropped, the connector maybe reused and will not be 
closed. So we just discard
    * its queued events in the output pipe connector.
@@ -298,8 +435,21 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
       decreaseHighPriorityTaskCount();
     }
 
-    if (outputPipeSink instanceof PipeConnectorWithEventDiscard) {
-      ((PipeConnectorWithEventDiscard) 
outputPipeSink).discardEventsOfPipe(committerKey);
+    discardOutputPipeSinkEventsOfPipe(committerKey);
+  }
+
+  private void discardOutputPipeSinkEventsOfPipe(final CommitterKey 
committerKey) {
+    if (!(outputPipeSink instanceof PipeConnectorWithEventDiscard)) {
+      return;
+    }
+
+    pendingDiscardCommitterKeys.offer(committerKey);
+    if (outputPipeSinkOperationLock.tryLock()) {
+      try {
+        discardPendingEventsOfPipeUnderLock();
+      } finally {
+        outputPipeSinkOperationLock.unlock();
+      }
     }
   }
 
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskTest.java
index e9d76a4fc7e..c60b9c2c251 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/sink/PipeSinkSubtaskTest.java
@@ -19,18 +19,29 @@
 
 package org.apache.iotdb.db.pipe.agent.task.subtask.sink;
 
+import org.apache.iotdb.commons.conf.CommonDescriptor;
 import 
org.apache.iotdb.commons.pipe.agent.task.connection.UnboundedBlockingPendingQueue;
 import org.apache.iotdb.commons.pipe.agent.task.progress.CommitterKey;
 import 
org.apache.iotdb.commons.pipe.sink.protocol.PipeConnectorWithEventDiscard;
 import org.apache.iotdb.pipe.api.PipeConnector;
+import 
org.apache.iotdb.pipe.api.customizer.configuration.PipeConnectorRuntimeConfiguration;
+import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameterValidator;
+import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters;
 import org.apache.iotdb.pipe.api.event.Event;
+import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent;
+import org.apache.iotdb.pipe.api.exception.PipeConnectionException;
 import org.apache.iotdb.pipe.api.exception.PipeException;
 
 import org.junit.Assert;
 import org.junit.Test;
 import org.mockito.Mockito;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -68,6 +79,156 @@ public class PipeSinkSubtaskTest {
     }
   }
 
+  @Test
+  public void testDiscardEventsOfPipeNotBlockedByConnectionRetry() throws 
Exception {
+    final CountDownLatch handshakeEntered = new CountDownLatch(1);
+    final CountDownLatch releaseHandshake = new CountDownLatch(1);
+    final CountDownLatch discardEntered = new CountDownLatch(1);
+    final AtomicBoolean discardDuringHandshake = new AtomicBoolean(false);
+    final PipeConnector connector =
+        new BlockingHandshakeConnector(
+            handshakeEntered,
+            releaseHandshake,
+            new CountDownLatch(0),
+            new AtomicBoolean(false),
+            discardEntered,
+            discardDuringHandshake);
+    final UnboundedBlockingPendingQueue<?> pendingQueue = 
mock(UnboundedBlockingPendingQueue.class);
+
+    final PipeSinkSubtask subtask =
+        new PipeSinkSubtask(
+            "PipeSinkSubtaskTest",
+            System.currentTimeMillis(),
+            "data_test",
+            "data_test",
+            0,
+            (UnboundedBlockingPendingQueue) pendingQueue,
+            connector);
+
+    final Thread failureThread =
+        new Thread(() -> subtask.onFailure(new 
PipeConnectionException("connection broken")));
+    failureThread.start();
+    Assert.assertTrue(handshakeEntered.await(5, TimeUnit.SECONDS));
+
+    final CountDownLatch discardReturned = new CountDownLatch(1);
+    final Thread discardThread =
+        new Thread(
+            () -> {
+              try {
+                subtask.discardEventsOfPipe(new CommitterKey("pipe", 1L, 1, 
-1));
+              } finally {
+                discardReturned.countDown();
+              }
+            });
+    discardThread.start();
+
+    try {
+      Assert.assertTrue(discardReturned.await(5, TimeUnit.SECONDS));
+      Assert.assertEquals(1L, discardEntered.getCount());
+      Assert.assertFalse(discardDuringHandshake.get());
+    } finally {
+      releaseHandshake.countDown();
+      failureThread.join(5000);
+      Assert.assertTrue(discardEntered.await(1, TimeUnit.SECONDS));
+      Assert.assertFalse(discardDuringHandshake.get());
+      subtask.close();
+    }
+  }
+
+  @Test
+  public void testCloseNotConcurrentWithConnectionRetry() throws Exception {
+    final int originalTimeout =
+        
CommonDescriptor.getInstance().getConfig().getDnConnectionTimeoutInMS();
+    CommonDescriptor.getInstance().getConfig().setDnConnectionTimeoutInMS(30);
+
+    final CountDownLatch handshakeEntered = new CountDownLatch(1);
+    final CountDownLatch releaseHandshake = new CountDownLatch(1);
+    final CountDownLatch closeEntered = new CountDownLatch(1);
+    final AtomicBoolean closeDuringHandshake = new AtomicBoolean(false);
+    final PipeConnector connector =
+        new BlockingHandshakeConnector(
+            handshakeEntered,
+            releaseHandshake,
+            closeEntered,
+            closeDuringHandshake,
+            new CountDownLatch(0),
+            new AtomicBoolean(false));
+    final UnboundedBlockingPendingQueue<?> pendingQueue = 
mock(UnboundedBlockingPendingQueue.class);
+
+    final PipeSinkSubtask subtask =
+        new PipeSinkSubtask(
+            "PipeSinkSubtaskTest",
+            System.currentTimeMillis(),
+            "data_test",
+            "data_test",
+            0,
+            (UnboundedBlockingPendingQueue) pendingQueue,
+            connector);
+
+    final Thread failureThread =
+        new Thread(() -> subtask.onFailure(new 
PipeConnectionException("connection broken")));
+    failureThread.start();
+
+    try {
+      Assert.assertTrue(handshakeEntered.await(5, TimeUnit.SECONDS));
+
+      final long startTime = System.currentTimeMillis();
+      subtask.close();
+
+      Assert.assertTrue(System.currentTimeMillis() - startTime < 1000);
+      Assert.assertFalse(closeEntered.await(100, TimeUnit.MILLISECONDS));
+      Assert.assertFalse(closeDuringHandshake.get());
+    } finally {
+      releaseHandshake.countDown();
+      Assert.assertTrue(closeEntered.await(1, TimeUnit.SECONDS));
+      failureThread.join(5000);
+      Assert.assertFalse(closeDuringHandshake.get());
+      
CommonDescriptor.getInstance().getConfig().setDnConnectionTimeoutInMS(originalTimeout);
+    }
+  }
+
+  @Test
+  public void testCloseDoesNotWaitForeverForConnectorClose() throws Exception {
+    final int originalTimeout =
+        
CommonDescriptor.getInstance().getConfig().getDnConnectionTimeoutInMS();
+    CommonDescriptor.getInstance().getConfig().setDnConnectionTimeoutInMS(30);
+
+    final PipeConnector connector = mock(PipeConnector.class);
+    final UnboundedBlockingPendingQueue<?> pendingQueue = 
mock(UnboundedBlockingPendingQueue.class);
+    final CountDownLatch closeEntered = new CountDownLatch(1);
+    final CountDownLatch releaseClose = new CountDownLatch(1);
+
+    doAnswer(
+            invocation -> {
+              closeEntered.countDown();
+              releaseClose.await(2, TimeUnit.SECONDS);
+              return null;
+            })
+        .when(connector)
+        .close();
+
+    final PipeSinkSubtask subtask =
+        new PipeSinkSubtask(
+            "PipeSinkSubtaskTest",
+            System.currentTimeMillis(),
+            "data_test",
+            "data_test",
+            0,
+            (UnboundedBlockingPendingQueue) pendingQueue,
+            connector);
+
+    try {
+      final long startTime = System.currentTimeMillis();
+      subtask.close();
+
+      Assert.assertTrue(closeEntered.await(1, TimeUnit.SECONDS));
+      Assert.assertTrue(System.currentTimeMillis() - startTime < 1000);
+    } finally {
+      releaseClose.countDown();
+      
CommonDescriptor.getInstance().getConfig().setDnConnectionTimeoutInMS(originalTimeout);
+    }
+  }
+
   @Test
   public void testTransferExceptionUsesDisplayTaskID() throws Exception {
     final PipeConnector connector = mock(PipeConnector.class);
@@ -104,4 +265,97 @@ public class PipeSinkSubtaskTest {
       subtask.close();
     }
   }
+
+  private static class BlockingHandshakeConnector
+      implements PipeConnector, PipeConnectorWithEventDiscard {
+
+    private final CountDownLatch handshakeEntered;
+    private final CountDownLatch releaseHandshake;
+    private final CountDownLatch closeEntered;
+    private final AtomicBoolean closeDuringHandshake;
+    private final CountDownLatch discardEntered;
+    private final AtomicBoolean discardDuringHandshake;
+    private volatile boolean handshaking;
+
+    private BlockingHandshakeConnector(
+        final CountDownLatch handshakeEntered, final CountDownLatch 
releaseHandshake) {
+      this(
+          handshakeEntered,
+          releaseHandshake,
+          new CountDownLatch(0),
+          new AtomicBoolean(false),
+          new CountDownLatch(0),
+          new AtomicBoolean(false));
+    }
+
+    private BlockingHandshakeConnector(
+        final CountDownLatch handshakeEntered,
+        final CountDownLatch releaseHandshake,
+        final CountDownLatch closeEntered,
+        final AtomicBoolean closeDuringHandshake,
+        final CountDownLatch discardEntered,
+        final AtomicBoolean discardDuringHandshake) {
+      this.handshakeEntered = handshakeEntered;
+      this.releaseHandshake = releaseHandshake;
+      this.closeEntered = closeEntered;
+      this.closeDuringHandshake = closeDuringHandshake;
+      this.discardEntered = discardEntered;
+      this.discardDuringHandshake = discardDuringHandshake;
+    }
+
+    @Override
+    public void validate(final PipeParameterValidator validator) throws 
Exception {
+      // No-op
+    }
+
+    @Override
+    public void customize(
+        final PipeParameters parameters, final 
PipeConnectorRuntimeConfiguration configuration)
+        throws Exception {
+      // No-op
+    }
+
+    @Override
+    public void handshake() throws Exception {
+      handshaking = true;
+      handshakeEntered.countDown();
+      try {
+        releaseHandshake.await(5, TimeUnit.SECONDS);
+      } finally {
+        handshaking = false;
+      }
+    }
+
+    @Override
+    public void heartbeat() throws Exception {
+      // No-op
+    }
+
+    @Override
+    public void transfer(final TabletInsertionEvent tabletInsertionEvent) 
throws Exception {
+      // No-op
+    }
+
+    @Override
+    public void transfer(final Event event) throws Exception {
+      // No-op
+    }
+
+    @Override
+    public void close() throws Exception {
+      if (handshaking) {
+        closeDuringHandshake.set(true);
+      }
+      closeEntered.countDown();
+    }
+
+    @Override
+    public void discardEventsOfPipe(
+        final String pipeName, final long creationTime, final int regionId) {
+      if (handshaking) {
+        discardDuringHandshake.set(true);
+      }
+      discardEntered.countDown();
+    }
+  }
 }
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/PipeTaskAgent.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/PipeTaskAgent.java
index a8763131307..ee465ada69b 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/PipeTaskAgent.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/PipeTaskAgent.java
@@ -165,7 +165,16 @@ public abstract class PipeTaskAgent {
 
   public TPushPipeMetaRespExceptionMessage handleSinglePipeMetaChanges(
       final PipeMeta pipeMetaFromCoordinator) {
-    acquireWriteLock();
+    final String pipeName = 
pipeMetaFromCoordinator.getStaticMeta().getPipeName();
+    if (!tryWriteLockWithTimeOutInMs(
+        
CommonDescriptor.getInstance().getConfig().getDnConnectionTimeoutInMS() * 2L / 
3)) {
+      return new TPushPipeMetaRespExceptionMessage(
+          pipeName,
+          String.format(
+              "Timed out to wait for the pipe task agent write lock when 
handling single pipe meta changes for pipe %s.",
+              pipeName),
+          System.currentTimeMillis());
+    }
     try {
       return handleSinglePipeMetaChangesInternal(pipeMetaFromCoordinator);
     } finally {
@@ -368,7 +377,15 @@ public abstract class PipeTaskAgent {
   protected abstract void freezeRate(final String pipeName, final long 
creationTime);
 
   public TPushPipeMetaRespExceptionMessage handleDropPipe(final String 
pipeName) {
-    acquireWriteLock();
+    if (!tryWriteLockWithTimeOutInMs(
+        
CommonDescriptor.getInstance().getConfig().getDnConnectionTimeoutInMS() * 2L / 
3)) {
+      return new TPushPipeMetaRespExceptionMessage(
+          pipeName,
+          String.format(
+              "Timed out to wait for the pipe task agent write lock when 
dropping pipe %s.",
+              pipeName),
+          System.currentTimeMillis());
+    }
     try {
       return handleDropPipeInternal(pipeName);
     } finally {
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/subtask/PipeAbstractSinkSubtask.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/subtask/PipeAbstractSinkSubtask.java
index 9ad6f874eaa..1b51e460956 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/subtask/PipeAbstractSinkSubtask.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/subtask/PipeAbstractSinkSubtask.java
@@ -101,38 +101,24 @@ public abstract class PipeAbstractSinkSubtask extends 
PipeReportableSubtask {
     synchronized (this) {
       isSubmitted = false;
 
-      if (isClosed.get()) {
-        LOGGER.info(PipeMessages.ON_FAILURE_IGNORED_CONNECTOR_DROPPED, 
throwable);
-        clearReferenceCountAndReleaseLastEvent(null);
-        return;
-      }
-
-      // We assume that the event is cleared as the "lastEvent" in processor 
subtask and reaches the
-      // connector subtask. Then, it may fail because of released resource and 
block the other pipes
-      // using the same connector. We simply discard it.
-      if (lastExceptionEvent instanceof EnrichedEvent
-          && ((EnrichedEvent) lastExceptionEvent).isReleased()) {
-        LOGGER.info(PipeMessages.ON_FAILURE_IGNORED_EVENT_RELEASED, throwable);
-        submitSelf();
+      if (tryIgnoreFailure(throwable)) {
         return;
       }
+    }
 
-      // If lastExceptionEvent != lastEvent, it indicates that the lastEvent's 
reference has been
-      // changed because the pipe of it has been dropped. In that case, we 
just discard the event.
-      if (lastEvent != lastExceptionEvent) {
-        LOGGER.info(PipeMessages.ON_FAILURE_IGNORED_EVENT_PIPE_DROPPED, 
throwable);
-        clearReferenceCountAndReleaseLastExceptionEvent();
-        submitSelf();
+    if (throwable instanceof PipeConnectionException) {
+      // Retry to connect to the target system if the connection is broken. Do 
not hold the subtask
+      // lock here because handshaking with an external sink may block for a 
long time, while drop
+      // pipe needs the same lock to discard in-flight events.
+      if (onPipeConnectionException(throwable)) {
         return;
       }
 
-      if (throwable instanceof PipeConnectionException) {
-        // Retry to connect to the target system if the connection is broken
-        // We should reconstruct the client before re-submit the subtask
-        if (onPipeConnectionException(throwable)) {
-          // return if the pipe task should be stopped
+      synchronized (this) {
+        if (tryIgnoreFailure(throwable)) {
           return;
         }
+
         if 
(PipeConfig.getInstance().isPipeSinkRetryLocallyForConnectionError()) {
           super.onFailure(
               new PipeRuntimeSinkNonReportTimeConfigurableException(
@@ -140,19 +126,57 @@ public abstract class PipeAbstractSinkSubtask extends 
PipeReportableSubtask {
           return;
         }
       }
+    }
 
-      // Handle exceptions if any available clients exist
-      // Notice that the PipeRuntimeConnectorCriticalException must be thrown 
here
-      // because the upper layer relies on this to stop all the related pipe 
tasks
-      // Other exceptions may cause the subtask to stop forever and can not be 
restarted
-      if (throwable instanceof PipeRuntimeSinkCriticalException) {
-        super.onFailure(throwable);
-      } else {
-        // Print stack trace for better debugging
-        PipeLogger.log(
-            LOGGER::warn, throwable, 
PipeMessages.NON_CRITICAL_EXCEPTION_WILL_THROW_CRITICAL);
-        super.onFailure(new 
PipeRuntimeSinkCriticalException(throwable.getMessage()));
+    synchronized (this) {
+      if (tryIgnoreFailure(throwable)) {
+        return;
       }
+      handleFailure(throwable);
+    }
+  }
+
+  private boolean tryIgnoreFailure(final Throwable throwable) {
+    if (isClosed.get()) {
+      LOGGER.info(PipeMessages.ON_FAILURE_IGNORED_CONNECTOR_DROPPED, 
throwable);
+      clearReferenceCountAndReleaseLastEvent(null);
+      return true;
+    }
+
+    // We assume that the event is cleared as the "lastEvent" in processor 
subtask and reaches the
+    // connector subtask. Then, it may fail because of released resource and 
block the other pipes
+    // using the same connector. We simply discard it.
+    if (lastExceptionEvent instanceof EnrichedEvent
+        && ((EnrichedEvent) lastExceptionEvent).isReleased()) {
+      LOGGER.info(PipeMessages.ON_FAILURE_IGNORED_EVENT_RELEASED, throwable);
+      submitSelf();
+      return true;
+    }
+
+    // If lastExceptionEvent != lastEvent, it indicates that the lastEvent's 
reference has been
+    // changed because the pipe of it has been dropped. In that case, we just 
discard the event.
+    if (lastEvent != lastExceptionEvent) {
+      LOGGER.info(PipeMessages.ON_FAILURE_IGNORED_EVENT_PIPE_DROPPED, 
throwable);
+      clearReferenceCountAndReleaseLastExceptionEvent();
+      submitSelf();
+      return true;
+    }
+
+    return false;
+  }
+
+  private void handleFailure(final Throwable throwable) {
+    // Handle exceptions if any available clients exist
+    // Notice that the PipeRuntimeConnectorCriticalException must be thrown 
here
+    // because the upper layer relies on this to stop all the related pipe 
tasks
+    // Other exceptions may cause the subtask to stop forever and can not be 
restarted
+    if (throwable instanceof PipeRuntimeSinkCriticalException) {
+      super.onFailure(throwable);
+    } else {
+      // Print stack trace for better debugging
+      PipeLogger.log(
+          LOGGER::warn, throwable, 
PipeMessages.NON_CRITICAL_EXCEPTION_WILL_THROW_CRITICAL);
+      super.onFailure(new 
PipeRuntimeSinkCriticalException(throwable.getMessage()));
     }
   }
 
@@ -169,7 +193,9 @@ public abstract class PipeAbstractSinkSubtask extends 
PipeReportableSubtask {
     int retry = 0;
     while (retry < MAX_RETRY_TIMES) {
       try {
-        outputPipeSink.handshake();
+        if (!handshakeOutputPipeSink()) {
+          return false;
+        }
         LOGGER.info(PipeMessages.HANDSHAKE_SUCCESS, 
outputPipeSink.getClass().getName());
         break;
       } catch (final Exception e) {
@@ -224,6 +250,11 @@ public abstract class PipeAbstractSinkSubtask extends 
PipeReportableSubtask {
     return false;
   }
 
+  protected boolean handshakeOutputPipeSink() throws Exception {
+    outputPipeSink.handshake();
+    return true;
+  }
+
   private long getHandshakeRetrySleepInterval(final Throwable throwable, final 
int retry) {
     final long defaultInterval = retry * 
PipeConfig.getInstance().getPipeSinkRetryIntervalMs();
     return isAuthenticationFailure(throwable)
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/subtask/PipeSubtask.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/subtask/PipeSubtask.java
index 8522fce0cd2..d87fe3a5f38 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/subtask/PipeSubtask.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/subtask/PipeSubtask.java
@@ -58,7 +58,7 @@ public abstract class PipeSubtask
   // For fail-over
   public static final int MAX_RETRY_TIMES = 5;
   protected final AtomicInteger retryCount = new AtomicInteger(0);
-  protected Event lastEvent;
+  protected volatile Event lastEvent;
 
   protected PipeSubtask(final String taskID, final long creationTime) {
     super();


Reply via email to