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 5d3e2eb121e Preserve unlisted pipes in partial metadata updates 
(#19043)
5d3e2eb121e is described below

commit 5d3e2eb121ed327ca65d89036a7c9c45d4bf8f79
Author: Caideyipi <[email protected]>
AuthorDate: Fri Oct 30 09:49:35 2026 +0800

    Preserve unlisted pipes in partial metadata updates (#19043)
    
    * Fix partial pipe metadata reconciliation
    
    * Fix stale partial pipe metadata reconciliation
---
 .../db/pipe/agent/task/PipeDataNodeTaskAgent.java  |  43 +++++++
 .../impl/DataNodeInternalRPCServiceImpl.java       |   2 +-
 .../pipe/agent/task/PipeDataNodeTaskAgentTest.java | 141 +++++++++++++++++++++
 3 files changed, 185 insertions(+), 1 deletion(-)

diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgent.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgent.java
index 5b7775c2315..82e8e2da608 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgent.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgent.java
@@ -200,6 +200,49 @@ public class PipeDataNodeTaskAgent extends PipeTaskAgent {
         .put(consensusGroupId, pipeTaskMeta);
   }
 
+  /** Applies a partial metadata batch without dropping pipes omitted from the 
request. */
+  public List<TPushPipeMetaRespExceptionMessage> handleMultiPipeMetaChanges(
+      final List<PipeMeta> pipeMetaListFromCoordinator) {
+    if (pipeMetaListFromCoordinator.isEmpty()) {
+      return Collections.emptyList();
+    }
+    if (!tryWriteLockWithTimeOutInMs(
+        
CommonDescriptor.getInstance().getConfig().getDnConnectionTimeoutInMS() * 2L / 
3)) {
+      return null;
+    }
+    try {
+      // Reconciliation and alter-progress reuse need one complete view under 
the same write lock.
+      // Resolve replacements using the keeper's name/visibility identity, 
including new versions
+      // of existing pipes. Preserve the incoming old-DROPPED/new pair used by 
alter procedures.
+      final Set<PipeStaticMeta> replacedLocalMetas =
+          pipeMetaListFromCoordinator.stream()
+              .map(
+                  meta -> {
+                    final PipeStaticMeta staticMeta = meta.getStaticMeta();
+                    final PipeStatus status = 
meta.getRuntimeMeta().getStatus().get();
+                    if (status == PipeStatus.DROPPED) {
+                      return pipeMetaKeeper.getPipeMeta(
+                          staticMeta.getPipeName(), 
staticMeta.getCreationTime());
+                    }
+                    return status == PipeStatus.PRE_DELETE
+                        ? null
+                        : pipeMetaKeeper.getPipeMeta(staticMeta);
+                  })
+              .filter(Objects::nonNull)
+              .map(PipeMeta::getStaticMeta)
+              .collect(Collectors.toSet());
+      final List<PipeMeta> fullPipeMetaList = new 
ArrayList<>(pipeMetaListFromCoordinator);
+      for (final PipeMeta localMeta : pipeMetaKeeper.getPipeMetaList()) {
+        if (!replacedLocalMetas.contains(localMeta.getStaticMeta())) {
+          fullPipeMetaList.add(localMeta);
+        }
+      }
+      return handlePipeMetaChangesInternal(fullPipeMetaList);
+    } finally {
+      releaseWriteLock();
+    }
+  }
+
   @Override
   protected TPushPipeMetaRespExceptionMessage 
handleSinglePipeMetaChangesInternal(
       final PipeMeta pipeMetaFromCoordinator) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
index 1f5f95b4460..509d2904aa6 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
@@ -1451,7 +1451,7 @@ public class DataNodeInternalRPCServiceImpl implements 
IDataNodeRPCService.Iface
               final List<TPushPipeMetaRespExceptionMessage> exceptionMessages) 
{
             final List<TPushPipeMetaRespExceptionMessage> 
exceptionMessagesFromAgent =
                 PipeDataNodeAgent.task()
-                    .handlePipeMetaChanges(
+                    .handleMultiPipeMetaChanges(
                         pipeMetas.stream()
                             .map(PipeMeta::deserialize4TaskAgent)
                             .collect(Collectors.toList()));
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgentTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgentTest.java
index 83709d41c3f..fe1bfbe4aab 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgentTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/PipeDataNodeTaskAgentTest.java
@@ -42,7 +42,9 @@ import org.awaitility.Awaitility;
 import org.junit.Assert;
 import org.junit.Test;
 
+import java.io.IOException;
 import java.lang.reflect.Field;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -55,6 +57,145 @@ public class PipeDataNodeTaskAgentTest {
   private static final int LOCAL_NODE_ID = 1;
   private static final int REGION_ID = 7;
 
+  @Test
+  public void testEmptyPartialMetadataKeepsExistingPipe() throws Exception {
+    final PipeDataNodeTaskAgent agent = newActiveTaskAgent();
+    final PipeMetaKeeper keeper = getField(agent, "pipeMetaKeeper");
+    final PipeMeta existing = newEmptyPipeMeta("existing", 1L);
+    addEmptyPipe(agent, existing);
+
+    Assert.assertEquals(
+        Collections.emptyList(), 
agent.handleMultiPipeMetaChanges(Collections.emptyList()));
+
+    Assert.assertSame(existing, keeper.getPipeMeta(existing.getStaticMeta()));
+  }
+
+  @Test
+  public void testPartialMetadataUpdatesOnlyRequestedPipe() throws Exception {
+    final PipeDataNodeTaskAgent agent = newActiveTaskAgent();
+    final PipeMetaKeeper keeper = getField(agent, "pipeMetaKeeper");
+    final PipeMeta existing = newEmptyPipeMeta("existing", 1L);
+    final PipeMeta unrelated = newEmptyPipeMeta("unrelated", 2L);
+    addEmptyPipe(agent, existing);
+    addEmptyPipe(agent, unrelated);
+    final PipeMeta updated = newEmptyPipeMeta("existing", 1L);
+    updated.getRuntimeMeta().getStatus().set(PipeStatus.RUNNING);
+
+    Assert.assertEquals(
+        Collections.emptyList(),
+        agent.handleMultiPipeMetaChanges(Collections.singletonList(updated)));
+
+    Assert.assertEquals(PipeStatus.RUNNING, 
existing.getRuntimeMeta().getStatus().get());
+    Assert.assertSame(unrelated, 
keeper.getPipeMeta(unrelated.getStaticMeta()));
+    Assert.assertEquals(PipeStatus.STOPPED, 
unrelated.getRuntimeMeta().getStatus().get());
+  }
+
+  @Test
+  public void testPartialDropKeepsUnrelatedPipeAndFullSnapshotStillRemovesIt() 
throws Exception {
+    final PipeDataNodeTaskAgent agent = newActiveTaskAgent();
+    final PipeMetaKeeper keeper = getField(agent, "pipeMetaKeeper");
+    final PipeMeta existing = newEmptyPipeMeta("existing", 1L);
+    final PipeMeta unrelated = newEmptyPipeMeta("unrelated", 2L);
+    addEmptyPipe(agent, existing);
+    addEmptyPipe(agent, unrelated);
+    final PipeMeta dropped = newEmptyPipeMeta("existing", 1L);
+    dropped.getRuntimeMeta().getStatus().set(PipeStatus.DROPPED);
+
+    Assert.assertEquals(
+        Collections.emptyList(),
+        agent.handleMultiPipeMetaChanges(Collections.singletonList(dropped)));
+
+    Assert.assertNull(keeper.getPipeMeta(existing.getStaticMeta()));
+    Assert.assertSame(unrelated, 
keeper.getPipeMeta(unrelated.getStaticMeta()));
+    
Assert.assertTrue(agent.handlePipeMetaChanges(Collections.emptyList()).isEmpty());
+    Assert.assertTrue(keeper.isEmpty());
+  }
+
+  @Test
+  public void testPartialStaleDropDoesNotRemoveNewerPipe() throws Exception {
+    final PipeDataNodeTaskAgent agent = newActiveTaskAgent();
+    final PipeMetaKeeper keeper = getField(agent, "pipeMetaKeeper");
+    final PipeMeta current = newEmptyPipeMeta("existing", 3L);
+    addEmptyPipe(agent, current);
+    final PipeMeta staleDropped = newEmptyPipeMeta("existing", 1L);
+    staleDropped.getRuntimeMeta().getStatus().set(PipeStatus.DROPPED);
+
+    Assert.assertEquals(
+        Collections.emptyList(),
+        
agent.handleMultiPipeMetaChanges(Collections.singletonList(staleDropped)));
+
+    Assert.assertSame(current, keeper.getPipeMeta(current.getStaticMeta()));
+  }
+
+  @Test
+  public void testPartialPreDeleteDoesNotRemoveExistingPipe() throws Exception 
{
+    final PipeDataNodeTaskAgent agent = newActiveTaskAgent();
+    final PipeMetaKeeper keeper = getField(agent, "pipeMetaKeeper");
+    final PipeMeta current = newEmptyPipeMeta("existing", 3L);
+    addEmptyPipe(agent, current);
+    final PipeMeta preDelete = newEmptyPipeMeta("existing", 4L);
+    preDelete.getRuntimeMeta().getStatus().set(PipeStatus.PRE_DELETE);
+
+    Assert.assertEquals(
+        Collections.emptyList(),
+        
agent.handleMultiPipeMetaChanges(Collections.singletonList(preDelete)));
+
+    Assert.assertSame(current, keeper.getPipeMeta(current.getStaticMeta()));
+  }
+
+  @Test
+  public void testPartialAlterKeepsReplacementAndUnrelatedPipe() throws 
Exception {
+    final PipeDataNodeTaskAgent agent = newActiveTaskAgent();
+    final PipeMetaKeeper keeper = getField(agent, "pipeMetaKeeper");
+    final PipeMeta existing = newEmptyPipeMeta("existing", 1L);
+    final PipeMeta unrelated = newEmptyPipeMeta("unrelated", 2L);
+    addEmptyPipe(agent, existing);
+    addEmptyPipe(agent, unrelated);
+    final PipeMeta dropped = newEmptyPipeMeta("existing", 1L);
+    dropped.getRuntimeMeta().getStatus().set(PipeStatus.DROPPED);
+    final PipeMeta replacement = newEmptyPipeMeta("existing", 3L);
+
+    Assert.assertEquals(
+        Collections.emptyList(),
+        agent.handleMultiPipeMetaChanges(Arrays.asList(dropped, replacement)));
+
+    Assert.assertEquals(
+        3L, 
keeper.getPipeMeta(replacement.getStaticMeta()).getStaticMeta().getCreationTime());
+    Assert.assertSame(unrelated, 
keeper.getPipeMeta(unrelated.getStaticMeta()));
+    Assert.assertEquals(2, keeper.getPipeMetaCount());
+  }
+
+  private void addEmptyPipe(final PipeDataNodeTaskAgent agent, final PipeMeta 
meta)
+      throws Exception {
+    final PipeMetaKeeper keeper = getField(agent, "pipeMetaKeeper");
+    final PipeTaskManager manager = getField(agent, "pipeTaskManager");
+    keeper.addPipeMeta(meta);
+    manager.addPipeTasks(meta.getStaticMeta(), Collections.emptyMap());
+  }
+
+  private static PipeDataNodeTaskAgent newActiveTaskAgent() {
+    return new PipeDataNodeTaskAgent() {
+      @Override
+      protected boolean isShutdown() {
+        return false;
+      }
+    };
+  }
+
+  private static PipeMeta newEmptyPipeMeta(final String name, final long 
creationTime)
+      throws IOException {
+    return PipeMeta.deserialize4TaskAgent(
+        new PipeMeta(
+                new PipeStaticMeta(
+                    name,
+                    creationTime,
+                    Collections.emptyMap(),
+                    Collections.emptyMap(),
+                    Collections.emptyMap()),
+                new PipeRuntimeMeta())
+            .serialize());
+  }
+
   @Test
   public void testGetPipeTaskProgressIndexReportsMissingTaskMeta() throws 
Exception {
     final PipeDataNodeTaskAgent taskAgent = new PipeDataNodeTaskAgent();

Reply via email to