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 834418ed5ed [Pipe] Improve timeout diagnostics and DROP PIPE 
observability (#18297)
834418ed5ed is described below

commit 834418ed5edc62a10c45f5457a7f94a214eac67e
Author: Caideyipi <[email protected]>
AuthorDate: Wed Jul 29 09:53:22 2026 +0800

    [Pipe] Improve timeout diagnostics and DROP PIPE observability (#18297)
    
    * Improve Pipe procedure timeout diagnostics
    
    * Add pre-delete state to drop pipe procedure
    
    * Address pipe timeout diagnostic review comments
    
    * Fix pipe timeout message i18n
---
 .../iotdb/confignode/i18n/ConfigNodeMessages.java  |   8 +
 .../iotdb/confignode/i18n/ManagerMessages.java     |   3 +
 .../iotdb/confignode/i18n/ProcedureMessages.java   |  28 +++
 .../iotdb/confignode/i18n/ConfigNodeMessages.java  |   8 +
 .../iotdb/confignode/i18n/ManagerMessages.java     |   3 +
 .../iotdb/confignode/i18n/ProcedureMessages.java   |  40 ++++
 .../iotdb/confignode/manager/ProcedureManager.java |  72 ++++---
 .../runtime/heartbeat/PipeHeartbeatParser.java     |   8 +
 .../confignode/persistence/pipe/PipeTaskInfo.java  |  58 +++++-
 .../procedure/env/ConfigNodeProcedureEnv.java      |  66 +++++--
 .../procedure/impl/StateMachineProcedure.java      |  11 ++
 .../impl/pipe/AbstractOperatePipeProcedureV2.java  | 211 ++++++++++++++++++++-
 .../impl/pipe/runtime/PipeMetaSyncProcedure.java   |   4 +
 .../impl/pipe/task/AlterPipeProcedureV2.java       |   2 +-
 .../impl/pipe/task/DropPipeProcedureV2.java        |  48 ++++-
 .../confignode/procedure/scheduler/LockQueue.java  |   6 +-
 .../request/ConfigPhysicalPlanSerDeTest.java       |   2 +-
 .../consensus/response/pipe/PipeTableRespTest.java |  12 ++
 .../runtime/heartbeat/PipeHeartbeatParserTest.java |  36 ++++
 .../pipe/PipeTaskInfoAutoRestartTest.java          |  41 ++++
 .../pipe/AbstractOperatePipeProcedureV2Test.java   |  31 +++
 .../impl/pipe/task/DropPipeProcedureV2Test.java    | 154 +++++++++++++++
 .../commons/pipe/agent/task/PipeTaskAgent.java     |   6 +
 .../commons/pipe/agent/task/meta/PipeStatus.java   |   3 +
 .../iotdb/commons/pipe/task/PipeMetaDeSerTest.java |  13 ++
 25 files changed, 812 insertions(+), 62 deletions(-)

diff --git 
a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
 
b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
index 494201b1e9f..014ad8e807c 100644
--- 
a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
+++ 
b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
@@ -516,15 +516,23 @@ public final class ConfigNodeMessages {
   public static final String FAILED_TO_SET_PIPE_STATUS_BECAUSE = "Failed to 
set pipe status, because ";
   public static final String FAILED_TO_DROP_PIPE_BECAUSE = "Failed to drop 
pipe, because ";
   public static final String FAILED_TO_ALTER_PIPE_BECAUSE = "Failed to alter 
pipe, because ";
+  public static final String 
EXCEPTION_FAILED_TO_ALTER_PIPE_ARG_THE_PIPE_DOES_NOT_EXIST_29E0DCEB =
+      "Failed to alter pipe %s, the pipe does not exist";
+  public static final String 
EXCEPTION_FAILED_TO_ALTER_PIPE_ARG_THE_PIPE_IS_BEING_DROPPED_919F1E2B =
+      "Failed to alter pipe %s, the pipe is being dropped";
   public static final String FAILED_TO_CREATE_MULTIPLE_PIPES_BECAUSE = "Failed 
to create multiple pipes, because ";
   public static final String FAILED_TO_START_PIPE_BECAUSE_PIPE_DOES_NOT_EXIST =
       "Failed to start pipe %s, the pipe does not exist";
   public static final String 
FAILED_TO_START_PIPE_BECAUSE_PIPE_IS_ALREADY_DROPPED =
       "Failed to start pipe %s, the pipe is already dropped";
+  public static final String 
EXCEPTION_FAILED_TO_START_PIPE_ARG_THE_PIPE_IS_BEING_DROPPED_B41F4638 =
+      "Failed to start pipe %s, the pipe is being dropped";
   public static final String FAILED_TO_STOP_PIPE_BECAUSE_PIPE_DOES_NOT_EXIST =
       "Failed to stop pipe %s, the pipe does not exist";
   public static final String 
FAILED_TO_STOP_PIPE_BECAUSE_PIPE_IS_ALREADY_DROPPED =
       "Failed to stop pipe %s, the pipe is already dropped";
+  public static final String 
EXCEPTION_FAILED_TO_STOP_PIPE_ARG_THE_PIPE_IS_BEING_DROPPED_37AFB22B =
+      "Failed to stop pipe %s, the pipe is being dropped";
   public static final String FAILED_TO_HANDLE_LEADER_CHANGE_BECAUSE = "Failed 
to handle leader change, because ";
   public static final String FAILED_TO_HANDLE_META_CHANGES_BECAUSE = "Failed 
to handle meta changes, because ";
   public static final String GET_PIPEPLUGIN_JAR_FAILED_BECAUSE = "Get 
PipePlugin_Jar failed, because ";
diff --git 
a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
 
b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
index 20316edc397..7028e97be7e 100644
--- 
a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
+++ 
b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
@@ -694,5 +694,8 @@ public final class ManagerMessages {
   public static final String 
MESSAGE_SUBSCRIPTIONOWNERLEASESYNCER_IS_STOPPED_SUCCESSFULLY_11442F29 = 
"SubscriptionOwnerLeaseSyncer is stopped successfully.";
   public static final String 
MESSAGE_NO_AVAILABLE_ARG_REGIONGROUP_FOR_DATABASE_ARG_REGIONGROUPS_VISIBLE_IN_PARTITIONINFO_AND_THEIR_LOADCACHE_STATUS_ARG_615F5D49
 =
       "No available {} RegionGroup for Database: {}. RegionGroups visible in 
PartitionInfo and their LoadCache status: {}";
+  public static final String
+      
MESSAGE_ARG_PLEASE_MANUALLY_CHECK_LATER_WHETHER_THE_PROCEDURE_IS_EXECUTED_SUCCESSFULLY_A82B739D
 =
+          "%s Please manually check later whether the procedure is executed 
successfully.";
 
 }
diff --git 
a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
 
b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
index 8c0be448f7f..1234336a79a 100644
--- 
a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
+++ 
b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
@@ -1408,4 +1408,32 @@ public final class ProcedureMessages {
   public static final String 
MESSAGE_UNEXPECTED_DATAPARTITIONTABLEINTEGRITYCHECKPROCEDURESTATE_ARG_WHEN_SHOWING_PROGRESS_D3C07BA1
 =
       "Unexpected DataPartitionTableIntegrityCheckProcedureState {} when 
showing progress";
 
+  public static final String 
MESSAGE_NO_PROCEDURE_WORKER_IS_CURRENTLY_AVAILABLE_WORKERS_MAY_BE_BUSY_OR_BLOCKED_BY_OTHER_PROCEDURES_AB0B1595
 =
+      "no Procedure worker is currently available; workers may be busy or 
blocked by other procedures.";
+  public static final String 
MESSAGE_PIPE_OPERATION_ARG_TIMED_OUT_PROCEDUREID_ARG_STUCK_AT_ARG_REASON_ARG_THE_PROCEDURE_IS_STILL_RUNNING_7EEAC50E
 =
+      "Pipe operation %s timed out (procedureId=%d). Stuck at %s. Reason: %s. 
The procedure is still running.";
+  public static final String 
MESSAGE_WAITING_TO_ACQUIRE_THE_PIPETASKCOORDINATOR_LOCK_BECAUSE_ANOTHER_PIPE_OPERATION_IS_HOLDING_IT_25A3B6B8
 =
+      "waiting to acquire the PipeTaskCoordinator lock because another Pipe 
operation is holding it.";
+  public static final String 
MESSAGE_WAITING_TO_ACQUIRE_THE_CONFIGNODE_NODE_LOCK_BECAUSE_ANOTHER_NODE_PROCEDURE_IS_HOLDING_IT_56494E86
 =
+      "waiting to acquire the ConfigNode node lock because another node 
procedure is holding it.";
+  public static final String 
MESSAGE_WAITING_TO_ACQUIRE_THE_CONFIGNODE_NODE_LOCK_HELD_BY_ARG_PROCEDUREID_ARG_3F432041
 =
+      "waiting to acquire the ConfigNode node lock held by %s 
(procedureId=%d).";
+  public static final String 
MESSAGE_PIPE_REQUEST_OR_PLUGIN_VALIDATION_HAS_NOT_COMPLETED_A_PLUGIN_CHECK_OR_METADATA_ACCESS_MAY_BE_SLOW_57C36CEF
 =
+      "Pipe request or plugin validation has not completed; a plugin check or 
metadata access may be slow.";
+  public static final String 
MESSAGE_PIPE_METADATA_CALCULATION_HAS_NOT_COMPLETED_METADATA_ACCESS_OR_LOCAL_CALCULATION_MAY_BE_SLOW_DEBF2504
 =
+      "Pipe metadata calculation has not completed; metadata access or local 
calculation may be slow.";
+  public static final String 
MESSAGE_THE_CONFIGNODE_CONSENSUS_WRITE_HAS_NOT_RETURNED_RUN_SHOW_CLUSTER_TO_CHECK_NODE_STATUS_B0A6E1A7
 =
+      "the ConfigNode consensus write has not returned; run SHOW CLUSTER to 
check node status.";
+  public static final String 
MESSAGE_DATANODES_ARG_HAVE_NOT_RESPONDED_TO_THE_PIPE_METADATA_PUSH_RUN_SHOW_CLUSTER_TO_CHECK_THEIR_STATUS_9C2F806F
 =
+      "DataNodes %s have not responded to the Pipe metadata push; run SHOW 
CLUSTER to check their status.";
+  public static final String 
MESSAGE_THE_PIPE_METADATA_PUSH_HAS_NOT_COMPLETED_RUN_SHOW_CLUSTER_TO_CHECK_DATANODE_STATUS_A8F3F0A0
 =
+      "the Pipe metadata push has not completed; run SHOW CLUSTER to check 
DataNode status.";
+  public static final String 
MESSAGE_THE_PREVIOUS_ATTEMPT_FAILED_WITH_ARG_AND_THIS_STATE_IS_BEING_RETRIED_7A541F27
 =
+      "the previous attempt failed with '%s' and this state is being retried.";
+  public static final String 
MESSAGE_THE_STATE_FAILED_WITH_ARG_AND_ROLLBACK_IS_PENDING_E7B43829 =
+      "the state failed with '%s' and rollback is pending.";
+  public static final String MESSAGE_ROLLING_BACK_AFTER_FAILURE_ARG_474DF456 =
+      "rolling back after failure: %s.";
+  public static final String 
MESSAGE_ROLLING_BACK_AFTER_AN_EARLIER_FAILURE_850D0AF5 =
+      "rolling back after an earlier failure.";
 }
diff --git 
a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
 
b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
index 0193e5a0485..3c3bce0b845 100644
--- 
a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
+++ 
b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
@@ -480,15 +480,23 @@ public final class ConfigNodeMessages {
   public static final String FAILED_TO_SET_PIPE_STATUS_BECAUSE = "设置 pipe 
状态失败,原因:";
   public static final String FAILED_TO_DROP_PIPE_BECAUSE = "删除 pipe 失败,原因:";
   public static final String FAILED_TO_ALTER_PIPE_BECAUSE = "修改 pipe 失败,原因:";
+  public static final String 
EXCEPTION_FAILED_TO_ALTER_PIPE_ARG_THE_PIPE_DOES_NOT_EXIST_29E0DCEB =
+      "修改 Pipe %s 失败,该 Pipe 不存在";
+  public static final String 
EXCEPTION_FAILED_TO_ALTER_PIPE_ARG_THE_PIPE_IS_BEING_DROPPED_919F1E2B =
+      "修改 Pipe %s 失败,该 Pipe 正在被删除";
   public static final String FAILED_TO_CREATE_MULTIPLE_PIPES_BECAUSE = "批量创建 
pipe 失败,原因:";
   public static final String FAILED_TO_START_PIPE_BECAUSE_PIPE_DOES_NOT_EXIST =
       "启动 pipe %s 失败,pipe 不存在";
   public static final String 
FAILED_TO_START_PIPE_BECAUSE_PIPE_IS_ALREADY_DROPPED =
       "启动 pipe %s 失败,pipe 已被删除";
+  public static final String 
EXCEPTION_FAILED_TO_START_PIPE_ARG_THE_PIPE_IS_BEING_DROPPED_B41F4638 =
+      "启动 Pipe %s 失败,该 Pipe 正在被删除";
   public static final String FAILED_TO_STOP_PIPE_BECAUSE_PIPE_DOES_NOT_EXIST =
       "停止 pipe %s 失败,pipe 不存在";
   public static final String 
FAILED_TO_STOP_PIPE_BECAUSE_PIPE_IS_ALREADY_DROPPED =
       "停止 pipe %s 失败,pipe 已被删除";
+  public static final String 
EXCEPTION_FAILED_TO_STOP_PIPE_ARG_THE_PIPE_IS_BEING_DROPPED_37AFB22B =
+      "停止 Pipe %s 失败,该 Pipe 正在被删除";
   public static final String FAILED_TO_HANDLE_LEADER_CHANGE_BECAUSE = "处理 
leader 变更失败,原因:";
   public static final String FAILED_TO_HANDLE_META_CHANGES_BECAUSE = 
"处理元数据变更失败,原因:";
   public static final String GET_PIPEPLUGIN_JAR_FAILED_BECAUSE = "获取 
PipePlugin Jar 失败,原因:";
diff --git 
a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java
 
b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java
index 8dc9afd1e8b..77c005cddf4 100644
--- 
a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java
+++ 
b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java
@@ -673,5 +673,8 @@ public final class ManagerMessages {
   public static final String 
MESSAGE_SUBSCRIPTIONOWNERLEASESYNCER_IS_STOPPED_SUCCESSFULLY_11442F29 = 
"SubscriptionOwnerLeaseSyncer 已成功停止。";
   public static final String 
MESSAGE_NO_AVAILABLE_ARG_REGIONGROUP_FOR_DATABASE_ARG_REGIONGROUPS_VISIBLE_IN_PARTITIONINFO_AND_THEIR_LOADCACHE_STATUS_ARG_615F5D49
 =
       "数据库 {} 没有可用的 {} RegionGroup。PartitionInfo 中可见的 RegionGroup 及其 LoadCache 
状态:{}";
+  public static final String
+      
MESSAGE_ARG_PLEASE_MANUALLY_CHECK_LATER_WHETHER_THE_PROCEDURE_IS_EXECUTED_SUCCESSFULLY_A82B739D
 =
+          "%s 请稍后手动检查该 Procedure 是否执行成功。";
 
 }
diff --git 
a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
 
b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
index 91bf304b326..624a82278e7 100644
--- 
a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
+++ 
b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
@@ -1484,4 +1484,44 @@ public final class ProcedureMessages {
   public static final String
       
MESSAGE_UNEXPECTED_DATAPARTITIONTABLEINTEGRITYCHECKPROCEDURESTATE_ARG_WHEN_SHOWING_PROGRESS_D3C07BA1
 =
           "非预期的 DataPartitionTableIntegrityCheckProcedureState {}(显示进度时)";
+  public static final String
+      
MESSAGE_PIPE_OPERATION_ARG_TIMED_OUT_PROCEDUREID_ARG_STUCK_AT_ARG_REASON_ARG_THE_PROCEDURE_IS_STILL_RUNNING_7EEAC50E
 =
+          "Pipe 操作 %s 超时(procedureId=%d)。卡在 %s。原因:%s。该 Procedure 仍在运行。";
+  public static final String
+      
MESSAGE_NO_PROCEDURE_WORKER_IS_CURRENTLY_AVAILABLE_WORKERS_MAY_BE_BUSY_OR_BLOCKED_BY_OTHER_PROCEDURES_AB0B1595
 =
+          "当前没有可用的 Procedure worker;worker 可能正忙或被其他 Procedure 阻塞。";
+  public static final String
+      
MESSAGE_WAITING_TO_ACQUIRE_THE_PIPETASKCOORDINATOR_LOCK_BECAUSE_ANOTHER_PIPE_OPERATION_IS_HOLDING_IT_25A3B6B8
 =
+          "正在等待获取 PipeTaskCoordinator 锁,因为另一个 Pipe 操作正在持有该锁。";
+  public static final String
+      
MESSAGE_WAITING_TO_ACQUIRE_THE_CONFIGNODE_NODE_LOCK_BECAUSE_ANOTHER_NODE_PROCEDURE_IS_HOLDING_IT_56494E86
 =
+          "正在等待获取 ConfigNode 节点锁,因为另一个节点 Procedure 正在持有该锁。";
+  public static final String
+      
MESSAGE_WAITING_TO_ACQUIRE_THE_CONFIGNODE_NODE_LOCK_HELD_BY_ARG_PROCEDUREID_ARG_3F432041
 =
+          "正在等待获取由 %s(procedureId=%d)持有的 ConfigNode 节点锁。";
+  public static final String
+      
MESSAGE_PIPE_REQUEST_OR_PLUGIN_VALIDATION_HAS_NOT_COMPLETED_A_PLUGIN_CHECK_OR_METADATA_ACCESS_MAY_BE_SLOW_57C36CEF
 =
+          "Pipe 请求或插件校验尚未完成;插件检查或元数据访问可能过慢。";
+  public static final String
+      
MESSAGE_PIPE_METADATA_CALCULATION_HAS_NOT_COMPLETED_METADATA_ACCESS_OR_LOCAL_CALCULATION_MAY_BE_SLOW_DEBF2504
 =
+          "Pipe 元数据计算尚未完成;元数据访问或本地计算可能过慢。";
+  public static final String
+      
MESSAGE_THE_CONFIGNODE_CONSENSUS_WRITE_HAS_NOT_RETURNED_RUN_SHOW_CLUSTER_TO_CHECK_NODE_STATUS_B0A6E1A7
 =
+          "ConfigNode 共识写尚未返回;请执行 SHOW CLUSTER 检查节点状态。";
+  public static final String
+      
MESSAGE_DATANODES_ARG_HAVE_NOT_RESPONDED_TO_THE_PIPE_METADATA_PUSH_RUN_SHOW_CLUSTER_TO_CHECK_THEIR_STATUS_9C2F806F
 =
+          "DataNode %s 尚未响应 Pipe 元数据推送;请执行 SHOW CLUSTER 检查其状态。";
+  public static final String
+      
MESSAGE_THE_PIPE_METADATA_PUSH_HAS_NOT_COMPLETED_RUN_SHOW_CLUSTER_TO_CHECK_DATANODE_STATUS_A8F3F0A0
 =
+          "Pipe 元数据推送尚未完成;请执行 SHOW CLUSTER 检查 DataNode 状态。";
+  public static final String
+      
MESSAGE_THE_PREVIOUS_ATTEMPT_FAILED_WITH_ARG_AND_THIS_STATE_IS_BEING_RETRIED_7A541F27
 =
+          "上一次尝试因“%s”失败,正在重试此状态。";
+  public static final String
+      MESSAGE_THE_STATE_FAILED_WITH_ARG_AND_ROLLBACK_IS_PENDING_E7B43829 =
+          "此状态因“%s”失败,正在等待回滚。";
+  public static final String MESSAGE_ROLLING_BACK_AFTER_FAILURE_ARG_474DF456 =
+      "正在回滚,失败原因:%s。";
+  public static final String 
MESSAGE_ROLLING_BACK_AFTER_AN_EARLIER_FAILURE_850D0AF5 =
+      "正在回滚此前发生的失败。";
 }
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
index 8f5b27b7e2f..e6a8476500f 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
@@ -74,6 +74,7 @@ import 
org.apache.iotdb.confignode.procedure.impl.node.RemoveAINodeProcedure;
 import 
org.apache.iotdb.confignode.procedure.impl.node.RemoveConfigNodeProcedure;
 import 
org.apache.iotdb.confignode.procedure.impl.node.RemoveDataNodesProcedure;
 import 
org.apache.iotdb.confignode.procedure.impl.partition.DataPartitionTableIntegrityCheckProcedure;
+import 
org.apache.iotdb.confignode.procedure.impl.pipe.AbstractOperatePipeProcedureV2;
 import 
org.apache.iotdb.confignode.procedure.impl.pipe.plugin.CreatePipePluginProcedure;
 import 
org.apache.iotdb.confignode.procedure.impl.pipe.plugin.DropPipePluginProcedure;
 import 
org.apache.iotdb.confignode.procedure.impl.pipe.runtime.PipeHandleLeaderChangeProcedure;
@@ -1661,7 +1662,7 @@ public class ProcedureManager {
         return status;
       } else {
         return new TSStatus(TSStatusCode.PIPE_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status, procedure));
       }
     } catch (final Exception e) {
       return new 
TSStatus(TSStatusCode.PIPE_ERROR.getStatusCode()).setMessage(e.getMessage());
@@ -1677,7 +1678,7 @@ public class ProcedureManager {
         return status;
       } else {
         return new TSStatus(TSStatusCode.PIPE_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status, procedure));
       }
     } catch (final Exception e) {
       return new 
TSStatus(TSStatusCode.PIPE_ERROR.getStatusCode()).setMessage(e.getMessage());
@@ -1708,7 +1709,7 @@ public class ProcedureManager {
         return status;
       } else {
         return new TSStatus(TSStatusCode.PIPE_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status, procedure));
       }
     } catch (Exception e) {
       return new 
TSStatus(TSStatusCode.PIPE_ERROR.getStatusCode()).setMessage(e.getMessage());
@@ -1739,7 +1740,7 @@ public class ProcedureManager {
         return status;
       } else {
         return new TSStatus(TSStatusCode.PIPE_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status, procedure));
       }
     } catch (Exception e) {
       return new 
TSStatus(TSStatusCode.PIPE_ERROR.getStatusCode()).setMessage(e.getMessage());
@@ -1786,7 +1787,7 @@ public class ProcedureManager {
         return status;
       } else {
         return new TSStatus(TSStatusCode.PIPE_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status, procedure));
       }
     } catch (Exception e) {
       return new 
TSStatus(TSStatusCode.PIPE_ERROR.getStatusCode()).setMessage(e.getMessage());
@@ -1803,12 +1804,12 @@ public class ProcedureManager {
       return status;
     } else {
       // if time out, optimistically believe that this procedure will execute 
successfully.
-      if (status.getMessage().equals(PROCEDURE_TIMEOUT_MESSAGE)) {
+      if (isProcedureTimeout(status)) {
         return new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode());
       }
       // otherwise, some exceptions must have occurred, throw them.
       return new TSStatus(TSStatusCode.PIPE_ERROR.getStatusCode())
-          .setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+          .setMessage(wrapTimeoutMessageForPipeProcedure(status));
     }
   }
 
