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

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


The following commit(s) were added to refs/heads/dev/1.3 by this push:
     new d3fb1eac571 Fix pipe drop stuck on blocking sink reconnect (#18145) 
(#18188)
d3fb1eac571 is described below

commit d3fb1eac571bfb9e4d6847ac5d849f870a5fc77b
Author: Caideyipi <[email protected]>
AuthorDate: Mon Jul 13 12:10:00 2026 +0800

    Fix pipe drop stuck on blocking sink reconnect (#18145) (#18188)
    
    * Fix pipe drop stuck on blocking sink reconnect
    
    * Address pipe sink subtask review comments
    
    (cherry picked from commit 4482e65468a0d3699bfed927c8afa293551ae5e2)
---
 .../agent/task/subtask/sink/PipeSinkSubtask.java   | 172 +++++++++++++--
 .../task/subtask/sink/PipeSinkSubtaskTest.java     | 242 +++++++++++++++++++++
 .../commons/pipe/agent/task/PipeTaskAgent.java     |  21 +-
 .../task/subtask/PipeAbstractSinkSubtask.java      | 118 ++++++----
 .../pipe/agent/task/subtask/PipeSubtask.java       |   2 +-
 5 files changed, 487 insertions(+), 68 deletions(-)

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 ad8a1273d00..b8c7423c232 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;
@@ -49,6 +50,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 {
 
@@ -67,6 +73,8 @@ public class PipeSinkSubtask extends PipeAbstractSinkSubtask {
   // when no event can be pulled.
   public static final PipeHeartbeatEvent CRON_HEARTBEAT_EVENT =
       new PipeHeartbeatEvent("cron", false);
+  private final ReentrantLock outputPipeConnectorOperationLock = new 
ReentrantLock();
+  private final Queue<CommitterKey> pendingDiscardCommitterKeys = new 
ConcurrentLinkedQueue<>();
 
   public PipeSinkSubtask(
       final String taskID,
@@ -111,15 +119,19 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
       }
 
       if (event instanceof TabletInsertionEvent) {
-        outputPipeConnector.transfer((TabletInsertionEvent) event);
-        PipeDataRegionSinkMetrics.getInstance().markTabletEvent(taskID);
+        if (executeOutputPipeConnectorOperation(
+            () -> outputPipeConnector.transfer((TabletInsertionEvent) event))) 
{
+          PipeDataRegionSinkMetrics.getInstance().markTabletEvent(taskID);
+        }
       } else if (event instanceof TsFileInsertionEvent) {
-        outputPipeConnector.transfer((TsFileInsertionEvent) event);
-        PipeDataRegionSinkMetrics.getInstance().markTsFileEvent(taskID);
+        if (executeOutputPipeConnectorOperation(
+            () -> outputPipeConnector.transfer((TsFileInsertionEvent) event))) 
{
+          PipeDataRegionSinkMetrics.getInstance().markTsFileEvent(taskID);
+        }
       } else if (event instanceof PipeSchemaRegionWritePlanEvent) {
-        outputPipeConnector.transfer(event);
-        if (((PipeSchemaRegionWritePlanEvent) event).getPlanNode().getType()
-            != PlanNodeType.DELETE_DATA) {
+        if (executeOutputPipeConnectorOperation(() -> 
outputPipeConnector.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);
@@ -127,10 +139,12 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
       } else if (event instanceof PipeHeartbeatEvent) {
         transferHeartbeatEvent((PipeHeartbeatEvent) event);
       } else {
-        outputPipeConnector.transfer(
-            event instanceof UserDefinedEnrichedEvent
-                ? ((UserDefinedEnrichedEvent) event).getUserDefinedEvent()
-                : event);
+        executeOutputPipeConnectorOperation(
+            () ->
+                outputPipeConnector.transfer(
+                    event instanceof UserDefinedEnrichedEvent
+                        ? ((UserDefinedEnrichedEvent) 
event).getUserDefinedEvent()
+                        : event));
       }
 
       decreaseReferenceCountAndReleaseLastEvent(event, true);
@@ -149,8 +163,13 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
     }
 
     try {
-      outputPipeConnector.heartbeat();
-      outputPipeConnector.transfer(event);
+      if (!executeOutputPipeConnectorOperation(
+          () -> {
+            outputPipeConnector.heartbeat();
+            outputPipeConnector.transfer(event);
+          })) {
+        return;
+      }
     } catch (final Exception e) {
       throw new PipeConnectionException(
           "PipeConnector: "
@@ -167,6 +186,54 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
     PipeDataRegionSinkMetrics.getInstance().markPipeHeartbeatEvent(taskID);
   }
 
+  @Override
+  protected boolean handshakeOutputPipeConnector() throws Exception {
+    return executeOutputPipeConnectorOperation(() -> 
outputPipeConnector.handshake());
+  }
+
+  private boolean executeOutputPipeConnectorOperation(final 
OutputPipeConnectorOperation operation)
+      throws Exception {
+    outputPipeConnectorOperationLock.lock();
+    try {
+      discardPendingEventsOfPipeUnderLock();
+      if (isClosed.get()) {
+        return false;
+      }
+
+      operation.execute();
+      discardPendingEventsOfPipeUnderLock();
+      return true;
+    } finally {
+      outputPipeConnectorOperationLock.unlock();
+    }
+  }
+
+  private void discardPendingEventsOfPipeUnderLock() {
+    if (!(outputPipeConnector instanceof PipeConnectorWithEventDiscard)) {
+      pendingDiscardCommitterKeys.clear();
+      return;
+    }
+
+    CommitterKey committerKey;
+    while ((committerKey = pendingDiscardCommitterKeys.poll()) != null) {
+      try {
+        ((PipeConnectorWithEventDiscard) 
outputPipeConnector).discardEventsOfPipe(committerKey);
+      } catch (final Exception e) {
+        LOGGER.warn(
+            "Failed to discard events of pipe {} in connector subtask {}.",
+            committerKey.getPipeName(),
+            taskID,
+            e);
+      }
+    }
+  }
+
+  @FunctionalInterface
+  private interface OutputPipeConnectorOperation {
+
+    void execute() throws Exception;
+  }
+
   @Override
   public void close() {
     if (!attributeSortedString.startsWith("schema_")) {
@@ -178,12 +245,13 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
     isClosed.set(true);
     try {
       final long startTime = System.currentTimeMillis();
-      outputPipeConnector.close();
-      LOGGER.info(
-          "Pipe: connector subtask {} ({}) was closed within {} ms",
-          taskID,
-          outputPipeConnector,
-          System.currentTimeMillis() - startTime);
+      if (closeOutputPipeConnector()) {
+        LOGGER.info(
+            "Pipe: connector subtask {} ({}) was closed within {} ms",
+            taskID,
+            outputPipeConnector,
+            System.currentTimeMillis() - startTime);
+      }
     } catch (final Exception e) {
       LOGGER.info(
           "Exception occurred when closing pipe connector subtask {}, root 
cause: {}",
@@ -198,6 +266,55 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
     }
   }
 
+  private boolean closeOutputPipeConnector() throws Exception {
+    final AtomicReference<Exception> exception = new AtomicReference<>();
+    final AtomicBoolean closeStarted = new AtomicBoolean(false);
+    final Thread closeThread =
+        new Thread(
+            () -> {
+              outputPipeConnectorOperationLock.lock();
+              try {
+                discardPendingEventsOfPipeUnderLock();
+                closeStarted.set(true);
+                outputPipeConnector.close();
+              } catch (final Exception e) {
+                exception.set(e);
+              } finally {
+                outputPipeConnectorOperationLock.unlock();
+              }
+            },
+            "PipeSinkSubtaskClose-" + taskID);
+    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(
+          "Pipe sink subtask close timed out after {} ms for {}, close 
operation {}.",
+          timeoutInMs,
+          taskID,
+          closeStarted.get() ? "is still running" : "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.
@@ -247,8 +364,21 @@ public class PipeSinkSubtask extends 
PipeAbstractSinkSubtask {
       decreaseHighPriorityTaskCount();
     }
 
-    if (outputPipeConnector instanceof PipeConnectorWithEventDiscard) {
-      ((PipeConnectorWithEventDiscard) 
outputPipeConnector).discardEventsOfPipe(committerKey);
+    discardOutputPipeConnectorEventsOfPipe(committerKey);
+  }
+
+  private void discardOutputPipeConnectorEventsOfPipe(final CommitterKey 
committerKey) {
+    if (!(outputPipeConnector instanceof PipeConnectorWithEventDiscard)) {
+      return;
+    }
+
+    pendingDiscardCommitterKeys.offer(committerKey);
+    if (outputPipeConnectorOperationLock.tryLock()) {
+      try {
+        discardPendingEventsOfPipeUnderLock();
+      } finally {
+        outputPipeConnectorOperationLock.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 2a15fb9ea18..7d30078eaa8 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,14 +19,27 @@
 
 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.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.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.withSettings;
@@ -60,4 +73,233 @@ public class PipeSinkSubtaskTest {
       subtask.close();
     }
   }
+
+  @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",
+            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",
+            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",
+            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);
+    }
+  }
+
+  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,
+        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 ee629b065df..03906e7e977 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
@@ -163,7 +163,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 {
@@ -355,7 +364,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 071b5897920..eadd889287c 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
@@ -96,44 +96,24 @@ public abstract class PipeAbstractSinkSubtask extends 
PipeReportableSubtask {
     synchronized (this) {
       isSubmitted = false;
 
-      if (isClosed.get()) {
-        LOGGER.info(
-            "onFailure in pipe transfer, ignored because the connector subtask 
is 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(
-            "onFailure in pipe transfer, ignored because the failure event is 
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(
-            "onFailure in pipe transfer, ignored because the failure event's 
pipe is 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(
@@ -141,21 +121,64 @@ 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,
-            "A non PipeRuntimeSinkCriticalException occurred, will throw a 
PipeRuntimeSinkCriticalException.");
-        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(
+          "onFailure in pipe transfer, ignored because the connector subtask 
is 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(
+          "onFailure in pipe transfer, ignored because the failure event is 
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(
+          "onFailure in pipe transfer, ignored because the failure event's 
pipe is 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,
+          "A non PipeRuntimeSinkCriticalException occurred, will throw a 
PipeRuntimeSinkCriticalException.");
+      super.onFailure(new 
PipeRuntimeSinkCriticalException(throwable.getMessage()));
     }
   }
 
@@ -171,7 +194,9 @@ public abstract class PipeAbstractSinkSubtask extends 
PipeReportableSubtask {
     int retry = 0;
     while (retry < MAX_RETRY_TIMES) {
       try {
-        outputPipeConnector.handshake();
+        if (!handshakeOutputPipeConnector()) {
+          return false;
+        }
         LOGGER.info(
             "{} handshakes with the target system successfully.",
             outputPipeConnector.getClass().getName());
@@ -231,6 +256,11 @@ public abstract class PipeAbstractSinkSubtask extends 
PipeReportableSubtask {
     return false;
   }
 
+  protected boolean handshakeOutputPipeConnector() throws Exception {
+    outputPipeConnector.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 2da797c2b3b..1b58d1d6178 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
@@ -55,7 +55,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