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

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


The following commit(s) were added to refs/heads/dev/1.3 by this push:
     new 8ea23e531e1 Fix write retries, Pipe resource cleanup, subscription 
progress, and snapshot recovery (#18180) (#18218)
8ea23e531e1 is described below

commit 8ea23e531e1a26f2415484f80f70f93de166bd10
Author: Caideyipi <[email protected]>
AuthorDate: Thu Jul 16 09:28:13 2026 +0800

    Fix write retries, Pipe resource cleanup, subscription progress, and 
snapshot recovery (#18180) (#18218)
    
    * Fix write retry, pipe fallback, and snapshot recovery edge cases
    
    * Fix additional pipe, write, and snapshot recovery failures
    
    * Added fixes
    
    * Harden subscription commit progress deserialization
    
    * Fix consensus subscription initial frontier synchronization
---
 .../agent/runtime/PipeConfigNodeRuntimeAgent.java  |  6 ++
 .../agent/runtime/PipeConfigRegionListener.java    | 19 +++++
 .../persistence/executor/ConfigPlanExecutor.java   | 14 +++-
 .../confignode/persistence/pipe/PipeInfo.java      |  5 --
 .../confignode/persistence/quota/QuotaInfo.java    |  1 +
 .../persistence/schema/TemplatePreSetTable.java    |  2 +
 .../persistence/schema/TemplateTable.java          | 12 ++-
 .../PipeConfigRegionListenerSnapshotTest.java      | 59 ++++++++++++++
 .../confignode/persistence/QuotaInfoTest.java      | 14 ++++
 .../schema/TemplatePreSetTableTest.java            | 19 ++++-
 .../persistence/schema/TemplateTableTest.java      | 10 ++-
 .../protocol/airgap/IoTDBDataRegionAirGapSink.java | 31 ++++----
 .../thrift/async/IoTDBDataRegionAsyncSink.java     | 44 +++++++----
 .../async/handler/PipeTransferTsFileHandler.java   | 24 +++++-
 .../thrift/sync/IoTDBDataRegionSyncSink.java       | 31 ++++----
 .../db/storageengine/dataregion/DataRegion.java    |  6 +-
 .../dataregion/HashLastFlushTimeMap.java           |  1 +
 .../dataregion/memtable/TsFileProcessor.java       | 17 +++-
 .../batch/SubscriptionPipeTsFileEventBatch.java    | 26 ++++++-
 .../PipeTransferTsFileHandlerCleanupTest.java      | 86 ++++++++++++++++++++
 .../dataregion/LastFlushTimeMapTest.java           | 33 ++++++++
 ...ubscriptionPipeTsFileEventBatchCleanupTest.java | 76 ++++++++++++++++++
 .../queue/ConcurrentIterableLinkedQueue.java       |  5 +-
 .../AbstractSerializableListeningQueue.java        |  2 +-
 .../ConcurrentIterableLinkedQueueTest.java         | 21 +++++
 .../listening/AbstractPipeListeningQueueTest.java  | 91 ++++++++++++++++++++++
 26 files changed, 587 insertions(+), 68 deletions(-)

diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/runtime/PipeConfigNodeRuntimeAgent.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/runtime/PipeConfigNodeRuntimeAgent.java
index f5a864cd974..d8636988f96 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/runtime/PipeConfigNodeRuntimeAgent.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/runtime/PipeConfigNodeRuntimeAgent.java
@@ -24,6 +24,7 @@ import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeException;
 import org.apache.iotdb.commons.log.LoggerPeriodicalLogReducer;
 import org.apache.iotdb.commons.pipe.agent.runtime.PipePeriodicalJobExecutor;
 import 
org.apache.iotdb.commons.pipe.agent.runtime.PipePeriodicalPhantomReferenceCleaner;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeMeta;
 import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTaskMeta;
 import org.apache.iotdb.commons.pipe.config.PipeConfig;
 import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
@@ -126,6 +127,11 @@ public class PipeConfigNodeRuntimeAgent implements 
IService {
     regionListener.decreaseReference(parameters);
   }
 
+  public void reconcileListenerReferences(final Iterable<PipeMeta> pipeMetas)
+      throws IllegalPathException {
+    regionListener.reconcileReferences(pipeMetas);
+  }
+
   /** Notify the region listener that the leader is ready to allow pipe 
operations. */
   public void notifyLeaderReady() {
     regionListener.notifyLeaderReady();
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/runtime/PipeConfigRegionListener.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/runtime/PipeConfigRegionListener.java
index 278e133494b..3fb5202f11e 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/runtime/PipeConfigRegionListener.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/runtime/PipeConfigRegionListener.java
@@ -20,6 +20,7 @@
 package org.apache.iotdb.confignode.manager.pipe.agent.runtime;
 
 import org.apache.iotdb.commons.exception.IllegalPathException;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeMeta;
 import 
org.apache.iotdb.confignode.manager.pipe.source.ConfigRegionListeningFilter;
 import 
org.apache.iotdb.confignode.manager.pipe.source.ConfigRegionListeningQueue;
 import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters;
@@ -57,6 +58,24 @@ public class PipeConfigRegionListener {
     }
   }
 
+  public synchronized void reconcileReferences(final Iterable<PipeMeta> 
pipeMetas)
+      throws IllegalPathException {
+    int referenceCount = 0;
+    for (final PipeMeta pipeMeta : pipeMetas) {
+      if (!ConfigRegionListeningFilter.parseListeningPlanTypeSet(
+              pipeMeta.getStaticMeta().getExtractorParameters())
+          .isEmpty()) {
+        referenceCount++;
+      }
+    }
+    listeningQueueReferenceCount = referenceCount;
+    if (referenceCount == 0) {
+      listeningQueue.close();
+    } else {
+      listeningQueue.open();
+    }
+  }
+
   public synchronized boolean isLeaderReady() {
     return isLeaderReady.get();
   }
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/executor/ConfigPlanExecutor.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/executor/ConfigPlanExecutor.java
index 60ec748fb54..07efa5bc24f 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/executor/ConfigPlanExecutor.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/executor/ConfigPlanExecutor.java
@@ -22,6 +22,7 @@ package org.apache.iotdb.confignode.persistence.executor;
 import org.apache.iotdb.common.rpc.thrift.TSStatus;
 import org.apache.iotdb.common.rpc.thrift.TSchemaNode;
 import org.apache.iotdb.commons.auth.AuthException;
+import org.apache.iotdb.commons.exception.IllegalPathException;
 import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.commons.schema.node.MNodeType;
 import org.apache.iotdb.commons.schema.ttl.TTLCache;
@@ -663,9 +664,16 @@ public class ConfigPlanExecutor {
               }
             });
     if (result.get()) {
-      LOGGER.info(
-          "[ConfigNodeSnapshot] Load snapshot success, latestSnapshotRootDir: 
{}",
-          latestSnapshotRootDir);
+      try {
+        PipeConfigNodeAgent.runtime()
+            
.reconcileListenerReferences(pipeInfo.getPipeTaskInfo().getPipeMetaList());
+        LOGGER.info(
+            "[ConfigNodeSnapshot] Load snapshot success, 
latestSnapshotRootDir: {}",
+            latestSnapshotRootDir);
+      } catch (final IllegalPathException e) {
+        result.set(false);
+        LOGGER.error(e.getMessage(), e);
+      }
     }
   }
 
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/pipe/PipeInfo.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/pipe/PipeInfo.java
index 032534f9161..1da72eae7c8 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/pipe/PipeInfo.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/pipe/PipeInfo.java
@@ -290,11 +290,6 @@ public class PipeInfo implements SnapshotProcessor {
 
     try {
       pipeTaskInfo.processLoadSnapshot(snapshotDir);
-
-      for (final PipeMeta pipeMeta : pipeTaskInfo.getPipeMetaList()) {
-        PipeConfigNodeAgent.runtime()
-            
.increaseListenerReference(pipeMeta.getStaticMeta().getExtractorParameters());
-      }
     } catch (final Exception ex) {
       LOGGER.error("Failed to load pipe task info from snapshot", ex);
       loadPipeTaskInfoException = ex;
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/quota/QuotaInfo.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/quota/QuotaInfo.java
index 93d2b272109..f4b68c03e83 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/quota/QuotaInfo.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/quota/QuotaInfo.java
@@ -258,6 +258,7 @@ public class QuotaInfo implements SnapshotProcessor {
 
   public void clear() {
     spaceQuotaLimit.clear();
+    spaceQuotaUsage.clear();
     throttleQuotaLimit.clear();
   }
 }
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/TemplatePreSetTable.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/TemplatePreSetTable.java
index 251100c0071..bd9d0bcc67b 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/TemplatePreSetTable.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/TemplatePreSetTable.java
@@ -146,6 +146,8 @@ public class TemplatePreSetTable {
     try {
       File snapshotFile = new File(snapshotDir, SNAPSHOT_FILENAME);
       if (!snapshotFile.exists()) {
+        // Empty preset tables are represented by the absence of a snapshot 
file.
+        templatePreSetMap.clear();
         return;
       }
 
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/TemplateTable.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/TemplateTable.java
index 23cde4e9447..04a09632409 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/TemplateTable.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/TemplateTable.java
@@ -260,9 +260,8 @@ public class TemplateTable {
         BufferedInputStream bufferedInputStream = new 
BufferedInputStream(fileInputStream)) {
       // Load snapshot of template
       this.templateMap.clear();
+      this.templateIdMap.clear();
       deserialize(bufferedInputStream);
-      bufferedInputStream.close();
-      fileInputStream.close();
     } finally {
       templateReadWriteLock.writeLock().unlock();
     }
@@ -270,6 +269,13 @@ public class TemplateTable {
 
   @TestOnly
   public void clear() {
-    this.templateMap.clear();
+    templateReadWriteLock.writeLock().lock();
+    try {
+      this.templateMap.clear();
+      this.templateIdMap.clear();
+      this.templateIdGenerator.set(0);
+    } finally {
+      templateReadWriteLock.writeLock().unlock();
+    }
   }
 }
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/agent/runtime/PipeConfigRegionListenerSnapshotTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/agent/runtime/PipeConfigRegionListenerSnapshotTest.java
new file mode 100644
index 00000000000..3d7b6529a3a
--- /dev/null
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/pipe/agent/runtime/PipeConfigRegionListenerSnapshotTest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.confignode.manager.pipe.agent.runtime;
+
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeMeta;
+import org.apache.iotdb.commons.pipe.agent.task.meta.PipeStaticMeta;
+import org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class PipeConfigRegionListenerSnapshotTest {
+
+  @Test
+  public void testReconcileReferencesReplacesSnapshotState() throws Exception {
+    final Map<String, String> sourceAttributes = new HashMap<>();
+    sourceAttributes.put(PipeSourceConstant.EXTRACTOR_INCLUSION_KEY, 
"schema.database.create");
+    final PipeStaticMeta staticMeta =
+        new PipeStaticMeta(
+            "pipe", 1, sourceAttributes, Collections.emptyMap(), 
Collections.emptyMap());
+    final PipeMeta pipeMeta = Mockito.mock(PipeMeta.class);
+    Mockito.when(pipeMeta.getStaticMeta()).thenReturn(staticMeta);
+
+    final PipeConfigRegionListener listener = new PipeConfigRegionListener();
+    listener.increaseReference(staticMeta.getExtractorParameters());
+    listener.listener().close();
+
+    listener.reconcileReferences(Collections.emptyList());
+    listener.increaseReference(staticMeta.getExtractorParameters());
+    Assert.assertTrue(listener.listener().isOpened());
+
+    listener.reconcileReferences(Collections.singletonList(pipeMeta));
+    listener.reconcileReferences(Collections.singletonList(pipeMeta));
+    listener.decreaseReference(staticMeta.getExtractorParameters());
+    Assert.assertFalse(listener.listener().isOpened());
+  }
+}
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/QuotaInfoTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/QuotaInfoTest.java
index 134a0a203a9..c48b766b046 100644
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/QuotaInfoTest.java
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/QuotaInfoTest.java
@@ -37,6 +37,7 @@ import org.junit.Test;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -97,6 +98,19 @@ public class QuotaInfoTest {
     QuotaInfo quotaInfo2 = new QuotaInfo();
     quotaInfo2.processLoadSnapshot(snapshotDir);
 
+    final TSpaceQuota staleQuota = new TSpaceQuota();
+    staleQuota.setDeviceNum(1);
+    staleQuota.setTimeserieNum(1);
+    staleQuota.setDiskSize(1);
+    quotaInfo2.setSpaceQuota(
+        new SetSpaceQuotaPlan(Collections.singletonList("root.stale"), 
staleQuota));
+    
Assert.assertTrue(quotaInfo2.getSpaceQuotaUsage().containsKey("root.stale"));
+
+    quotaInfo2.processLoadSnapshot(snapshotDir);
+
+    
Assert.assertFalse(quotaInfo2.getSpaceQuotaLimit().containsKey("root.stale"));
+    
Assert.assertFalse(quotaInfo2.getSpaceQuotaUsage().containsKey("root.stale"));
+
     Assert.assertEquals(quotaInfo.getSpaceQuotaLimit(), 
quotaInfo2.getSpaceQuotaLimit());
     Assert.assertEquals(quotaInfo.getThrottleQuotaLimit(), 
quotaInfo2.getThrottleQuotaLimit());
   }
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/TemplatePreSetTableTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/TemplatePreSetTableTest.java
index 769359525df..9d90a71a97a 100644
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/TemplatePreSetTableTest.java
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/TemplatePreSetTableTest.java
@@ -104,11 +104,24 @@ public class TemplatePreSetTableTest {
       newTemplatePreSetTable = new TemplatePreSetTable();
       newTemplatePreSetTable.processLoadSnapshot(snapshotDir);
 
-      Assert.assertTrue(templatePreSetTable.isPreSet(templateId1, 
templateSetPath1));
-      Assert.assertTrue(templatePreSetTable.isPreSet(templateId2, 
templateSetPath1));
-      Assert.assertTrue(templatePreSetTable.isPreSet(templateId2, 
templateSetPath2));
+      Assert.assertTrue(newTemplatePreSetTable.isPreSet(templateId1, 
templateSetPath1));
+      Assert.assertTrue(newTemplatePreSetTable.isPreSet(templateId2, 
templateSetPath1));
+      Assert.assertTrue(newTemplatePreSetTable.isPreSet(templateId2, 
templateSetPath2));
     } catch (IOException e) {
       Assert.fail();
     }
   }
+
+  @Test
+  public void testEmptySnapshotReplacesExistingState() throws IOException, 
IllegalPathException {
+    final int templateId = 5;
+    final PartialPath templateSetPath = new PartialPath("root.db.t1");
+
+    Assert.assertTrue(templatePreSetTable.processTakeSnapshot(snapshotDir));
+    templatePreSetTable.preSetTemplate(templateId, templateSetPath);
+
+    templatePreSetTable.processLoadSnapshot(snapshotDir);
+
+    Assert.assertFalse(templatePreSetTable.isPreSet(templateId, 
templateSetPath));
+  }
 }
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/TemplateTableTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/TemplateTableTest.java
index c4b8aab849a..2f3c2bfd6d4 100644
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/TemplateTableTest.java
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/TemplateTableTest.java
@@ -77,7 +77,8 @@ public class TemplateTableTest {
     }
 
     templateTable.processTakeSnapshot(snapshotDir);
-    templateTable.clear();
+    Template staleTemplate = newSchemaTemplate("stale_template");
+    templateTable.createTemplate(staleTemplate);
     templateTable.processLoadSnapshot(snapshotDir);
 
     // show nodes in schemaengine template
@@ -86,6 +87,13 @@ public class TemplateTableTest {
       Template template = templates.get(i);
       Assert.assertEquals(template, 
templateTable.getTemplate(templateNameTmp));
     }
+
+    try {
+      templateTable.getTemplate(staleTemplate.getId());
+      Assert.fail("Template created after the snapshot should be removed when 
loading it");
+    } catch (MetadataException expected) {
+      // expected
+    }
   }
 
   @Test
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/airgap/IoTDBDataRegionAirGapSink.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/airgap/IoTDBDataRegionAirGapSink.java
index ed6f127213c..8226eeac0f7 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/airgap/IoTDBDataRegionAirGapSink.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/airgap/IoTDBDataRegionAirGapSink.java
@@ -256,19 +256,24 @@ public class IoTDBDataRegionAirGapSink extends 
IoTDBDataNodeAirGapSink {
     final List<File> sealedFiles = batchToTransfer.sealTsFiles();
     final Map<Pair<String, Long>, Double> pipe2WeightMap = 
batchToTransfer.deepCopyPipe2WeightMap();
 
-    for (final File tsFile : sealedFiles) {
-      doTransfer(pipe2WeightMap, socket, tsFile, null, null, tsFile.getName());
-      try {
-        RetryUtils.retryOnException(
-            () -> {
-              FileUtils.delete(tsFile);
-              return null;
-            });
-      } catch (final NoSuchFileException e) {
-        LOGGER.info("The file {} is not found, may already be deleted.", 
tsFile);
-      } catch (final Exception e) {
-        LOGGER.warn(
-            "Failed to delete batch file {}, this file should be deleted 
manually later", tsFile);
+    try {
+      for (final File tsFile : sealedFiles) {
+        doTransfer(pipe2WeightMap, socket, tsFile, null, null, 
tsFile.getName());
+      }
+    } finally {
+      for (final File tsFile : sealedFiles) {
+        try {
+          RetryUtils.retryOnException(
+              () -> {
+                FileUtils.delete(tsFile);
+                return null;
+              });
+        } catch (final NoSuchFileException e) {
+          LOGGER.info("The file {} is not found, may already be deleted.", 
tsFile);
+        } catch (final Exception e) {
+          LOGGER.warn(
+              "Failed to delete batch file {}, this file should be deleted 
manually later", tsFile);
+        }
       }
     }
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
index 77d3d02864f..abc10f4fcc2 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/IoTDBDataRegionAsyncSink.java
@@ -64,6 +64,7 @@ import org.apache.iotdb.rpc.TSStatusCode;
 import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq;
 
 import com.google.common.collect.ImmutableSet;
+import org.apache.commons.io.FileUtils;
 import org.apache.tsfile.exception.write.WriteProcessException;
 import org.apache.tsfile.utils.Pair;
 import org.slf4j.Logger;
@@ -249,6 +250,7 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink {
       final AtomicInteger eventsReferenceCount = new 
AtomicInteger(sealedFiles.size());
       final AtomicBoolean eventsHadBeenAddedToRetryQueue = new 
AtomicBoolean(false);
 
+      int transferredFileCount = 0;
       try {
         for (final File sealedFile : sealedFiles) {
           transfer(
@@ -262,8 +264,12 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink {
                   null,
                   false,
                   null));
+          transferredFileCount++;
         }
       } catch (final Exception e) {
+        for (int i = transferredFileCount; i < sealedFiles.size(); i++) {
+          FileUtils.deleteQuietly(sealedFiles.get(i));
+        }
         PipeLogger.log(LOGGER::warn, e, "Failed to transfer tsfile batch 
(%s).", sealedFiles);
         if (eventsHadBeenAddedToRetryQueue.compareAndSet(false, true)) {
           addFailureEventsToRetryQueue(events, e);
@@ -421,22 +427,28 @@ public class IoTDBDataRegionAsyncSink extends IoTDBSink {
 
   private void transfer(final PipeTransferTsFileHandler 
pipeTransferTsFileHandler) {
     transferTsFileCounter.incrementAndGet();
-    CompletableFuture<Void> completableFuture =
-        CompletableFuture.supplyAsync(
-            () -> {
-              AsyncPipeDataTransferServiceClient client = null;
-              try {
-                client = transferTsFileClientManager.borrowClient();
-                
pipeTransferTsFileHandler.transfer(transferTsFileClientManager, client);
-              } catch (final Exception ex) {
-                logOnClientException(client, ex);
-                pipeTransferTsFileHandler.onError(ex);
-              } finally {
-                transferTsFileCounter.decrementAndGet();
-              }
-              return null;
-            },
-            transferTsFileClientManager.getExecutor());
+    final CompletableFuture<Void> completableFuture;
+    try {
+      completableFuture =
+          CompletableFuture.supplyAsync(
+              () -> {
+                AsyncPipeDataTransferServiceClient client = null;
+                try {
+                  client = transferTsFileClientManager.borrowClient();
+                  
pipeTransferTsFileHandler.transfer(transferTsFileClientManager, client);
+                } catch (final Exception ex) {
+                  logOnClientException(client, ex);
+                  pipeTransferTsFileHandler.onError(ex);
+                } finally {
+                  transferTsFileCounter.decrementAndGet();
+                }
+                return null;
+              },
+              transferTsFileClientManager.getExecutor());
+    } catch (final RuntimeException e) {
+      transferTsFileCounter.decrementAndGet();
+      throw e;
+    }
 
     if (PipeConfig.getInstance().isTransferTsFileSync()) {
       try {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/handler/PipeTransferTsFileHandler.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/handler/PipeTransferTsFileHandler.java
index ba9f1e54b1e..b6936485770 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/handler/PipeTransferTsFileHandler.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/handler/PipeTransferTsFileHandler.java
@@ -38,6 +38,7 @@ import 
org.apache.iotdb.db.pipe.sink.payload.evolvable.request.PipeTransferTsFil
 import 
org.apache.iotdb.db.pipe.sink.payload.evolvable.request.PipeTransferTsFileSealReq;
 import 
org.apache.iotdb.db.pipe.sink.payload.evolvable.request.PipeTransferTsFileSealWithModReq;
 import 
org.apache.iotdb.db.pipe.sink.protocol.thrift.async.IoTDBDataRegionAsyncSink;
+import org.apache.iotdb.pipe.api.exception.PipeConnectionException;
 import org.apache.iotdb.pipe.api.exception.PipeException;
 import org.apache.iotdb.rpc.TSStatusCode;
 import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq;
@@ -154,6 +155,7 @@ public class PipeTransferTsFileHandler extends 
PipeTransferTrackableHandler {
           "Client has been returned to the pool. Current handler status is %s. 
Will not transfer %s.",
           sink.isClosed() ? "CLOSED" : "NOT CLOSED",
           tsFile);
+      onError(new PipeConnectionException("Client has been returned to the 
pool."));
       return;
     }
 
@@ -469,8 +471,26 @@ public class PipeTransferTsFileHandler extends 
PipeTransferTrackableHandler {
 
   @Override
   public void close() {
-    super.close();
-    releaseReadBufferMemoryBlock();
+    try {
+      if (reader != null) {
+        reader.close();
+        reader = null;
+      }
+
+      if (currentFile.exists()
+          && events.stream().anyMatch(event -> !(event instanceof 
PipeTsFileInsertionEvent))) {
+        RetryUtils.retryOnException(
+            () -> {
+              FileUtils.delete(currentFile);
+              return null;
+            });
+      }
+    } catch (final IOException e) {
+      LOGGER.warn("Failed to close file reader or delete generated batch 
file.", e);
+    } finally {
+      super.close();
+      releaseReadBufferMemoryBlock();
+    }
   }
 
   private void releaseReadBufferMemoryBlock() {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/sync/IoTDBDataRegionSyncSink.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/sync/IoTDBDataRegionSyncSink.java
index 2d3ec89756e..7a1fe1463f6 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/sync/IoTDBDataRegionSyncSink.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/sync/IoTDBDataRegionSyncSink.java
@@ -279,19 +279,24 @@ public class IoTDBDataRegionSyncSink extends 
IoTDBDataNodeSyncSink {
     final List<File> sealedFiles = batchToTransfer.sealTsFiles();
     final Map<Pair<String, Long>, Double> pipe2WeightMap = 
batchToTransfer.deepCopyPipe2WeightMap();
 
-    for (final File tsFile : sealedFiles) {
-      doTransfer(pipe2WeightMap, tsFile, null, null);
-      try {
-        RetryUtils.retryOnException(
-            () -> {
-              FileUtils.delete(tsFile);
-              return null;
-            });
-      } catch (final NoSuchFileException e) {
-        LOGGER.info("The file {} is not found, may already be deleted.", 
tsFile);
-      } catch (final Exception e) {
-        LOGGER.warn(
-            "Failed to delete batch file {}, this file should be deleted 
manually later", tsFile);
+    try {
+      for (final File tsFile : sealedFiles) {
+        doTransfer(pipe2WeightMap, tsFile, null, null);
+      }
+    } finally {
+      for (final File tsFile : sealedFiles) {
+        try {
+          RetryUtils.retryOnException(
+              () -> {
+                FileUtils.delete(tsFile);
+                return null;
+              });
+        } catch (final NoSuchFileException e) {
+          LOGGER.info("The file {} is not found, may already be deleted.", 
tsFile);
+        } catch (final Exception e) {
+          LOGGER.warn(
+              "Failed to delete batch file {}, this file should be deleted 
manually later", tsFile);
+        }
       }
     }
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
index 5568aa4015c..47032a273a8 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
@@ -709,7 +709,7 @@ public class DataRegion implements IDataRegionForQuery {
         // checked above
         //noinspection OptionalGetWithoutIsPresent
         long endTime = resource.getEndTime(deviceId).get();
-        endTimeMap.put(deviceId, endTime);
+        endTimeMap.merge(deviceId, endTime, Math::max);
       }
     }
     if (config.isEnableSeparateData()) {
@@ -1275,6 +1275,10 @@ public class DataRegion implements IDataRegionForQuery {
     try {
       tsFileProcessor.insertTablet(insertTabletNode, start, end, results);
     } catch (WriteProcessRejectException e) {
+      final TSStatus failureStatus = RpcUtils.getStatus(e.getErrorCode(), 
e.getMessage());
+      for (int i = start; i < end; i++) {
+        results[i] = failureStatus;
+      }
       logger.warn("insert to TsFileProcessor rejected, {}", e.getMessage());
       return false;
     } catch (WriteProcessException e) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/HashLastFlushTimeMap.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/HashLastFlushTimeMap.java
index f29c158cc0d..2452cfd0111 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/HashLastFlushTimeMap.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/HashLastFlushTimeMap.java
@@ -152,6 +152,7 @@ public class HashLastFlushTimeMap implements 
ILastFlushTimeMap {
   @Override
   public void clearFlushedTime() {
     partitionLatestFlushedTime.clear();
+    memCostForEachPartition.clear();
   }
 
   @Override
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
index 6bbcb427f87..da62e80252b 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
@@ -376,7 +376,13 @@ public class TsFileProcessor {
         }
       }
       long[] alignedMemIncrements = 
checkAlignedMemCostAndAddToTspInfoForRows(alignedList);
-      long[] nonAlignedMemIncrements = 
checkMemCostAndAddToTspInfoForRows(nonAlignedList);
+      final long[] nonAlignedMemIncrements;
+      try {
+        nonAlignedMemIncrements = 
checkMemCostAndAddToTspInfoForRows(nonAlignedList);
+      } catch (final WriteProcessException e) {
+        rollbackMemoryInfoIfNeeded(alignedMemIncrements);
+        throw e;
+      }
       memIncrements = new long[3];
       for (int i = 0; i < 3; i++) {
         memIncrements[i] = alignedMemIncrements[i] + 
nonAlignedMemIncrements[i];
@@ -1034,6 +1040,15 @@ public class TsFileProcessor {
     workMemTable.releaseTextDataSize(textDataIncrement);
   }
 
+  private void rollbackMemoryInfoIfNeeded(final long[] memIncrements) {
+    for (final long memIncrement : memIncrements) {
+      if (memIncrement != 0) {
+        rollbackMemoryInfo(memIncrements);
+        return;
+      }
+    }
+  }
+
   /**
    * Delete data which belongs to the timeseries `deviceId.measurementId` and 
the timestamp of which
    * <= 'timestamp' in the deletion. <br>
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/event/batch/SubscriptionPipeTsFileEventBatch.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/event/batch/SubscriptionPipeTsFileEventBatch.java
index 8e3eee58ad0..47ee100f7c9 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/event/batch/SubscriptionPipeTsFileEventBatch.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/event/batch/SubscriptionPipeTsFileEventBatch.java
@@ -20,6 +20,7 @@
 package org.apache.iotdb.db.subscription.event.batch;
 
 import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
+import org.apache.iotdb.commons.utils.TestOnly;
 import 
org.apache.iotdb.db.pipe.sink.payload.evolvable.batch.PipeTabletEventTsFileBatch;
 import 
org.apache.iotdb.db.subscription.broker.SubscriptionPrefetchingTsFileQueue;
 import org.apache.iotdb.db.subscription.event.SubscriptionEvent;
@@ -28,6 +29,7 @@ import 
org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent;
 import org.apache.iotdb.pipe.api.event.dml.insertion.TsFileInsertionEvent;
 import 
org.apache.iotdb.rpc.subscription.payload.poll.SubscriptionCommitContext;
 
+import org.apache.commons.io.FileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -42,6 +44,7 @@ public class SubscriptionPipeTsFileEventBatch extends 
SubscriptionPipeEventBatch
       LoggerFactory.getLogger(SubscriptionPipeTsFileEventBatch.class);
 
   private final PipeTabletEventTsFileBatch batch;
+  private final List<File> sealedFiles = new ArrayList<>();
 
   public SubscriptionPipeTsFileEventBatch(
       final int regionId,
@@ -52,6 +55,17 @@ public class SubscriptionPipeTsFileEventBatch extends 
SubscriptionPipeEventBatch
     this.batch = new PipeTabletEventTsFileBatch(maxDelayInMs, 
maxBatchSizeInBytes);
   }
 
+  @TestOnly
+  SubscriptionPipeTsFileEventBatch(
+      final int regionId,
+      final SubscriptionPrefetchingTsFileQueue prefetchingQueue,
+      final int maxDelayInMs,
+      final long maxBatchSizeInBytes,
+      final PipeTabletEventTsFileBatch batch) {
+    super(regionId, prefetchingQueue, maxDelayInMs, maxBatchSizeInBytes);
+    this.batch = batch;
+  }
+
   @Override
   public synchronized void ack() {
     batch.decreaseEventsReferenceCount(this.getClass().getName(), true);
@@ -59,9 +73,14 @@ public class SubscriptionPipeTsFileEventBatch extends 
SubscriptionPipeEventBatch
 
   @Override
   public synchronized void cleanUp(final boolean force) {
-    // close batch, it includes clearing the reference count of events
-    batch.close();
-    enrichedEvents.clear();
+    try {
+      // close batch, it includes clearing the reference count of events
+      batch.close();
+    } finally {
+      sealedFiles.forEach(FileUtils::deleteQuietly);
+      sealedFiles.clear();
+      enrichedEvents.clear();
+    }
   }
 
   /////////////////////////////// utility ///////////////////////////////
@@ -95,6 +114,7 @@ public class SubscriptionPipeTsFileEventBatch extends 
SubscriptionPipeEventBatch
 
     final List<SubscriptionEvent> events = new ArrayList<>();
     final List<File> tsFiles = batch.sealTsFiles();
+    sealedFiles.addAll(tsFiles);
     final AtomicInteger ackReferenceCount = new AtomicInteger(tsFiles.size());
     final AtomicInteger cleanReferenceCount = new 
AtomicInteger(tsFiles.size());
     for (final File tsFile : tsFiles) {
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/handler/PipeTransferTsFileHandlerCleanupTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/handler/PipeTransferTsFileHandlerCleanupTest.java
new file mode 100644
index 00000000000..aca597f0463
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/protocol/thrift/async/handler/PipeTransferTsFileHandlerCleanupTest.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.pipe.sink.protocol.thrift.async.handler;
+
+import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
+import org.apache.iotdb.db.pipe.event.common.tsfile.PipeTsFileInsertionEvent;
+import 
org.apache.iotdb.db.pipe.sink.protocol.thrift.async.IoTDBDataRegionAsyncSink;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class PipeTransferTsFileHandlerCleanupTest {
+
+  @Test
+  public void testCloseDeletesBatchFile() throws Exception {
+    final File file = Files.createTempFile("pipe-transfer-batch", 
".tsfile").toFile();
+    final EnrichedEvent event = Mockito.mock(EnrichedEvent.class);
+
+    createHandler(file, event).close();
+
+    Assert.assertFalse(file.exists());
+  }
+
+  @Test
+  public void testNullClientDeletesBatchFile() throws Exception {
+    final File file = Files.createTempFile("pipe-transfer-null-client", 
".tsfile").toFile();
+    final EnrichedEvent event = Mockito.mock(EnrichedEvent.class);
+    final PipeTransferTsFileHandler handler = createHandler(file, event);
+
+    handler.transfer(null, null);
+
+    Assert.assertFalse(file.exists());
+  }
+
+  @Test
+  public void testCloseKeepsSourceTsFile() throws Exception {
+    final File file = Files.createTempFile("pipe-transfer-source", 
".tsfile").toFile();
+    final PipeTsFileInsertionEvent event = 
Mockito.mock(PipeTsFileInsertionEvent.class);
+    try {
+      createHandler(file, event).close();
+      Assert.assertTrue(file.exists());
+    } finally {
+      if (file.exists()) {
+        Assert.assertTrue(file.delete());
+      }
+    }
+  }
+
+  private PipeTransferTsFileHandler createHandler(final File file, final 
EnrichedEvent event)
+      throws Exception {
+    return new PipeTransferTsFileHandler(
+        Mockito.mock(IoTDBDataRegionAsyncSink.class),
+        Collections.emptyMap(),
+        Collections.singletonList(event),
+        new AtomicInteger(1),
+        new AtomicBoolean(false),
+        file,
+        null,
+        false,
+        null);
+  }
+}
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/LastFlushTimeMapTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/LastFlushTimeMapTest.java
index b260445a5bd..877c71b1500 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/LastFlushTimeMapTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/LastFlushTimeMapTest.java
@@ -39,9 +39,13 @@ import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mockito;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Optional;
 
 public class LastFlushTimeMapTest {
 
@@ -130,6 +134,35 @@ public class LastFlushTimeMapTest {
         dataRegion.getLastFlushTimeMap().getFlushedTime(0, new 
PlainDeviceID("root.vehicle.d1")));
   }
 
+  @Test
+  public void testClearFlushedTimeClearsMemoryCost() {
+    HashLastFlushTimeMap lastFlushTimeMap = new HashLastFlushTimeMap();
+    IDeviceID device = new PlainDeviceID("root.vehicle.d0");
+
+    lastFlushTimeMap.updateMultiDeviceFlushedTime(0, 
Collections.singletonMap(device, 100L));
+    Assert.assertTrue(lastFlushTimeMap.getMemSize(0) > 0);
+    lastFlushTimeMap.clearFlushedTime();
+    Assert.assertEquals(0, lastFlushTimeMap.getMemSize(0));
+  }
+
+  @Test
+  public void testUpgradeDeviceFlushTimeUsesMaximumAcrossResources() {
+    final IDeviceID device = new PlainDeviceID("root.vehicle.d0");
+    final TsFileResource newerEndTimeResource = 
Mockito.mock(TsFileResource.class);
+    final TsFileResource olderEndTimeResource = 
Mockito.mock(TsFileResource.class);
+    
Mockito.when(newerEndTimeResource.getDevices()).thenReturn(Collections.singleton(device));
+    
Mockito.when(newerEndTimeResource.getEndTime(device)).thenReturn(Optional.of(100L));
+    
Mockito.when(olderEndTimeResource.getDevices()).thenReturn(Collections.singleton(device));
+    
Mockito.when(olderEndTimeResource.getEndTime(device)).thenReturn(Optional.of(50L));
+
+    dataRegion.getLastFlushTimeMap().clearFlushedTime();
+    dataRegion.getLastFlushTimeMap().checkAndCreateFlushedTimePartition(0, 
true);
+    dataRegion.upgradeAndUpdateDeviceLastFlushTime(
+        0, Arrays.asList(newerEndTimeResource, olderEndTimeResource));
+
+    Assert.assertEquals(100L, 
dataRegion.getLastFlushTimeMap().getFlushedTime(0, device));
+  }
+
   @Test
   public void testRecoverLastFlushTimeMap()
       throws IOException, IllegalPathException, WriteProcessException, 
DataRegionException {
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/event/batch/SubscriptionPipeTsFileEventBatchCleanupTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/event/batch/SubscriptionPipeTsFileEventBatchCleanupTest.java
new file mode 100644
index 00000000000..9496ac6169f
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/event/batch/SubscriptionPipeTsFileEventBatchCleanupTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.subscription.event.batch;
+
+import 
org.apache.iotdb.db.pipe.sink.payload.evolvable.batch.PipeTabletEventTsFileBatch;
+import 
org.apache.iotdb.db.subscription.broker.SubscriptionPrefetchingTsFileQueue;
+import org.apache.iotdb.db.subscription.event.SubscriptionEvent;
+import 
org.apache.iotdb.rpc.subscription.payload.poll.SubscriptionCommitContext;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.List;
+
+public class SubscriptionPipeTsFileEventBatchCleanupTest {
+
+  @Test
+  public void testDeletesSealedFilesAfterAllSharedEventsAreCleaned() throws 
Exception {
+    final File temporaryDirectory =
+        Files.createTempDirectory("subscription-tsfile-cleanup").toFile();
+    final File firstFile = new File(temporaryDirectory, "first.tsfile");
+    final File secondFile = new File(temporaryDirectory, "second.tsfile");
+    Assert.assertTrue(firstFile.createNewFile());
+    Assert.assertTrue(secondFile.createNewFile());
+
+    final PipeTabletEventTsFileBatch innerBatch = 
Mockito.mock(PipeTabletEventTsFileBatch.class);
+    final SubscriptionPrefetchingTsFileQueue queue =
+        Mockito.mock(SubscriptionPrefetchingTsFileQueue.class);
+    Mockito.when(innerBatch.isEmpty()).thenReturn(false);
+    Mockito.when(innerBatch.sealTsFiles()).thenReturn(Arrays.asList(firstFile, 
secondFile));
+    Mockito.when(queue.generateSubscriptionCommitContext())
+        .thenReturn(
+            new SubscriptionCommitContext(1, 1, "topic", "group", 1),
+            new SubscriptionCommitContext(1, 1, "topic", "group", 2));
+
+    try {
+      final SubscriptionPipeTsFileEventBatch batch =
+          new SubscriptionPipeTsFileEventBatch(1, queue, 1, 1, innerBatch);
+      final List<SubscriptionEvent> events = 
batch.generateSubscriptionEvents();
+
+      Assert.assertEquals(2, events.size());
+      events.get(0).cleanUp(false);
+      Assert.assertTrue(firstFile.exists());
+      Assert.assertTrue(secondFile.exists());
+
+      events.get(1).cleanUp(false);
+      Assert.assertFalse(firstFile.exists());
+      Assert.assertFalse(secondFile.exists());
+      Mockito.verify(innerBatch, Mockito.times(1)).close();
+    } finally {
+      FileUtils.deleteDirectory(temporaryDirectory);
+    }
+  }
+}
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/datastructure/queue/ConcurrentIterableLinkedQueue.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/datastructure/queue/ConcurrentIterableLinkedQueue.java
index 306b5491f32..95608a38e58 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/datastructure/queue/ConcurrentIterableLinkedQueue.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/datastructure/queue/ConcurrentIterableLinkedQueue.java
@@ -136,6 +136,7 @@ public class ConcurrentIterableLinkedQueue<E> {
       if (firstNode == null) {
         firstNode = pilotNode;
         lastNode = pilotNode;
+        pilotNode.next = null;
       }
 
       // Update iterators if necessary
@@ -207,9 +208,7 @@ public class ConcurrentIterableLinkedQueue<E> {
     lock.writeLock().lock();
     try {
       this.firstIndex = firstIndex;
-      if (tailIndex < firstIndex) {
-        tailIndex = firstIndex;
-      }
+      tailIndex = firstIndex;
     } finally {
       lock.writeLock().unlock();
     }
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/datastructure/queue/listening/AbstractSerializableListeningQueue.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/datastructure/queue/listening/AbstractSerializableListeningQueue.java
index f398bf7e0ab..1371d856c50 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/datastructure/queue/listening/AbstractSerializableListeningQueue.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/datastructure/queue/listening/AbstractSerializableListeningQueue.java
@@ -138,7 +138,7 @@ public abstract class AbstractSerializableListeningQueue<E> 
implements Closeable
       return;
     }
 
-    queue.clear();
+    close();
     try (final FileInputStream inputStream = new 
FileInputStream(snapshotFile)) {
       isClosed.set(ReadWriteIOUtils.readBool(inputStream));
       final QueueSerializerType type =
diff --git 
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/datastructure/ConcurrentIterableLinkedQueueTest.java
 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/datastructure/ConcurrentIterableLinkedQueueTest.java
index a7a7310b113..60917a710c0 100644
--- 
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/datastructure/ConcurrentIterableLinkedQueueTest.java
+++ 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/datastructure/ConcurrentIterableLinkedQueueTest.java
@@ -480,4 +480,25 @@ public class ConcurrentIterableLinkedQueueTest {
     Assert.assertTrue(itr.hasNext());
     Assert.assertEquals(Integer.valueOf(5), itr.next());
   }
+
+  @Test(timeout = 60000)
+  public void testSetFirstIndexReplacesEmptyQueueIndexes() {
+    queue.add(1);
+    queue.add(2);
+    queue.clear();
+
+    queue.setFirstIndex(1);
+
+    Assert.assertEquals(1, queue.getFirstIndex());
+    Assert.assertEquals(1, queue.getTailIndex());
+    queue.add(3);
+    Assert.assertEquals(2, queue.getTailIndex());
+
+    try (final ConcurrentIterableLinkedQueue<Integer>.DynamicIterator itr =
+        queue.iterateFromEarliest()) {
+      Assert.assertTrue(itr.hasNext());
+      Assert.assertEquals(Integer.valueOf(3), itr.next());
+      Assert.assertFalse(itr.hasNext());
+    }
+  }
 }
diff --git 
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/datastructure/queue/listening/AbstractPipeListeningQueueTest.java
 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/datastructure/queue/listening/AbstractPipeListeningQueueTest.java
new file mode 100644
index 00000000000..10755b41787
--- /dev/null
+++ 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/pipe/datastructure/queue/listening/AbstractPipeListeningQueueTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.commons.pipe.datastructure.queue.listening;
+
+import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
+import org.apache.iotdb.commons.pipe.event.PipeSnapshotEvent;
+import org.apache.iotdb.pipe.api.event.Event;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+
+public class AbstractPipeListeningQueueTest {
+
+  @Test
+  public void testDeserializeReleasesExistingQueueAndSnapshotCache() throws 
Exception {
+    final Path temporaryDirectory = 
Files.createTempDirectory("pipe-listening-queue-snapshot");
+    final File snapshotFile = 
temporaryDirectory.resolve("queue.snapshot").toFile();
+    final TestPipeListeningQueue emptyQueue = new TestPipeListeningQueue();
+    final TestPipeListeningQueue queue = new TestPipeListeningQueue();
+    final EnrichedEvent queuedEvent = Mockito.mock(EnrichedEvent.class);
+    final PipeSnapshotEvent cachedSnapshot = 
Mockito.mock(PipeSnapshotEvent.class);
+    try {
+      Assert.assertTrue(emptyQueue.serializeToFile(snapshotFile));
+
+      queue.open();
+      queue.addEvent(queuedEvent);
+      queue.addSnapshots(Collections.singletonList(cachedSnapshot));
+      Assert.assertEquals(1, queue.getSize());
+      Assert.assertEquals(1, 
queue.findAvailableSnapshots(false).getRight().size());
+
+      queue.deserializeFromFile(snapshotFile);
+
+      Mockito.verify(queuedEvent)
+          .decreaseReferenceCount(AbstractPipeListeningQueue.class.getName(), 
false);
+      Mockito.verify(cachedSnapshot)
+          .decreaseReferenceCount(AbstractPipeListeningQueue.class.getName(), 
false);
+      Assert.assertEquals(0, queue.getSize());
+      
Assert.assertTrue(queue.findAvailableSnapshots(false).getRight().isEmpty());
+      Assert.assertFalse(queue.isOpened());
+    } finally {
+      Files.deleteIfExists(snapshotFile.toPath());
+      Files.deleteIfExists(temporaryDirectory);
+    }
+  }
+
+  private static final class TestPipeListeningQueue extends 
AbstractPipeListeningQueue {
+
+    private void addEvent(final EnrichedEvent event) {
+      tryListen(event);
+    }
+
+    private void addSnapshots(final List<PipeSnapshotEvent> snapshots) {
+      tryListen(snapshots);
+    }
+
+    @Override
+    protected ByteBuffer serializeToByteBuffer(final Event event) {
+      return ByteBuffer.allocate(0);
+    }
+
+    @Override
+    protected Event deserializeFromByteBuffer(final ByteBuffer byteBuffer) {
+      return null;
+    }
+  }
+}

Reply via email to