@@ -1872,7 +1873,7 @@ public class ProcedureManager {
         return status;
       } else {
         return new TSStatus(TSStatusCode.PIPE_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status));
       }
     } catch (Exception e) {
       return new 
TSStatus(TSStatusCode.PIPE_ERROR.getStatusCode()).setMessage(e.getMessage());
@@ -1888,7 +1889,7 @@ public class ProcedureManager {
         return status;
       } else {
         return new TSStatus(TSStatusCode.PIPE_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status));
       }
     } catch (Exception e) {
       return new 
TSStatus(TSStatusCode.PIPE_ERROR.getStatusCode()).setMessage(e.getMessage());
@@ -1905,7 +1906,7 @@ public class ProcedureManager {
         return status;
       } else {
         return new TSStatus(TSStatusCode.CREATE_TOPIC_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status));
       }
     } catch (Exception e) {
       return new TSStatus(TSStatusCode.CREATE_TOPIC_ERROR.getStatusCode())
@@ -1946,7 +1947,7 @@ public class ProcedureManager {
         return status;
       }
       return new TSStatus(TSStatusCode.ALTER_TOPIC_ERROR.getStatusCode())
-          .setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+          .setMessage(wrapTimeoutMessageForPipeProcedure(status));
     } catch (Exception e) {
       return new TSStatus(TSStatusCode.ALTER_TOPIC_ERROR.getStatusCode())
           .setMessage(e.getMessage());
@@ -2042,7 +2043,7 @@ public class ProcedureManager {
         return status;
       } else {
         return new TSStatus(TSStatusCode.DROP_TOPIC_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status));
       }
     } catch (Exception e) {
       return new 
TSStatus(TSStatusCode.DROP_TOPIC_ERROR.getStatusCode()).setMessage(e.getMessage());
@@ -2058,7 +2059,7 @@ public class ProcedureManager {
         return status;
       } else {
         return new TSStatus(TSStatusCode.TOPIC_PUSH_META_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status));
       }
     } catch (Exception e) {
       return new TSStatus(TSStatusCode.TOPIC_PUSH_META_ERROR.getStatusCode())
@@ -2075,7 +2076,7 @@ public class ProcedureManager {
         return status;
       } else {
         return new TSStatus(TSStatusCode.CREATE_CONSUMER_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status));
       }
     } catch (Exception e) {
       return new TSStatus(TSStatusCode.CREATE_CONSUMER_ERROR.getStatusCode())
@@ -2092,7 +2093,7 @@ public class ProcedureManager {
         return status;
       } else {
         return new TSStatus(TSStatusCode.DROP_CONSUMER_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status));
       }
     } catch (Exception e) {
       return new TSStatus(TSStatusCode.DROP_CONSUMER_ERROR.getStatusCode())
@@ -2109,7 +2110,7 @@ public class ProcedureManager {
         return status;
       } else {
         return new 
TSStatus(TSStatusCode.CONSUMER_PUSH_META_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status));
       }
     } catch (Exception e) {
       return new 
TSStatus(TSStatusCode.CONSUMER_PUSH_META_ERROR.getStatusCode())
@@ -2126,7 +2127,7 @@ public class ProcedureManager {
         return status;
       } else {
         return new 
TSStatus(TSStatusCode.CONSUMER_PUSH_META_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status));
       }
     } catch (Exception e) {
       return new 
TSStatus(TSStatusCode.CONSUMER_PUSH_META_ERROR.getStatusCode())
@@ -2141,14 +2142,14 @@ public class ProcedureManager {
       TSStatus status = waitingProcedureFinished(procedure);
       if (status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
         return status;
-      } else if (PROCEDURE_TIMEOUT_MESSAGE.equals(status.getMessage())) {
+      } else if (isProcedureTimeout(status)) {
         // we assume that a timeout has occurred in the procedure related to 
the pipe in the
         // subscription procedure
         return new 
TSStatus(TSStatusCode.SUBSCRIPTION_PIPE_TIMEOUT_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status));
       } else {
         return new 
TSStatus(TSStatusCode.SUBSCRIPTION_SUBSCRIBE_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status));
       }
     } catch (Exception e) {
       return new 
TSStatus(TSStatusCode.SUBSCRIPTION_SUBSCRIBE_ERROR.getStatusCode())
@@ -2163,14 +2164,14 @@ public class ProcedureManager {
       TSStatus status = waitingProcedureFinished(procedure);
       if (status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
         return status;
-      } else if (PROCEDURE_TIMEOUT_MESSAGE.equals(status.getMessage())) {
+      } else if (isProcedureTimeout(status)) {
         // we assume that a timeout has occurred in the procedure related to 
the pipe in the
         // subscription procedure
         return new 
TSStatus(TSStatusCode.SUBSCRIPTION_PIPE_TIMEOUT_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status));
       } else {
         return new 
TSStatus(TSStatusCode.SUBSCRIPTION_UNSUBSCRIBE_ERROR.getStatusCode())
-            
.setMessage(wrapTimeoutMessageForPipeProcedure(status.getMessage()));
+            .setMessage(wrapTimeoutMessageForPipeProcedure(status));
       }
     } catch (Exception e) {
       return new 
TSStatus(TSStatusCode.SUBSCRIPTION_UNSUBSCRIBE_ERROR.getStatusCode())
@@ -2228,7 +2229,7 @@ public class ProcedureManager {
     if (!procedure.isFinished()) {
       // The procedure is still executing
       status =
-          RpcUtils.getStatus(TSStatusCode.OVERLAP_WITH_EXISTING_TASK, 
PROCEDURE_TIMEOUT_MESSAGE);
+          RpcUtils.getStatus(TSStatusCode.INTERNAL_REQUEST_TIME_OUT, 
PROCEDURE_TIMEOUT_MESSAGE);
     } else {
       if (procedure.isSuccess()) {
         if (procedure.getResult() != null) {
@@ -2258,12 +2259,25 @@ public class ProcedureManager {
     return status;
   }
 
-  private static String wrapTimeoutMessageForPipeProcedure(String message) {
-    if (message.equals(PROCEDURE_TIMEOUT_MESSAGE)) {
-      return message
-          + " Please manually check later whether the procedure is executed 
successfully.";
+  private static boolean isProcedureTimeout(final TSStatus status) {
+    return status.getCode() == 
TSStatusCode.INTERNAL_REQUEST_TIME_OUT.getStatusCode();
+  }
+
+  private static String wrapTimeoutMessageForPipeProcedure(final TSStatus 
status) {
+    if (isProcedureTimeout(status)) {
+      return String.format(
+          ManagerMessages
+              
.MESSAGE_ARG_PLEASE_MANUALLY_CHECK_LATER_WHETHER_THE_PROCEDURE_IS_EXECUTED_SUCCESSFULLY_A82B739D,
+          status.getMessage());
     }
-    return message;
+    return status.getMessage();
+  }
+
+  private static String wrapTimeoutMessageForPipeProcedure(
+      final TSStatus status, final AbstractOperatePipeProcedureV2 procedure) {
+    return isProcedureTimeout(status)
+        ? procedure.getTimeoutDiagnosticMessage()
+        : status.getMessage();
   }
 
   public static void sleepWithoutInterrupt(final long timeToSleep) {
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParser.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParser.java
index ca68dee4f7b..c33beed69c0 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParser.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParser.java
@@ -142,6 +142,11 @@ public class PipeHeartbeatParser {
       final int nodeId,
       final PipeHeartbeat pipeHeartbeat) {
     for (final PipeMeta pipeMetaFromCoordinator : 
pipeTaskInfo.get().getPipeMetaList()) {
+      if (PipeStatus.PRE_DELETE.equals(
+          pipeMetaFromCoordinator.getRuntimeMeta().getStatus().get())) {
+        continue;
+      }
+
       final PipeStaticMeta staticMeta = 
pipeMetaFromCoordinator.getStaticMeta();
       final PipeMeta pipeMetaFromAgent = pipeHeartbeat.getPipeMeta(staticMeta);
       if (pipeMetaFromAgent == null) {
@@ -292,6 +297,9 @@ public class PipeHeartbeatParser {
                         }
 
                         final PipeRuntimeMeta runtimeMeta = 
pipeMeta.getRuntimeMeta();
+                        if 
(PipeStatus.PRE_DELETE.equals(runtimeMeta.getStatus().get())) {
+                          return;
+                        }
                         if 
(!runtimeMeta.getStatus().get().equals(PipeStatus.STOPPED)) {
                           // Record the connector exception for each pipe 
affected
                           Map<Integer, PipeRuntimeException> exceptionMap =
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/pipe/PipeTaskInfo.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/pipe/PipeTaskInfo.java
index b3019444172..36b87ea5ce7 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/pipe/PipeTaskInfo.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/pipe/PipeTaskInfo.java
@@ -90,7 +90,6 @@ import java.util.stream.StreamSupport;
 
 import static 
org.apache.iotdb.commons.pipe.agent.plugin.builtin.BuiltinPipePlugin.IOTDB_THRIFT_CONNECTOR;
 import static 
org.apache.iotdb.commons.pipe.config.constant.PipeRPCMessageConstant.PIPE_ALREADY_EXIST_MSG;
-import static 
org.apache.iotdb.commons.pipe.config.constant.PipeRPCMessageConstant.PIPE_NOT_EXIST_MSG;
 
 public class PipeTaskInfo implements SnapshotProcessor {
 
@@ -227,7 +226,19 @@ public class PipeTaskInfo implements SnapshotProcessor {
     if (!isPipeExisted(alterPipeRequest.getPipeName(), 
alterPipeRequest.isTableModel)) {
       final String exceptionMessage =
           String.format(
-              "Failed to alter pipe %s, %s", alterPipeRequest.getPipeName(), 
PIPE_NOT_EXIST_MSG);
+              ConfigNodeMessages
+                  
.EXCEPTION_FAILED_TO_ALTER_PIPE_ARG_THE_PIPE_DOES_NOT_EXIST_29E0DCEB,
+              alterPipeRequest.getPipeName());
+      LOGGER.info(exceptionMessage);
+      throw new PipeException(exceptionMessage);
+    }
+    if (PipeStatus.PRE_DELETE.equals(
+        getPipeStatus(alterPipeRequest.getPipeName(), 
alterPipeRequest.isTableModel))) {
+      final String exceptionMessage =
+          String.format(
+              ConfigNodeMessages
+                  
.EXCEPTION_FAILED_TO_ALTER_PIPE_ARG_THE_PIPE_IS_BEING_DROPPED_919F1E2B,
+              alterPipeRequest.getPipeName());
       LOGGER.info(exceptionMessage);
       throw new PipeException(exceptionMessage);
     }
@@ -372,6 +383,15 @@ public class PipeTaskInfo implements SnapshotProcessor {
       LOGGER.warn(exceptionMessage);
       throw new PipeException(exceptionMessage);
     }
+    if (pipeStatus == PipeStatus.PRE_DELETE) {
+      final String exceptionMessage =
+          String.format(
+              ConfigNodeMessages
+                  
.EXCEPTION_FAILED_TO_START_PIPE_ARG_THE_PIPE_IS_BEING_DROPPED_B41F4638,
+              pipeName);
+      LOGGER.warn(exceptionMessage);
+      throw new PipeException(exceptionMessage);
+    }
   }
 
   private void checkBeforeStartPipeInternal(final String pipeName, final 
boolean isTableModel)
@@ -392,6 +412,15 @@ public class PipeTaskInfo implements SnapshotProcessor {
       LOGGER.warn(exceptionMessage);
       throw new PipeException(exceptionMessage);
     }
+    if (pipeStatus == PipeStatus.PRE_DELETE) {
+      final String exceptionMessage =
+          String.format(
+              ConfigNodeMessages
+                  
.EXCEPTION_FAILED_TO_START_PIPE_ARG_THE_PIPE_IS_BEING_DROPPED_B41F4638,
+              pipeName);
+      LOGGER.warn(exceptionMessage);
+      throw new PipeException(exceptionMessage);
+    }
   }
 
   public void checkBeforeStopPipe(final String pipeName) throws PipeException {
@@ -430,6 +459,15 @@ public class PipeTaskInfo implements SnapshotProcessor {
       LOGGER.warn(exceptionMessage);
       throw new PipeException(exceptionMessage);
     }
+    if (pipeStatus == PipeStatus.PRE_DELETE) {
+      final String exceptionMessage =
+          String.format(
+              ConfigNodeMessages
+                  
.EXCEPTION_FAILED_TO_STOP_PIPE_ARG_THE_PIPE_IS_BEING_DROPPED_37AFB22B,
+              pipeName);
+      LOGGER.warn(exceptionMessage);
+      throw new PipeException(exceptionMessage);
+    }
   }
 
   private void checkBeforeStopPipeInternal(final String pipeName, final 
boolean isTableModel)
@@ -450,6 +488,15 @@ public class PipeTaskInfo implements SnapshotProcessor {
       LOGGER.warn(exceptionMessage);
       throw new PipeException(exceptionMessage);
     }
+    if (pipeStatus == PipeStatus.PRE_DELETE) {
+      final String exceptionMessage =
+          String.format(
+              ConfigNodeMessages
+                  
.EXCEPTION_FAILED_TO_STOP_PIPE_ARG_THE_PIPE_IS_BEING_DROPPED_37AFB22B,
+              pipeName);
+      LOGGER.warn(exceptionMessage);
+      throw new PipeException(exceptionMessage);
+    }
   }
 
   public void checkBeforeDropPipe(final String pipeName) {
@@ -1125,6 +1172,10 @@ public class PipeTaskInfo implements SnapshotProcessor {
 
           final PipeRuntimeMeta runtimeMeta = pipeMeta.getRuntimeMeta();
 
+          if (PipeStatus.PRE_DELETE.equals(runtimeMeta.getStatus().get())) {
+            continue;
+          }
+
           // Keep user-stopped pipes out of the auto-restart flow. Otherwise, 
a failed STOPPED meta
           // sync can turn a manually stopped pipe into a runtime-stopped one 
and the next
           // PipeMetaSyncer round will restart it automatically.
@@ -1177,7 +1228,8 @@ public class PipeTaskInfo implements SnapshotProcessor {
         .forEach(
             pipeMeta -> {
               final PipeRuntimeMeta runtimeMeta = pipeMeta.getRuntimeMeta();
-              if (runtimeMeta.getIsStoppedByRuntimeException()) {
+              if (!PipeStatus.PRE_DELETE.equals(runtimeMeta.getStatus().get())
+                  && runtimeMeta.getIsStoppedByRuntimeException()) {
                 runtimeMeta.setExceptionsClearTime(exceptionsClearTime);
                 runtimeMeta.getStatus().set(PipeStatus.RUNNING);
 
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/env/ConfigNodeProcedureEnv.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/env/ConfigNodeProcedureEnv.java
index 76bf575681c..5c020ebf2ec 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/env/ConfigNodeProcedureEnv.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/env/ConfigNodeProcedureEnv.java
@@ -97,6 +97,7 @@ import org.slf4j.LoggerFactory;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -104,6 +105,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 public class ConfigNodeProcedureEnv {
@@ -111,6 +113,7 @@ public class ConfigNodeProcedureEnv {
   private static final Logger LOG = 
LoggerFactory.getLogger(ConfigNodeProcedureEnv.class);
 
   private static final int RUNTIME_META_PUSH_RETRY_NUM = 1;
+  private static final Consumer<Set<Integer>> NO_OP_PENDING_DATA_NODE_TRACKER 
= ignored -> {};
 
   /** Add or remove node lock. */
   private final LockQueue nodeLock = new LockQueue();
@@ -615,6 +618,11 @@ public class ConfigNodeProcedureEnv {
 
   public Map<Integer, TPushPipeMetaResp> pushAllPipeMetaToDataNodes(
       List<ByteBuffer> pipeMetaBinaryList) {
+    return pushAllPipeMetaToDataNodes(pipeMetaBinaryList, 
NO_OP_PENDING_DATA_NODE_TRACKER);
+  }
+
+  public Map<Integer, TPushPipeMetaResp> pushAllPipeMetaToDataNodes(
+      List<ByteBuffer> pipeMetaBinaryList, final Consumer<Set<Integer>> 
pendingDataNodeTracker) {
     final Map<Integer, TDataNodeLocation> dataNodeLocationMap =
         configManager.getNodeManager().getRegisteredDataNodeLocations();
     final TPushPipeMetaReq request = new 
TPushPipeMetaReq().setPipeMetas(pipeMetaBinaryList);
@@ -623,13 +631,17 @@ public class ConfigNodeProcedureEnv {
         new DataNodeAsyncRequestContext<>(
             CnToDnAsyncRequestType.PIPE_PUSH_ALL_META, request, 
dataNodeLocationMap);
     final long timeoutInMs = getRequiredPipeMetadataRequestTimeoutInMs();
-    sendRequiredMetadataRequest(clientHandler, timeoutInMs);
-    fillMissingPipePushMetaResponses(clientHandler, timeoutInMs);
-    return clientHandler.getResponseMap();
+    return sendPipeMetaRequest(clientHandler, timeoutInMs, false, 
pendingDataNodeTracker);
   }
 
   public Map<Integer, TPushPipeMetaResp> pushAllPipeMetaToDataNodesBestEffort(
       List<ByteBuffer> pipeMetaBinaryList) {
+    return pushAllPipeMetaToDataNodesBestEffort(
+        pipeMetaBinaryList, NO_OP_PENDING_DATA_NODE_TRACKER);
+  }
+
+  public Map<Integer, TPushPipeMetaResp> pushAllPipeMetaToDataNodesBestEffort(
+      List<ByteBuffer> pipeMetaBinaryList, final Consumer<Set<Integer>> 
pendingDataNodeTracker) {
     final Map<Integer, TDataNodeLocation> dataNodeLocationMap =
         configManager.getNodeManager().getRegisteredDataNodeLocations();
     final TPushPipeMetaReq request = new 
TPushPipeMetaReq().setPipeMetas(pipeMetaBinaryList);
@@ -637,12 +649,16 @@ public class ConfigNodeProcedureEnv {
     final DataNodeAsyncRequestContext<TPushPipeMetaReq, TPushPipeMetaResp> 
clientHandler =
         new DataNodeAsyncRequestContext<>(
             CnToDnAsyncRequestType.PIPE_PUSH_ALL_META, request, 
dataNodeLocationMap);
-    final long timeoutInMs = sendBestEffortRuntimeMetaRequest(clientHandler);
-    fillMissingPipePushMetaResponses(clientHandler, timeoutInMs);
-    return clientHandler.getResponseMap();
+    return sendPipeMetaRequest(
+        clientHandler, getRuntimeMetaPushTimeoutInMs(), true, 
pendingDataNodeTracker);
   }
 
   public Map<Integer, TPushPipeMetaResp> 
pushSinglePipeMetaToDataNodes(ByteBuffer pipeMetaBinary) {
+    return pushSinglePipeMetaToDataNodes(pipeMetaBinary, 
NO_OP_PENDING_DATA_NODE_TRACKER);
+  }
+
+  public Map<Integer, TPushPipeMetaResp> pushSinglePipeMetaToDataNodes(
+      ByteBuffer pipeMetaBinary, final Consumer<Set<Integer>> 
pendingDataNodeTracker) {
     final Map<Integer, TDataNodeLocation> dataNodeLocationMap =
         configManager.getNodeManager().getRegisteredDataNodeLocations();
     final TPushSinglePipeMetaReq request = new 
TPushSinglePipeMetaReq().setPipeMeta(pipeMetaBinary);
@@ -651,12 +667,15 @@ public class ConfigNodeProcedureEnv {
         new DataNodeAsyncRequestContext<>(
             CnToDnAsyncRequestType.PIPE_PUSH_SINGLE_META, request, 
dataNodeLocationMap);
     final long timeoutInMs = getRequiredPipeMetadataRequestTimeoutInMs();
-    sendRequiredMetadataRequest(clientHandler, timeoutInMs);
-    fillMissingPipePushMetaResponses(clientHandler, timeoutInMs);
-    return clientHandler.getResponseMap();
+    return sendPipeMetaRequest(clientHandler, timeoutInMs, false, 
pendingDataNodeTracker);
   }
 
   public Map<Integer, TPushPipeMetaResp> dropSinglePipeOnDataNodes(String 
pipeNameToDrop) {
+    return dropSinglePipeOnDataNodes(pipeNameToDrop, 
NO_OP_PENDING_DATA_NODE_TRACKER);
+  }
+
+  public Map<Integer, TPushPipeMetaResp> dropSinglePipeOnDataNodes(
+      String pipeNameToDrop, final Consumer<Set<Integer>> 
pendingDataNodeTracker) {
     final Map<Integer, TDataNodeLocation> dataNodeLocationMap =
         configManager.getNodeManager().getRegisteredDataNodeLocations();
     final TPushSinglePipeMetaReq request =
@@ -666,13 +685,16 @@ public class ConfigNodeProcedureEnv {
         new DataNodeAsyncRequestContext<>(
             CnToDnAsyncRequestType.PIPE_PUSH_SINGLE_META, request, 
dataNodeLocationMap);
     final long timeoutInMs = getRequiredPipeMetadataRequestTimeoutInMs();
-    sendRequiredMetadataRequest(clientHandler, timeoutInMs);
-    fillMissingPipePushMetaResponses(clientHandler, timeoutInMs);
-    return clientHandler.getResponseMap();
+    return sendPipeMetaRequest(clientHandler, timeoutInMs, false, 
pendingDataNodeTracker);
   }
 
   public Map<Integer, TPushPipeMetaResp> pushMultiPipeMetaToDataNodes(
       List<ByteBuffer> pipeMetaBinaryList) {
+    return pushMultiPipeMetaToDataNodes(pipeMetaBinaryList, 
NO_OP_PENDING_DATA_NODE_TRACKER);
+  }
+
+  public Map<Integer, TPushPipeMetaResp> pushMultiPipeMetaToDataNodes(
+      List<ByteBuffer> pipeMetaBinaryList, final Consumer<Set<Integer>> 
pendingDataNodeTracker) {
     final Map<Integer, TDataNodeLocation> dataNodeLocationMap =
         configManager.getNodeManager().getRegisteredDataNodeLocations();
     final TPushMultiPipeMetaReq request =
@@ -682,9 +704,7 @@ public class ConfigNodeProcedureEnv {
         new DataNodeAsyncRequestContext<>(
             CnToDnAsyncRequestType.PIPE_PUSH_MULTI_META, request, 
dataNodeLocationMap);
     final long timeoutInMs = getRequiredPipeMetadataRequestTimeoutInMs();
-    sendRequiredMetadataRequest(clientHandler, timeoutInMs);
-    fillMissingPipePushMetaResponses(clientHandler, timeoutInMs);
-    return clientHandler.getResponseMap();
+    return sendPipeMetaRequest(clientHandler, timeoutInMs, false, 
pendingDataNodeTracker);
   }
 
   public Map<Integer, TPushPipeMetaResp> dropMultiPipeOnDataNodes(List<String> 
pipeNamesToDrop) {
@@ -973,6 +993,22 @@ public class ConfigNodeProcedureEnv {
         && getLoadManager().getNodeStatus(dataNodeId) != NodeStatus.Removing;
   }
 
+  private static Map<Integer, TPushPipeMetaResp> sendPipeMetaRequest(
+      final DataNodeAsyncRequestContext<?, TPushPipeMetaResp> clientHandler,
+      final long timeoutInMs,
+      final boolean keepSilent,
+      final Consumer<Set<Integer>> pendingDataNodeTracker) {
+    pendingDataNodeTracker.accept(
+        
Collections.unmodifiableSet(clientHandler.getNodeLocationMap().keySet()));
+    try {
+      sendRuntimeMetaRequest(clientHandler, keepSilent, timeoutInMs);
+      fillMissingPipePushMetaResponses(clientHandler, timeoutInMs);
+      return clientHandler.getResponseMap();
+    } finally {
+      pendingDataNodeTracker.accept(Collections.emptySet());
+    }
+  }
+
   private static long sendBestEffortRuntimeMetaRequest(
       final DataNodeAsyncRequestContext<?, ?> clientHandler) {
     final long timeoutInMs = getRuntimeMetaPushTimeoutInMs();
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/StateMachineProcedure.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/StateMachineProcedure.java
index 8f3c1c93633..d0cd62e5ef0 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/StateMachineProcedure.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/StateMachineProcedure.java
@@ -132,6 +132,17 @@ public abstract class StateMachineProcedure<Env, TState> 
extends Procedure<Env>
     setNextState(getStateId(state));
   }
 
+  /**
+   * Returns whether the specified state is already present in the persisted 
state history.
+   *
+   * <p>The current state is included once it has been scheduled. This is 
useful when an append-only
+   * state is added to a procedure and the new execution path needs to coexist 
with procedures
+   * persisted by an older version.
+   */
+  protected final boolean hasReachedState(final TState state) {
+    return states.contains(getStateId(state));
+  }
+
   /**
    * Add a child procedure to execute.
    *
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 26e84e3ebe4..88cd54dbe28 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
@@ -27,6 +27,7 @@ import 
org.apache.iotdb.commons.pipe.config.constant.SystemConstant;
 import org.apache.iotdb.confignode.i18n.ProcedureMessages;
 import 
org.apache.iotdb.confignode.manager.pipe.metric.overview.PipeProcedureMetrics;
 import org.apache.iotdb.confignode.persistence.pipe.PipeTaskInfo;
+import org.apache.iotdb.confignode.procedure.Procedure;
 import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv;
 import org.apache.iotdb.confignode.procedure.exception.ProcedureException;
 import org.apache.iotdb.confignode.procedure.impl.node.AbstractNodeProcedure;
@@ -53,6 +54,8 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -103,6 +106,14 @@ public abstract class AbstractOperatePipeProcedureV2
   // recovered procedure is already re-scheduled by the procedure framework.
   private transient boolean shouldYieldAfterExecution;
 
+  // These fields are only used to report where a running Pipe procedure is 
blocked when the caller
+  // times out. They do not affect procedure execution and do not need to be 
persisted.
+  private volatile PipeProcedureExecutionStage executionStage =
+      PipeProcedureExecutionStage.WAITING_FOR_PROCEDURE_WORKER;
+  private volatile String lastExecutionExceptionMessage;
+  private volatile Procedure<?> nodeLockOwnerProcedure;
+  private volatile Set<Integer> pendingDataNodeIds = Collections.emptySet();
+
   private static final String SKIP_PIPE_PROCEDURE_MESSAGE =
       "Try to start a RUNNING pipe or stop a STOPPED pipe, do nothing.";
 
@@ -118,6 +129,7 @@ public abstract class AbstractOperatePipeProcedureV2
   @Override
   protected ProcedureLockState acquireLock(ConfigNodeProcedureEnv 
configNodeProcedureEnv) {
     LOGGER.debug(ProcedureMessages.PROCEDUREID_TRY_TO_ACQUIRE_PIPE_LOCK, 
getProcId());
+    executionStage = 
PipeProcedureExecutionStage.WAITING_FOR_PIPE_TASK_COORDINATOR_LOCK;
     pipeTaskInfo = acquireLockInternal(configNodeProcedureEnv);
     if (pipeTaskInfo == null) {
       LOGGER.warn(ProcedureMessages.PROCEDUREID_FAILED_TO_ACQUIRE_PIPE_LOCK, 
getProcId());
@@ -125,9 +137,12 @@ public abstract class AbstractOperatePipeProcedureV2
       LOGGER.debug(ProcedureMessages.PROCEDUREID_ACQUIRED_PIPE_LOCK, 
getProcId());
     }
 
+    executionStage = PipeProcedureExecutionStage.WAITING_FOR_NODE_LOCK;
     final ProcedureLockState procedureLockState = 
super.acquireLock(configNodeProcedureEnv);
     switch (procedureLockState) {
       case LOCK_ACQUIRED:
+        nodeLockOwnerProcedure = null;
+        updateExecutionStage(getCurrentState(), false);
         if (pipeTaskInfo == null) {
           LOGGER.warn(
               ProcedureMessages
@@ -141,6 +156,7 @@ public abstract class AbstractOperatePipeProcedureV2
         }
         break;
       case LOCK_EVENT_WAIT:
+        nodeLockOwnerProcedure = 
configNodeProcedureEnv.getNodeLock().getLockOwnerProcedure();
         if (pipeTaskInfo == null) {
           LOGGER.warn(
               
ProcedureMessages.PROCEDUREID_LOCK_EVENT_WAIT_WITHOUT_ACQUIRING_PIPE_LOCK,
@@ -192,6 +208,9 @@ public abstract class AbstractOperatePipeProcedureV2
             .updateTimer(this.getOperation().getName(), this.elapsedTime());
       }
       releasePipeTaskCoordinatorLock(configNodeProcedureEnv);
+      if (!isFinished()) {
+        executionStage = 
PipeProcedureExecutionStage.WAITING_FOR_PROCEDURE_WORKER;
+      }
     }
   }
 
@@ -236,6 +255,7 @@ public abstract class AbstractOperatePipeProcedureV2
   protected Flow executeFromState(ConfigNodeProcedureEnv env, 
OperatePipeTaskState state)
       throws InterruptedException {
     shouldYieldAfterExecution = false;
+    updateExecutionStage(state, false);
     if (pipeTaskInfo == null) {
       LOGGER.warn(
           
ProcedureMessages.PROCEDUREID_PIPE_LOCK_IS_NOT_ACQUIRED_EXECUTEFROMSTATE_S_EXECUTION_WILL,
@@ -247,6 +267,7 @@ public abstract class AbstractOperatePipeProcedureV2
       switch (state) {
         case VALIDATE_TASK:
           if (!executeFromValidateTask(env)) {
+            lastExecutionExceptionMessage = null;
             LOGGER.info(ProcedureMessages.PROCEDUREID, getProcId(), 
SKIP_PIPE_PROCEDURE_MESSAGE);
             // On client side, the message returned after the successful 
execution of the pipe
             // command corresponding to this procedure is "Msg: The statement 
is executed
@@ -266,13 +287,16 @@ public abstract class AbstractOperatePipeProcedureV2
           break;
         case OPERATE_ON_DATA_NODES:
           executeFromOperateOnDataNodes(env);
+          lastExecutionExceptionMessage = null;
           return Flow.NO_MORE_STATE;
         default:
           throw new UnsupportedOperationException(
               String.format(
                   
ProcedureMessages.UNKNOWN_STATE_DURING_EXECUTING_OPERATEPIPEPROCEDURE, state));
       }
+      lastExecutionExceptionMessage = null;
     } catch (Exception e) {
+      lastExecutionExceptionMessage = getExceptionMessage(e);
       // Retry before rollback
       if (getCycles() < RETRY_THRESHOLD) {
         LOGGER.warn(
@@ -319,6 +343,7 @@ public abstract class AbstractOperatePipeProcedureV2
   @Override
   protected void rollbackState(ConfigNodeProcedureEnv env, 
OperatePipeTaskState state)
       throws IOException, InterruptedException, ProcedureException {
+    updateExecutionStage(state, true);
     if (pipeTaskInfo == null) {
       LOGGER.warn(
           
ProcedureMessages.PROCEDUREID_PIPE_LOCK_IS_NOT_ACQUIRED_ROLLBACKSTATE_S_EXECUTION_WILL,
@@ -412,6 +437,167 @@ public abstract class AbstractOperatePipeProcedureV2
     return OperatePipeTaskState.VALIDATE_TASK;
   }
 
+  public final String getTimeoutDiagnosticMessage() {
+    final PipeProcedureExecutionStage currentExecutionStage = executionStage;
+    return String.format(
+        ProcedureMessages
+            
.MESSAGE_PIPE_OPERATION_ARG_TIMED_OUT_PROCEDUREID_ARG_STUCK_AT_ARG_REASON_ARG_THE_PROCEDURE_IS_STILL_RUNNING_7EEAC50E,
+        getOperation().name(),
+        getProcId(),
+        currentExecutionStage.name(),
+        getTimeoutReason(currentExecutionStage));
+  }
+
+  private String getTimeoutReason(final PipeProcedureExecutionStage 
currentExecutionStage) {
+    final String failureMessage = getFailureMessage();
+    if (currentExecutionStage.isRollback()) {
+      return getRollbackTimeoutReason(failureMessage);
+    }
+    if (isFailed() && failureMessage != null) {
+      return String.format(
+          
ProcedureMessages.MESSAGE_THE_STATE_FAILED_WITH_ARG_AND_ROLLBACK_IS_PENDING_E7B43829,
+          failureMessage);
+    }
+    if (lastExecutionExceptionMessage != null) {
+      return String.format(
+          ProcedureMessages
+              
.MESSAGE_THE_PREVIOUS_ATTEMPT_FAILED_WITH_ARG_AND_THIS_STATE_IS_BEING_RETRIED_7A541F27,
+          lastExecutionExceptionMessage);
+    }
+
+    return switch (currentExecutionStage) {
+      case WAITING_FOR_PROCEDURE_WORKER ->
+          ProcedureMessages
+              
.MESSAGE_NO_PROCEDURE_WORKER_IS_CURRENTLY_AVAILABLE_WORKERS_MAY_BE_BUSY_OR_BLOCKED_BY_OTHER_PROCEDURES_AB0B1595;
+      case WAITING_FOR_PIPE_TASK_COORDINATOR_LOCK ->
+          ProcedureMessages
+              
.MESSAGE_WAITING_TO_ACQUIRE_THE_PIPETASKCOORDINATOR_LOCK_BECAUSE_ANOTHER_PIPE_OPERATION_IS_HOLDING_IT_25A3B6B8;
+      case WAITING_FOR_NODE_LOCK -> getNodeLockTimeoutReason();
+      case VALIDATE_TASK ->
+          ProcedureMessages
+              
.MESSAGE_PIPE_REQUEST_OR_PLUGIN_VALIDATION_HAS_NOT_COMPLETED_A_PLUGIN_CHECK_OR_METADATA_ACCESS_MAY_BE_SLOW_57C36CEF;
+      case CALCULATE_INFO_FOR_TASK ->
+          ProcedureMessages
+              
.MESSAGE_PIPE_METADATA_CALCULATION_HAS_NOT_COMPLETED_METADATA_ACCESS_OR_LOCAL_CALCULATION_MAY_BE_SLOW_DEBF2504;
+      case WRITE_CONFIG_NODE_CONSENSUS ->
+          ProcedureMessages
+              
.MESSAGE_THE_CONFIGNODE_CONSENSUS_WRITE_HAS_NOT_RETURNED_RUN_SHOW_CLUSTER_TO_CHECK_NODE_STATUS_B0A6E1A7;
+      case OPERATE_ON_DATA_NODES -> getDataNodeTimeoutReason();
+      case ROLLBACK_VALIDATE_TASK,
+          ROLLBACK_CALCULATE_INFO_FOR_TASK,
+          ROLLBACK_WRITE_CONFIG_NODE_CONSENSUS,
+          ROLLBACK_OPERATE_ON_DATA_NODES ->
+          getRollbackTimeoutReason(failureMessage);
+    };
+  }
+
+  private static String getRollbackTimeoutReason(final String failureMessage) {
+    return failureMessage == null
+        ? 
ProcedureMessages.MESSAGE_ROLLING_BACK_AFTER_AN_EARLIER_FAILURE_850D0AF5
+        : String.format(
+            ProcedureMessages.MESSAGE_ROLLING_BACK_AFTER_FAILURE_ARG_474DF456, 
failureMessage);
+  }
+
+  private String getNodeLockTimeoutReason() {
+    final Procedure<?> lockOwnerProcedure = nodeLockOwnerProcedure;
+    if (lockOwnerProcedure == null) {
+      return ProcedureMessages
+          
.MESSAGE_WAITING_TO_ACQUIRE_THE_CONFIGNODE_NODE_LOCK_BECAUSE_ANOTHER_NODE_PROCEDURE_IS_HOLDING_IT_56494E86;
+    }
+    final String operation =
+        lockOwnerProcedure instanceof AbstractOperatePipeProcedureV2
+            ? ((AbstractOperatePipeProcedureV2) 
lockOwnerProcedure).getOperation().name()
+            : lockOwnerProcedure.getClass().getSimpleName();
+    return String.format(
+        ProcedureMessages
+            
.MESSAGE_WAITING_TO_ACQUIRE_THE_CONFIGNODE_NODE_LOCK_HELD_BY_ARG_PROCEDUREID_ARG_3F432041,
+        operation,
+        lockOwnerProcedure.getProcId());
+  }
+
+  private String getDataNodeTimeoutReason() {
+    final Set<Integer> currentPendingDataNodeIds = new 
TreeSet<>(pendingDataNodeIds);
+    return currentPendingDataNodeIds.isEmpty()
+        ? ProcedureMessages
+            
.MESSAGE_THE_PIPE_METADATA_PUSH_HAS_NOT_COMPLETED_RUN_SHOW_CLUSTER_TO_CHECK_DATANODE_STATUS_A8F3F0A0
+        : String.format(
+            ProcedureMessages
+                
.MESSAGE_DATANODES_ARG_HAVE_NOT_RESPONDED_TO_THE_PIPE_METADATA_PUSH_RUN_SHOW_CLUSTER_TO_CHECK_THEIR_STATUS_9C2F806F,
+            currentPendingDataNodeIds);
+  }
+
+  private String getFailureMessage() {
+    if (getException() != null) {
+      return getException().getMessage();
+    }
+    return lastExecutionExceptionMessage;
+  }
+
+  private static String getExceptionMessage(final Exception exception) {
+    return exception.getMessage() == null || exception.getMessage().isEmpty()
+        ? exception.getClass().getSimpleName()
+        : exception.getMessage();
+  }
+
+  protected final void updateExecutionStage(
+      final OperatePipeTaskState state, final boolean isRollback) {
+    if (state == null) {
+      executionStage = 
PipeProcedureExecutionStage.WAITING_FOR_PROCEDURE_WORKER;
+      return;
+    }
+    executionStage =
+        switch (state) {
+          case VALIDATE_TASK ->
+              isRollback
+                  ? PipeProcedureExecutionStage.ROLLBACK_VALIDATE_TASK
+                  : PipeProcedureExecutionStage.VALIDATE_TASK;
+          case CALCULATE_INFO_FOR_TASK ->
+              isRollback
+                  ? 
PipeProcedureExecutionStage.ROLLBACK_CALCULATE_INFO_FOR_TASK
+                  : PipeProcedureExecutionStage.CALCULATE_INFO_FOR_TASK;
+          case WRITE_CONFIG_NODE_CONSENSUS ->
+              isRollback
+                  ? 
PipeProcedureExecutionStage.ROLLBACK_WRITE_CONFIG_NODE_CONSENSUS
+                  : PipeProcedureExecutionStage.WRITE_CONFIG_NODE_CONSENSUS;
+          case OPERATE_ON_DATA_NODES ->
+              isRollback
+                  ? PipeProcedureExecutionStage.ROLLBACK_OPERATE_ON_DATA_NODES
+                  : PipeProcedureExecutionStage.OPERATE_ON_DATA_NODES;
+        };
+  }
+
+  protected final void setPendingDataNodeIds(final Set<Integer> 
pendingDataNodeIds) {
+    this.pendingDataNodeIds = pendingDataNodeIds;
+  }
+
+  private enum PipeProcedureExecutionStage {
+    WAITING_FOR_PROCEDURE_WORKER,
+    WAITING_FOR_PIPE_TASK_COORDINATOR_LOCK,
+    WAITING_FOR_NODE_LOCK,
+    VALIDATE_TASK,
+    CALCULATE_INFO_FOR_TASK,
+    WRITE_CONFIG_NODE_CONSENSUS,
+    OPERATE_ON_DATA_NODES,
+    ROLLBACK_VALIDATE_TASK(true),
+    ROLLBACK_CALCULATE_INFO_FOR_TASK(true),
+    ROLLBACK_WRITE_CONFIG_NODE_CONSENSUS(true),
+    ROLLBACK_OPERATE_ON_DATA_NODES(true);
+
+    private final boolean rollback;
+
+    PipeProcedureExecutionStage() {
+      this(false);
+    }
+
+    PipeProcedureExecutionStage(final boolean rollback) {
+      this.rollback = rollback;
+    }
+
+    private boolean isRollback() {
+      return rollback;
+    }
+  }
+
   /**
    * Pushing all the pipeMeta's to all the dataNodes, forcing an update to the 
pipe's runtime state.
    *
@@ -425,7 +611,7 @@ public abstract class AbstractOperatePipeProcedureV2
     for (final PipeMeta pipeMeta : pipeTaskInfo.get().getPipeMetaList()) {
       
pipeMetaBinaryList.add(copyAndFilterOutNonWorkingDataRegionPipeTasks(pipeMeta).serialize());
     }
-    return env.pushAllPipeMetaToDataNodes(pipeMetaBinaryList);
+    return env.pushAllPipeMetaToDataNodes(pipeMetaBinaryList, 
this::setPendingDataNodeIds);
   }
 
   /**
@@ -551,7 +737,8 @@ public abstract class AbstractOperatePipeProcedureV2
     for (final PipeMeta pipeMeta : pipeTaskInfo.get().getPipeMetaList()) {
       
pipeMetaBinaryList.add(copyAndFilterOutNonWorkingDataRegionPipeTasks(pipeMeta).serialize());
     }
-    return env.pushAllPipeMetaToDataNodesBestEffort(pipeMetaBinaryList);
+    return env.pushAllPipeMetaToDataNodesBestEffort(
+        pipeMetaBinaryList, this::setPendingDataNodeIds);
   }
 
   protected void pushPipeMetaToDataNodesBestEffort(ConfigNodeProcedureEnv env) 
{
@@ -575,7 +762,8 @@ public abstract class AbstractOperatePipeProcedureV2
     return env.pushSinglePipeMetaToDataNodes(
         copyAndFilterOutNonWorkingDataRegionPipeTasks(
                 pipeTaskInfo.get().getPipeMetaByPipeName(pipeName))
-            .serialize());
+            .serialize(),
+        this::setPendingDataNodeIds);
   }
 
   protected Map<Integer, TPushPipeMetaResp> pushSinglePipeMetaToDataNodes(
@@ -583,7 +771,8 @@ public abstract class AbstractOperatePipeProcedureV2
     return env.pushSinglePipeMetaToDataNodes(
         copyAndFilterOutNonWorkingDataRegionPipeTasks(
                 pipeTaskInfo.get().getPipeMetaByPipeName(pipeName, 
isTableModel))
-            .serialize());
+            .serialize(),
+        this::setPendingDataNodeIds);
   }
 
   protected Map<Integer, TPushPipeMetaResp> pushSinglePipeMetaToDataNodes(
@@ -591,7 +780,8 @@ public abstract class AbstractOperatePipeProcedureV2
     return env.pushSinglePipeMetaToDataNodes(
         copyAndFilterOutNonWorkingDataRegionPipeTasks(
                 pipeTaskInfo.get().getPipeMetaByPipeStaticMeta(pipeStaticMeta))
-            .serialize());
+            .serialize(),
+        this::setPendingDataNodeIds);
   }
 
   protected Map<Integer, TPushPipeMetaResp> 
pushSinglePipeMetaToDataNodes4Realtime(
@@ -609,7 +799,8 @@ public abstract class AbstractOperatePipeProcedureV2
                       SystemConstant.RESTART_OR_NEWLY_ADDED_KEY, 
Boolean.FALSE.toString())));
     }
     return env.pushSinglePipeMetaToDataNodes(
-        copyAndFilterOutNonWorkingDataRegionPipeTasks(pipeMeta).serialize());
+        copyAndFilterOutNonWorkingDataRegionPipeTasks(pipeMeta).serialize(),
+        this::setPendingDataNodeIds);
   }
 
   protected Map<Integer, TPushPipeMetaResp> 
pushSinglePipeMetaToDataNodes4Realtime(
@@ -627,7 +818,8 @@ public abstract class AbstractOperatePipeProcedureV2
                       SystemConstant.RESTART_OR_NEWLY_ADDED_KEY, 
Boolean.FALSE.toString())));
     }
     return env.pushSinglePipeMetaToDataNodes(
-        copyAndFilterOutNonWorkingDataRegionPipeTasks(pipeMeta).serialize());
+        copyAndFilterOutNonWorkingDataRegionPipeTasks(pipeMeta).serialize(),
+        this::setPendingDataNodeIds);
   }
 
   protected Map<Integer, TPushPipeMetaResp> 
pushSinglePipeMetaToDataNodes4Realtime(
@@ -645,7 +837,8 @@ public abstract class AbstractOperatePipeProcedureV2
                       SystemConstant.RESTART_OR_NEWLY_ADDED_KEY, 
Boolean.FALSE.toString())));
     }
     return env.pushSinglePipeMetaToDataNodes(
-        copyAndFilterOutNonWorkingDataRegionPipeTasks(pipeMeta).serialize());
+        copyAndFilterOutNonWorkingDataRegionPipeTasks(pipeMeta).serialize(),
+        this::setPendingDataNodeIds);
   }
 
   /**
@@ -657,7 +850,7 @@ public abstract class AbstractOperatePipeProcedureV2
    */
   protected Map<Integer, TPushPipeMetaResp> dropSinglePipeOnDataNodes(
       String pipeName, ConfigNodeProcedureEnv env) {
-    return env.dropSinglePipeOnDataNodes(pipeName);
+    return env.dropSinglePipeOnDataNodes(pipeName, 
this::setPendingDataNodeIds);
   }
 
   public static PipeMeta 
copyAndFilterOutNonWorkingDataRegionPipeTasks(PipeMeta originalPipeMeta)
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/pipe/runtime/PipeMetaSyncProcedure.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/pipe/runtime/PipeMetaSyncProcedure.java
index d371b7e9b20..f18cfb6d545 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/pipe/runtime/PipeMetaSyncProcedure.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/pipe/runtime/PipeMetaSyncProcedure.java
@@ -22,6 +22,7 @@ package 
org.apache.iotdb.confignode.procedure.impl.pipe.runtime;
 import org.apache.iotdb.common.rpc.thrift.TSStatus;
 import org.apache.iotdb.commons.consensus.index.impl.MinimumProgressIndex;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeMeta;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStatus;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTaskMeta;
 import org.apache.iotdb.commons.pipe.config.PipeConfig;
 import org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant;
@@ -125,6 +126,9 @@ public class PipeMetaSyncProcedure extends 
AbstractOperatePipeProcedureV2 {
         .getPipeMetaList()
         .forEach(
             pipeMeta -> {
+              if 
(PipeStatus.PRE_DELETE.equals(pipeMeta.getRuntimeMeta().getStatus().get())) {
+                return;
+              }
               if (!pipeMeta.getStaticMeta().isSourceExternal()) {
                 return;
               }
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/pipe/task/AlterPipeProcedureV2.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/pipe/task/AlterPipeProcedureV2.java
index ee42f4dc9f3..a0ada56ef38 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/pipe/task/AlterPipeProcedureV2.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/pipe/task/AlterPipeProcedureV2.java
@@ -333,7 +333,7 @@ public class AlterPipeProcedureV2 extends 
AbstractOperatePipeProcedureV2 {
     pipeMetaBinaryList.add(
         
copyAndFilterOutNonWorkingDataRegionPipeTasks(updatedPipeMeta).serialize());
 
-    return env.pushMultiPipeMetaToDataNodes(pipeMetaBinaryList);
+    return env.pushMultiPipeMetaToDataNodes(pipeMetaBinaryList, 
this::setPendingDataNodeIds);
   }
 
   @Override
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/pipe/task/DropPipeProcedureV2.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/pipe/task/DropPipeProcedureV2.java
index 80044cf04c8..0ae893b1261 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/pipe/task/DropPipeProcedureV2.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/pipe/task/DropPipeProcedureV2.java
@@ -23,12 +23,14 @@ import org.apache.iotdb.common.rpc.thrift.TSStatus;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeMeta;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStatus;
 import 
org.apache.iotdb.confignode.consensus.request.write.pipe.task.DropPipePlanV2;
+import 
org.apache.iotdb.confignode.consensus.request.write.pipe.task.SetPipeStatusPlanV2;
 import org.apache.iotdb.confignode.i18n.ConfigNodeMessages;
 import org.apache.iotdb.confignode.i18n.ProcedureMessages;
 import org.apache.iotdb.confignode.persistence.pipe.PipeTaskInfo;
 import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv;
 import 
org.apache.iotdb.confignode.procedure.impl.pipe.AbstractOperatePipeProcedureV2;
 import org.apache.iotdb.confignode.procedure.impl.pipe.PipeTaskOperation;
+import 
org.apache.iotdb.confignode.procedure.state.pipe.task.OperatePipeTaskState;
 import org.apache.iotdb.confignode.procedure.store.ProcedureType;
 import org.apache.iotdb.consensus.exception.ConsensusException;
 import org.apache.iotdb.pipe.api.exception.PipeException;
@@ -135,6 +137,42 @@ public class DropPipeProcedureV2 extends 
AbstractOperatePipeProcedureV2 {
     LOGGER.info(
         
ProcedureMessages.DROPPIPEPROCEDUREV2_EXECUTEFROMWRITECONFIGNODECONSENSUS, 
pipeName);
 
+    // Legacy procedures created without an explicit model do not persist 
pipeMetaToDrop. Restore
+    // it from PipeTaskInfo so a recovered procedure can still expose 
PRE_DELETE through SHOW PIPES.
+    if (!restorePipeMetaToDropIfNecessary()) {
+      return;
+    }
+
+    TSStatus response;
+    try {
+      response =
+          env.getConfigManager()
+              .getConsensusManager()
+              .write(
+                  isTableModelSet
+                      ? new SetPipeStatusPlanV2(pipeName, 
PipeStatus.PRE_DELETE, isTableModel)
+                      : new SetPipeStatusPlanV2(pipeName, 
PipeStatus.PRE_DELETE));
+    } catch (ConsensusException e) {
+      
LOGGER.warn(ConfigNodeMessages.FAILED_IN_THE_WRITE_API_EXECUTING_THE_CONSENSUS_LAYER_DUE,
 e);
+      response = new 
TSStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode());
+      response.setMessage(e.getMessage());
+    }
+    if (response.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+      throw new PipeException(response.getMessage());
+    }
+  }
+
+  boolean restorePipeMetaToDropIfNecessary() {
+    if (pipeMetaToDrop == null) {
+      pipeMetaToDrop =
+          isTableModelSet
+              ? pipeTaskInfo.get().getPipeMetaByPipeName(pipeName, 
isTableModel)
+              : pipeTaskInfo.get().getPipeMetaByPipeName(pipeName);
+    }
+    return pipeMetaToDrop != null;
+  }
+
+  private void dropPipeOnConfigNode(final ConfigNodeProcedureEnv env) throws 
PipeException {
     TSStatus response;
     try {
       response =
@@ -155,7 +193,7 @@ public class DropPipeProcedureV2 extends 
AbstractOperatePipeProcedureV2 {
   }
 
   @Override
-  public void executeFromOperateOnDataNodes(ConfigNodeProcedureEnv env) {
+  public void executeFromOperateOnDataNodes(ConfigNodeProcedureEnv env) throws 
PipeException {
     
LOGGER.info(ProcedureMessages.DROPPIPEPROCEDUREV2_EXECUTEFROMOPERATEONDATANODES,
 pipeName);
 
     String exceptionMessage;
@@ -169,17 +207,21 @@ public class DropPipeProcedureV2 extends 
AbstractOperatePipeProcedureV2 {
         droppedPipeMeta.getRuntimeMeta().getStatus().set(PipeStatus.DROPPED);
         exceptionMessage =
             parsePushPipeMetaExceptionForPipe(
-                pipeName, 
env.pushSinglePipeMetaToDataNodes(droppedPipeMeta.serialize()));
+                pipeName,
+                env.pushSinglePipeMetaToDataNodes(
+                    droppedPipeMeta.serialize(), this::setPendingDataNodeIds));
       }
     } catch (final IOException e) {
       exceptionMessage = e.getMessage();
     }
-    if (!exceptionMessage.isEmpty()) {
+    if (exceptionMessage != null && !exceptionMessage.isEmpty()) {
       LOGGER.warn(
           
ProcedureMessages.FAILED_TO_DROP_PIPE_DETAILS_METADATA_WILL_BE_SYNCHRONIZED_LATER,
           pipeName,
           exceptionMessage);
     }
+    updateExecutionStage(OperatePipeTaskState.WRITE_CONFIG_NODE_CONSENSUS, 
false);
+    dropPipeOnConfigNode(env);
   }
 
   @Override
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/LockQueue.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/LockQueue.java
index e2f5935a909..57b0817940e 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/LockQueue.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/scheduler/LockQueue.java
@@ -27,7 +27,7 @@ import java.util.ArrayDeque;
 public class LockQueue {
   private final ArrayDeque<Procedure<?>> deque = new ArrayDeque<>();
 
-  private Procedure<?> lockOwnerProcedure = null;
+  private volatile Procedure<?> lockOwnerProcedure = null;
 
   public boolean tryLock(Procedure<?> procedure) {
     if (lockOwnerProcedure == null) {
@@ -45,6 +45,10 @@ public class LockQueue {
     return true;
   }
 
+  public Procedure<?> getLockOwnerProcedure() {
+    return lockOwnerProcedure;
+  }
+
   public void waitProcedure(Procedure<?> procedure, ProcedureScheduler 
procedureScheduler) {
     if (lockOwnerProcedure == null) {
       procedureScheduler.addFront(procedure);
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanSerDeTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanSerDeTest.java
index e844dcf6910..35537a2cea2 100644
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanSerDeTest.java
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanSerDeTest.java
@@ -1014,7 +1014,7 @@ public class ConfigPhysicalPlanSerDeTest {
   public void SetPipeStatusPlanV2Test() throws IOException {
     final SetPipeStatusPlanV2 setPipeStatusPlanV2 =
         new SetPipeStatusPlanV2(
-            "pipe", 
org.apache.iotdb.commons.pipe.agent.task.meta.PipeStatus.RUNNING, true);
+            "pipe", 
org.apache.iotdb.commons.pipe.agent.task.meta.PipeStatus.PRE_DELETE, true);
     final SetPipeStatusPlanV2 setPipeStatusPlanV21 =
         (SetPipeStatusPlanV2)
             
ConfigPhysicalPlan.Factory.create(setPipeStatusPlanV2.serializeToByteBuffer());
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/response/pipe/PipeTableRespTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/response/pipe/PipeTableRespTest.java
index 093e5989bb0..f4437fd2c34 100644
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/response/pipe/PipeTableRespTest.java
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/consensus/response/pipe/PipeTableRespTest.java
@@ -23,6 +23,7 @@ import 
org.apache.iotdb.commons.consensus.index.impl.MinimumProgressIndex;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeMeta;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeRuntimeMeta;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStaticMeta;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStatus;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTaskMeta;
 import 
org.apache.iotdb.commons.pipe.agent.task.meta.PipeTemporaryMetaInCoordinator;
 import org.apache.iotdb.commons.pipe.config.constant.SystemConstant;
@@ -168,6 +169,17 @@ public class PipeTableRespTest {
     Assert.assertFalse(showPipeResult.get(2).isSetIsDegraded());
   }
 
+  @Test
+  public void testConvertToTShowPipeRespIncludesPreDeleteStatus() {
+    final PipeTableResp pipeTableResp = constructPipeTableResp();
+    
pipeTableResp.getAllPipeMeta().get(0).getRuntimeMeta().getStatus().set(PipeStatus.PRE_DELETE);
+
+    final List<TShowPipeInfo> showPipeResult =
+        pipeTableResp.convertToTShowPipeResp().getPipeInfoList();
+
+    Assert.assertEquals(PipeStatus.PRE_DELETE.name(), 
showPipeResult.get(0).getState());
+  }
+
   @Test
   public void testFilterByModelBeforeWhereClause() {
     TSStatus status = new 
TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode());
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParserTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParserTest.java
index d77992c18be..04a898629fd 100644
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParserTest.java
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/coordinator/runtime/heartbeat/PipeHeartbeatParserTest.java
@@ -214,6 +214,42 @@ public class PipeHeartbeatParserTest {
     verify(context.procedureManager, times(1)).pipeHandleMetaChange(true, 
false);
   }
 
+  @Test
+  public void testParseHeartbeatDoesNotOverwritePreDeleteStatus() throws 
Exception {
+    
CommonDescriptor.getInstance().getConfig().setSeperatedPipeHeartbeatEnabled(false);
+
+    final String pipeName = "preDeletePipe";
+    final PipeTaskInfo pipeTaskInfo = new PipeTaskInfo();
+    createPipe(pipeTaskInfo, pipeName, PipeStatus.RUNNING);
+
+    final PipeMeta pipeMeta = pipeTaskInfo.getPipeMetaByPipeName(pipeName);
+    final PipeRuntimeMeta runtimeMeta = pipeMeta.getRuntimeMeta();
+    runtimeMeta.getStatus().set(PipeStatus.PRE_DELETE);
+
+    final PipeTaskMeta agentTaskMeta =
+        new PipeTaskMeta(MinimumProgressIndex.INSTANCE, DATA_NODE_ID);
+    agentTaskMeta.trackExceptionMessage(new 
PipeRuntimeCriticalException("fresh failure", 300L));
+    final ConcurrentMap<Integer, PipeTaskMeta> agentPipeTasks = new 
ConcurrentHashMap<>();
+    agentPipeTasks.put(DATA_NODE_ID, agentTaskMeta);
+    final PipeHeartbeat heartbeat =
+        new PipeHeartbeat(
+            Collections.singletonList(
+                new PipeMeta(pipeMeta.getStaticMeta(), new 
PipeRuntimeMeta(agentPipeTasks))
+                    .serialize()),
+            Collections.singletonList(false),
+            Collections.singletonList(0L),
+            Collections.singletonList(0D),
+            null);
+
+    final ParserTestContext context = createParserTestContext(1, pipeTaskInfo);
+    context.parser.parseHeartbeat(DATA_NODE_ID, heartbeat);
+
+    Assert.assertEquals(PipeStatus.PRE_DELETE, runtimeMeta.getStatus().get());
+    Assert.assertFalse(
+        
runtimeMeta.getConsensusGroupId2TaskMetaMap().get(DATA_NODE_ID).hasExceptionMessages());
+    verify(context.procedureManager, 
never()).pipeHandleMetaChange(anyBoolean(), anyBoolean());
+  }
+
   @Test
   public void testParseHeartbeatRecordsPipeDegradedStatus() throws Exception {
     
CommonDescriptor.getInstance().getConfig().setSeperatedPipeHeartbeatEnabled(false);
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/pipe/PipeTaskInfoAutoRestartTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/pipe/PipeTaskInfoAutoRestartTest.java
index f938bdbd85f..fd9540aa7f6 100644
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/pipe/PipeTaskInfoAutoRestartTest.java
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/pipe/PipeTaskInfoAutoRestartTest.java
@@ -33,9 +33,11 @@ import 
org.apache.iotdb.commons.pipe.config.constant.SystemConstant;
 import 
org.apache.iotdb.confignode.consensus.request.write.pipe.task.CreatePipePlanV2;
 import 
org.apache.iotdb.confignode.consensus.request.write.pipe.task.DropPipePlanV2;
 import 
org.apache.iotdb.confignode.consensus.request.write.pipe.task.SetPipeStatusPlanV2;
+import org.apache.iotdb.confignode.i18n.ConfigNodeMessages;
 import org.apache.iotdb.confignode.rpc.thrift.TAlterPipeReq;
 import org.apache.iotdb.mpp.rpc.thrift.TPushPipeMetaResp;
 import org.apache.iotdb.mpp.rpc.thrift.TPushPipeMetaRespExceptionMessage;
+import org.apache.iotdb.pipe.api.exception.PipeException;
 import org.apache.iotdb.rpc.TSStatusCode;
 
 import org.junit.Assert;
@@ -352,6 +354,45 @@ public class PipeTaskInfoAutoRestartTest {
         extractorAttributes.get(SystemConstant.PIPE_VISIBILITY_KEY));
   }
 
+  @Test
+  public void testPreDeletePipeReportsDroppingInsteadOfNotExist() {
+    final String pipeName = "droppingPipe";
+    createPipe(pipeName, PipeStatus.STOPPED, true);
+    pipeTaskInfo.setPipeStatus(new SetPipeStatusPlanV2(pipeName, 
PipeStatus.PRE_DELETE, true));
+
+    final PipeException alterException =
+        Assert.assertThrows(
+            PipeException.class,
+            () ->
+                pipeTaskInfo.checkAndUpdateRequestBeforeAlterPipe(
+                    createAlterPipeRequest(pipeName, true)));
+    Assert.assertEquals(
+        String.format(
+            ConfigNodeMessages
+                
.EXCEPTION_FAILED_TO_ALTER_PIPE_ARG_THE_PIPE_IS_BEING_DROPPED_919F1E2B,
+            pipeName),
+        alterException.getMessage());
+
+    final PipeException startException =
+        Assert.assertThrows(
+            PipeException.class, () -> 
pipeTaskInfo.checkBeforeStartPipe(pipeName, true));
+    Assert.assertEquals(
+        String.format(
+            ConfigNodeMessages
+                
.EXCEPTION_FAILED_TO_START_PIPE_ARG_THE_PIPE_IS_BEING_DROPPED_B41F4638,
+            pipeName),
+        startException.getMessage());
+
+    final PipeException stopException =
+        Assert.assertThrows(
+            PipeException.class, () -> 
pipeTaskInfo.checkBeforeStopPipe(pipeName, true));
+    Assert.assertEquals(
+        String.format(
+            
ConfigNodeMessages.EXCEPTION_FAILED_TO_STOP_PIPE_ARG_THE_PIPE_IS_BEING_DROPPED_37AFB22B,
+            pipeName),
+        stopException.getMessage());
+  }
+
   private TAlterPipeReq createAlterPipeRequest(final String pipeName, final 
boolean isTableModel) {
     final TAlterPipeReq alterPipeRequest =
         new TAlterPipeReq(pipeName, new HashMap<>(), new HashMap<>(), false, 
false);
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
index 5993e73ec42..ae0feec0f72 100644
--- 
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
@@ -29,6 +29,7 @@ import org.junit.Assert;
 import org.junit.Test;
 
 import java.io.IOException;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 
 public class AbstractOperatePipeProcedureV2Test {
@@ -89,6 +90,32 @@ public class AbstractOperatePipeProcedureV2Test {
     Assert.assertEquals(2, procedure.calculateExecutionCount);
   }
 
+  @Test
+  public void testTimeoutDiagnosticReportsCurrentStateAndRetryReason() throws 
Exception {
+    final TestOperatePipeProcedure procedure = new TestOperatePipeProcedure();
+    procedure.failValidation = true;
+
+    procedure.executeFromState(null, OperatePipeTaskState.VALIDATE_TASK);
+
+    final String diagnosticMessage = procedure.getTimeoutDiagnosticMessage();
+    Assert.assertTrue(diagnosticMessage.contains("START_PIPE"));
+    Assert.assertTrue(diagnosticMessage.contains("VALIDATE_TASK"));
+    Assert.assertTrue(diagnosticMessage.contains("retry"));
+  }
+
+  @Test
+  public void testTimeoutDiagnosticReportsDataNodeOperation() throws Exception 
{
+    final TestOperatePipeProcedure procedure = new TestOperatePipeProcedure();
+    procedure.setPendingDataNodeIdsForTest(Set.of(3, 1));
+
+    procedure.executeFromState(null, 
OperatePipeTaskState.OPERATE_ON_DATA_NODES);
+
+    final String diagnosticMessage = procedure.getTimeoutDiagnosticMessage();
+    Assert.assertTrue(diagnosticMessage.contains("OPERATE_ON_DATA_NODES"));
+    Assert.assertTrue(diagnosticMessage.contains("DataNodes [1, 3]"));
+    Assert.assertTrue(diagnosticMessage.contains("SHOW CLUSTER"));
+  }
+
   private static class TestOperatePipeProcedure extends 
AbstractOperatePipeProcedureV2 {
 
     private int validateExecutionCount;
@@ -104,6 +131,10 @@ public class AbstractOperatePipeProcedureV2Test {
       return execute(null);
     }
 
+    private void setPendingDataNodeIdsForTest(final Set<Integer> 
pendingDataNodeIds) {
+      setPendingDataNodeIds(pendingDataNodeIds);
+    }
+
     @Override
     protected PipeTaskOperation getOperation() {
       return PipeTaskOperation.START_PIPE;
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/pipe/task/DropPipeProcedureV2Test.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/pipe/task/DropPipeProcedureV2Test.java
index 1317b838830..7e637f277fe 100644
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/pipe/task/DropPipeProcedureV2Test.java
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/pipe/task/DropPipeProcedureV2Test.java
@@ -19,19 +19,60 @@
 
 package org.apache.iotdb.confignode.procedure.impl.pipe.task;
 
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeRuntimeMeta;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStaticMeta;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStatus;
+import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlan;
+import 
org.apache.iotdb.confignode.consensus.request.write.pipe.task.CreatePipePlanV2;
+import 
org.apache.iotdb.confignode.consensus.request.write.pipe.task.DropPipePlanV2;
+import 
org.apache.iotdb.confignode.consensus.request.write.pipe.task.SetPipeStatusPlanV2;
+import org.apache.iotdb.confignode.manager.ConfigManager;
+import org.apache.iotdb.confignode.manager.consensus.ConsensusManager;
+import org.apache.iotdb.confignode.persistence.pipe.PipeTaskInfo;
+import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv;
 import org.apache.iotdb.confignode.procedure.store.ProcedureFactory;
+import org.apache.iotdb.pipe.api.exception.PipeException;
+import org.apache.iotdb.rpc.TSStatusCode;
 
 import org.apache.tsfile.utils.PublicBAOS;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
 
 import java.io.DataOutputStream;
 import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicReference;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 public class DropPipeProcedureV2Test {
+
+  private static class TestDropPipeProcedureV2 extends DropPipeProcedureV2 {
+
+    private TestDropPipeProcedureV2() {
+      super();
+    }
+
+    private TestDropPipeProcedureV2(final String pipeName) throws 
PipeException {
+      super(pipeName);
+    }
+
+    private TestDropPipeProcedureV2(final String pipeName, final boolean 
isTableModel)
+        throws PipeException {
+      super(pipeName, isTableModel);
+    }
+
+    private void setPipeTaskInfo(final PipeTaskInfo pipeTaskInfo) {
+      this.pipeTaskInfo = new AtomicReference<>(pipeTaskInfo);
+    }
+  }
+
   @Test
   public void serializeDeserializeTest() {
     PublicBAOS byteArrayOutputStream = new PublicBAOS();
@@ -72,4 +113,117 @@ public class DropPipeProcedureV2Test {
       fail();
     }
   }
+
+  @Test
+  public void testWriteConsensusMarksPreDeleteBeforeFinalDrop() throws 
Exception {
+    final String pipeName = "testPipe";
+    final PipeTaskInfo pipeTaskInfo = createPipeTaskInfo(pipeName);
+    final TestDropPipeProcedureV2 proc = new TestDropPipeProcedureV2(pipeName, 
false);
+    proc.setPipeTaskInfo(pipeTaskInfo);
+    
proc.executeFromCalculateInfoForTask(Mockito.mock(ConfigNodeProcedureEnv.class));
+
+    final ConfigNodeProcedureEnv env = 
Mockito.mock(ConfigNodeProcedureEnv.class);
+    final ConfigManager configManager = Mockito.mock(ConfigManager.class);
+    final ConsensusManager consensusManager = 
Mockito.mock(ConsensusManager.class);
+    Mockito.when(env.getConfigManager()).thenReturn(configManager);
+    
Mockito.when(configManager.getConsensusManager()).thenReturn(consensusManager);
+    Mockito.when(consensusManager.write(Mockito.any(ConfigPhysicalPlan.class)))
+        .thenReturn(new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode()));
+
+    proc.executeFromWriteConfigNodeConsensus(env);
+
+    final ArgumentCaptor<ConfigPhysicalPlan> planCaptor =
+        ArgumentCaptor.forClass(ConfigPhysicalPlan.class);
+    Mockito.verify(consensusManager).write(planCaptor.capture());
+    assertEquals(
+        new SetPipeStatusPlanV2(pipeName, PipeStatus.PRE_DELETE, false), 
planCaptor.getValue());
+  }
+
+  @Test
+  public void testDataNodeStageCommitsFinalDrop() throws Exception {
+    final String pipeName = "testPipe";
+    final PipeTaskInfo pipeTaskInfo = createPipeTaskInfo(pipeName);
+    final TestDropPipeProcedureV2 proc = new TestDropPipeProcedureV2(pipeName, 
false);
+    proc.setPipeTaskInfo(pipeTaskInfo);
+    
proc.executeFromCalculateInfoForTask(Mockito.mock(ConfigNodeProcedureEnv.class));
+
+    final ConfigNodeProcedureEnv env = 
Mockito.mock(ConfigNodeProcedureEnv.class);
+    final ConfigManager configManager = Mockito.mock(ConfigManager.class);
+    final ConsensusManager consensusManager = 
Mockito.mock(ConsensusManager.class);
+    Mockito.when(env.getConfigManager()).thenReturn(configManager);
+    
Mockito.when(configManager.getConsensusManager()).thenReturn(consensusManager);
+    
Mockito.when(env.pushSinglePipeMetaToDataNodes(Mockito.any(ByteBuffer.class), 
Mockito.any()))
+        .thenReturn(Collections.emptyMap());
+    Mockito.when(consensusManager.write(Mockito.any(ConfigPhysicalPlan.class)))
+        .thenReturn(new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode()));
+
+    proc.executeFromOperateOnDataNodes(env);
+
+    final ArgumentCaptor<ConfigPhysicalPlan> planCaptor =
+        ArgumentCaptor.forClass(ConfigPhysicalPlan.class);
+    Mockito.verify(consensusManager).write(planCaptor.capture());
+    assertEquals(new DropPipePlanV2(pipeName, false), planCaptor.getValue());
+  }
+
+  @Test
+  public void testRecoveredLegacyProcedureRestoresPipeMetaBeforePreDelete() 
throws Exception {
+    final String pipeName = "testPipe";
+    final PipeTaskInfo pipeTaskInfo = createPipeTaskInfo(pipeName);
+    final TestDropPipeProcedureV2 proc = new TestDropPipeProcedureV2(pipeName);
+    proc.setPipeTaskInfo(pipeTaskInfo);
+    
proc.executeFromCalculateInfoForTask(Mockito.mock(ConfigNodeProcedureEnv.class));
+
+    final PublicBAOS byteArrayOutputStream = new PublicBAOS();
+    proc.serialize(new DataOutputStream(byteArrayOutputStream));
+    final ByteBuffer byteBuffer =
+        ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0, 
byteArrayOutputStream.size());
+    byteBuffer.getShort();
+    final TestDropPipeProcedureV2 recoveredProc = new 
TestDropPipeProcedureV2();
+    recoveredProc.deserialize(byteBuffer);
+    recoveredProc.setPipeTaskInfo(pipeTaskInfo);
+
+    assertFalse(recoveredProc.isTableModelSet());
+    assertNull(recoveredProc.getPipeMetaToDrop());
+    assertTrue(recoveredProc.restorePipeMetaToDropIfNecessary());
+    assertEquals(pipeTaskInfo.getPipeMetaByPipeName(pipeName), 
recoveredProc.getPipeMetaToDrop());
+  }
+
+  @Test
+  public void testPreDeletePipeIsNotAutoRestarted() {
+    final String pipeName = "testPipe";
+    final PipeTaskInfo pipeTaskInfo = createPipeTaskInfo(pipeName);
+    pipeTaskInfo
+        .getPipeMetaByPipeName(pipeName)
+        .getRuntimeMeta()
+        .getStatus()
+        .set(PipeStatus.PRE_DELETE);
+    pipeTaskInfo
+        .getPipeMetaByPipeName(pipeName)
+        .getRuntimeMeta()
+        .setIsStoppedByRuntimeException(true);
+
+    assertFalse(pipeTaskInfo.autoRestart());
+    assertEquals(
+        PipeStatus.PRE_DELETE,
+        
pipeTaskInfo.getPipeMetaByPipeName(pipeName).getRuntimeMeta().getStatus().get());
+    assertTrue(
+        pipeTaskInfo
+            .getPipeMetaByPipeName(pipeName)
+            .getRuntimeMeta()
+            .getIsStoppedByRuntimeException());
+  }
+
+  private PipeTaskInfo createPipeTaskInfo(final String pipeName) {
+    final PipeTaskInfo pipeTaskInfo = new PipeTaskInfo();
+    pipeTaskInfo.createPipe(
+        new CreatePipePlanV2(
+            new PipeStaticMeta(
+                pipeName,
+                System.currentTimeMillis(),
+                Collections.emptyMap(),
+                Collections.emptyMap(),
+                Collections.emptyMap()),
+            new PipeRuntimeMeta()));
+    return pipeTaskInfo;
+  }
 }
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 ee465ada69b..f2f912cbc61 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
@@ -220,6 +220,12 @@ public abstract class PipeTaskAgent {
       return;
     }
 
+    // PRE_DELETE is a coordinator-only marker. The drop procedure will push 
DROPPED explicitly
+    // after the marker is persisted, so task agents should retain their 
current runtime state here.
+    if (metaFromCoordinator.getRuntimeMeta().getStatus().get() == 
PipeStatus.PRE_DELETE) {
+      return;
+    }
+
     if (metaFromCoordinator.getRuntimeMeta().getStatus().get() == 
PipeStatus.DROPPED) {
       dropPipe(pipeName, creationTime);
       return;
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeStatus.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeStatus.java
index 0feca59bc85..cdd3a491fa4 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeStatus.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/agent/task/meta/PipeStatus.java
@@ -25,6 +25,7 @@ public enum PipeStatus {
   RUNNING((byte) 0),
   STOPPED((byte) 1),
   DROPPED((byte) 2),
+  PRE_DELETE((byte) 3),
   ;
 
   private final byte type;
@@ -45,6 +46,8 @@ public enum PipeStatus {
         return PipeStatus.STOPPED;
       case 2:
         return PipeStatus.DROPPED;
+      case 3:
+        return PipeStatus.PRE_DELETE;
       default:
         throw new IllegalArgumentException(SchemaMessages.SCHEMA_INVALID_INPUT 
+ type);
     }
diff --git 
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/task/PipeMetaDeSerTest.java
 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/task/PipeMetaDeSerTest.java
index 3b30ec62a7f..eee842621bb 100644
--- 
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/task/PipeMetaDeSerTest.java
+++ 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/task/PipeMetaDeSerTest.java
@@ -50,6 +50,14 @@ import java.util.concurrent.ConcurrentHashMap;
 
 public class PipeMetaDeSerTest {
 
+  @Test
+  public void testPipeStatusTypeCompatibility() {
+    Assert.assertEquals((byte) 0, PipeStatus.RUNNING.getType());
+    Assert.assertEquals((byte) 1, PipeStatus.STOPPED.getType());
+    Assert.assertEquals((byte) 2, PipeStatus.DROPPED.getType());
+    Assert.assertEquals((byte) 3, PipeStatus.PRE_DELETE.getType());
+  }
+
   @Test
   public void test() throws IOException {
     final PipeStaticMeta pipeStaticMeta =
@@ -157,6 +165,11 @@ public class PipeMetaDeSerTest {
     pipeRuntimeMeta1 = PipeRuntimeMeta.deserialize(runtimeByteBuffer);
     Assert.assertEquals(pipeRuntimeMeta, pipeRuntimeMeta1);
 
+    pipeRuntimeMeta.getStatus().set(PipeStatus.PRE_DELETE);
+    runtimeByteBuffer = pipeRuntimeMeta.serialize();
+    pipeRuntimeMeta1 = PipeRuntimeMeta.deserialize(runtimeByteBuffer);
+    Assert.assertEquals(pipeRuntimeMeta, pipeRuntimeMeta1);
+
     final PipeMeta pipeMeta = new PipeMeta(pipeStaticMeta, pipeRuntimeMeta);
     final ByteBuffer byteBuffer = pipeMeta.serialize();
     final PipeMeta pipeMeta1 = PipeMeta.deserialize4Coordinator(byteBuffer);

Reply via email to