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 10c5f89ba52 [PIPE][Subscription] Fix pipe races and subscription WAL 
replay wakeup (#18904)
10c5f89ba52 is described below

commit 10c5f89ba52ec39cd6bfb6b8b735287cc0d935f9
Author: Caideyipi <[email protected]>
AuthorDate: Tue Oct 20 09:42:21 2026 +0800

    [PIPE][Subscription] Fix pipe races and subscription WAL replay wakeup 
(#18904)
    
    * [Test] Fix empty finite pipe auto-drop startup race
    
    * [PIPE] Preserve historical TsFile mods after compaction
    
    (cherry picked from commit f4b0879f814afba78e40f9265ae083f20ccfac70)
    
    * [Subscription] Wake WAL replay after realtime admission rejection
    
    * [Subscription] Wake replay outside pending queue lock
    
    * [Pipe] Refresh pipe user id when credentials change
---
 .../auto/enhanced/IoTDBPipeAutoDropIT.java         |  12 +-
 .../common/tsfile/PipeTsFileInsertionEvent.java    |  19 ++-
 .../resource/tsfile/PipeTsFileResourceManager.java |  13 +-
 ...istoricalDataRegionTsFileAndDeletionSource.java |  19 ++-
 .../execution/config/TableConfigTaskVisitor.java   |  46 ++++++++
 .../consensus/ConsensusPrefetchingQueue.java       |  39 +++---
 .../pipe/event/PipeTsFileInsertionEventTest.java   | 108 +++++++++++++++++
 .../resource/PipeTsFileResourceManagerTest.java    |  52 ++++++++
 .../config/PipeAuthenticationIdentityTest.java     | 131 +++++++++++++++++++++
 .../consensus/ConsensusPrefetchingQueueTest.java   |   5 +-
 10 files changed, 405 insertions(+), 39 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/treemodel/auto/enhanced/IoTDBPipeAutoDropIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/treemodel/auto/enhanced/IoTDBPipeAutoDropIT.java
index a3e5d32cb72..19b30f628d9 100644
--- 
a/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/treemodel/auto/enhanced/IoTDBPipeAutoDropIT.java
+++ 
b/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/treemodel/auto/enhanced/IoTDBPipeAutoDropIT.java
@@ -175,16 +175,8 @@ public class IoTDBPipeAutoDropIT extends 
AbstractPipeDualTreeModelAutoIT {
                       .setExtractorAttributes(realtimeSourceAttributes))
               .getCode());
 
-      Assert.assertEquals(
-          TSStatusCode.SUCCESS_STATUS.getStatusCode(),
-          client.startPipe("query_pipe_without_data_region").getCode());
-      Assert.assertEquals(
-          TSStatusCode.SUCCESS_STATUS.getStatusCode(),
-          client.startPipe("history_pipe_without_data_region").getCode());
-      Assert.assertEquals(
-          TSStatusCode.SUCCESS_STATUS.getStatusCode(),
-          client.startPipe("realtime_pipe_without_data_region").getCode());
-
+      // Pipes start automatically after creation. Explicitly starting finite 
pipes can race with
+      // their auto-drop when they have no DataRegions to process.
       await()
           .pollInSameThread()
           .pollInterval(1L, TimeUnit.SECONDS)
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEvent.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEvent.java
index 9690eed5d75..a83a027627b 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEvent.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/PipeTsFileInsertionEvent.java
@@ -55,6 +55,7 @@ import 
org.apache.iotdb.db.pipe.resource.memory.PipeMemoryManager.TsFileParserMe
 import org.apache.iotdb.db.pipe.resource.tsfile.PipeTsFileResourceManager;
 import 
org.apache.iotdb.db.pipe.source.dataregion.realtime.assigner.PipeTsFileEpochProgressIndexKeeper;
 import org.apache.iotdb.db.storageengine.dataregion.memtable.TsFileProcessor;
+import 
org.apache.iotdb.db.storageengine.dataregion.modification.ModificationFile;
 import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
 import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent;
 import org.apache.iotdb.pipe.api.event.dml.insertion.TsFileInsertionEvent;
@@ -274,7 +275,9 @@ public class PipeTsFileInsertionEvent extends 
PipeInsertionEvent
     // hard-link it to each pipe dir
     this.tsFile = Objects.isNull(tsFile) ? resource.getTsFile() : tsFile;
 
-    this.isWithMod = isWithMod && resource.anyModFileExists();
+    // Historical files and their mods have already been pinned. Compaction 
may have removed
+    // the original resource before this event is created, so inspect the 
pinned mods later.
+    this.isWithMod = isWithMod && (isGeneratedByHistoricalExtractor || 
resource.anyModFileExists());
     this.modFile = this.isWithMod ? resource.getExclusiveModFile().getFile() : 
null;
     // TODO: process the shared mod file
     this.sharedModFile =
@@ -463,9 +466,22 @@ public class PipeTsFileInsertionEvent extends 
PipeInsertionEvent
         PipeTsFileResourceManager.getPipeTsFileResourcePipeName(pipeName, 
creationTime);
     final File originalTsFile = tsFile;
     final File originalModFile = modFile;
+    final boolean originalIsWithMod = isWithMod;
     File increasedTsFile = null;
     boolean increased = false;
     try {
+      if (isGeneratedByHistoricalExtractor) {
+        tsFile =
+            PipeTsFileResourceManager.getHardlinkOrCopiedFileInPipeDir(
+                tsFile, pipeTsFileResourcePipeName);
+        if (isWithMod) {
+          modFile = ModificationFile.getExclusiveMods(tsFile);
+          isWithMod = modFile.exists();
+          if (!isWithMod) {
+            modFile = null;
+          }
+        }
+      }
       increasedTsFile =
           PipeDataNodeResourceManager.tsfile()
               .increaseFileReference(tsFile, true, pipeTsFileResourcePipeName);
@@ -490,6 +506,7 @@ public class PipeTsFileInsertionEvent extends 
PipeInsertionEvent
       }
       tsFile = originalTsFile;
       modFile = originalModFile;
+      isWithMod = originalIsWithMod;
       LOGGER.warn(
           String.format(
               
DataNodePipeMessages.INCREASE_REFERENCE_COUNT_TSFILE_OR_MODFILE_ERROR_HOLDER_FMT,
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/tsfile/PipeTsFileResourceManager.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/tsfile/PipeTsFileResourceManager.java
index db981663681..02898d17b4b 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/tsfile/PipeTsFileResourceManager.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/tsfile/PipeTsFileResourceManager.java
@@ -427,14 +427,17 @@ public class PipeTsFileResourceManager {
         : hardlinkOrCopiedFileToTsFilePublicResourceMap;
   }
 
-  public void pinTsFileResource(
+  /** Returns whether a companion mod-file reference was acquired. */
+  public boolean pinTsFileResource(
       final TsFileResource resource, final boolean withMods, final @Nullable 
String pipeName)
       throws IOException {
     final File pinnedTsFile = increaseFileReference(resource.getTsFile(), 
true, pipeName);
     try {
       if (withMods && resource.getExclusiveModFile().exists()) {
         increaseFileReference(resource.getExclusiveModFile().getFile(), false, 
pipeName);
+        return true;
       }
+      return false;
     } catch (final IOException | RuntimeException e) {
       // Pinning is a two-file operation.  Do not leave the TsFile pinned when 
the mod file cannot
       // be pinned (for example, when the pipe directory is temporarily 
unavailable).
@@ -448,9 +451,7 @@ public class PipeTsFileResourceManager {
   }
 
   public void unpinTsFileResource(
-      final TsFileResource resource,
-      final boolean shouldTransferModFile,
-      final @Nullable String pipeName)
+      final TsFileResource resource, final @Nullable String pipeName, final 
boolean pinnedModFile)
       throws IOException {
     Exception firstException = null;
     try {
@@ -462,7 +463,9 @@ public class PipeTsFileResourceManager {
 
     // Always attempt the mod-file cleanup even when resolving/decreasing the 
TsFile fails.  A
     // failed first cleanup must not strand the second reference.
-    if (shouldTransferModFile && resource.exclusiveModFileExists()) {
+    // Use the state captured when pinning. Compaction can remove the original 
mod file, and
+    // an event can acquire mod-file references even when no mod file was 
originally pinned.
+    if (pinnedModFile) {
       try {
         decreaseFileReference(
             
getHardlinkOrCopiedFileInPipeDir(resource.getExclusiveModFile().getFile(), 
pipeName),
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/historical/PipeHistoricalDataRegionTsFileAndDeletionSource.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/historical/PipeHistoricalDataRegionTsFileAndDeletionSource.java
index a28598ddec1..041d33c2faf 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/historical/PipeHistoricalDataRegionTsFileAndDeletionSource.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/historical/PipeHistoricalDataRegionTsFileAndDeletionSource.java
@@ -175,6 +175,7 @@ public class PipeHistoricalDataRegionTsFileAndDeletionSource
   private Queue<PersistentResource> pendingQueue;
   private final Map<TsFileResource, Set<String>> 
filteredTsFileResources2TableNames =
       new HashMap<>();
+  private final Set<TsFileResource> resourcesWithPinnedModFile = new 
HashSet<>();
   private final Map<PersistentResource, Long> 
pendingResource2ReplicateIndexForIoTV2 =
       new HashMap<>();
   private final Set<PersistentResource> historicalProgressReportResources = 
new HashSet<>();
@@ -800,8 +801,11 @@ public class 
PipeHistoricalDataRegionTsFileAndDeletionSource
                 // Pin the resource, in case the file is removed by compaction 
or anything.
                 // Will unpin it after the PipeTsFileInsertionEvent is created 
and pinned.
                 try {
-                  PipeDataNodeResourceManager.tsfile()
-                      .pinTsFileResource(resource, shouldTransferModFile, 
pipeNameWithCreationTime);
+                  if (PipeDataNodeResourceManager.tsfile()
+                      .pinTsFileResource(
+                          resource, shouldTransferModFile, 
pipeNameWithCreationTime)) {
+                    resourcesWithPinnedModFile.add(resource);
+                  }
                   return false;
                 } catch (final IOException e) {
                   ++statistics.pinFailedCount;
@@ -1217,7 +1221,8 @@ public class 
PipeHistoricalDataRegionTsFileAndDeletionSource
     } finally {
       try {
         PipeDataNodeResourceManager.tsfile()
-            .unpinTsFileResource(resource, shouldTransferModFile, 
pipeNameWithCreationTime);
+            .unpinTsFileResource(
+                resource, pipeNameWithCreationTime, 
resourcesWithPinnedModFile.remove(resource));
       } catch (final IOException e) {
         LOGGER.warn(
             
DataNodePipeMessages.PIPE_FAILED_TO_UNPIN_SKIPPED_HISTORICAL_TSFILERESOURCE,
@@ -1329,7 +1334,8 @@ public class 
PipeHistoricalDataRegionTsFileAndDeletionSource
       if (shouldUnpinResource) {
         try {
           PipeDataNodeResourceManager.tsfile()
-              .unpinTsFileResource(resource, shouldTransferModFile, 
pipeNameWithCreationTime);
+              .unpinTsFileResource(
+                  resource, pipeNameWithCreationTime, 
resourcesWithPinnedModFile.remove(resource));
         } catch (final IOException e) {
           LOGGER.warn(
               
DataNodePipeMessages.PIPE_FAILED_TO_UNPIN_TSFILERESOURCE_AFTER_CREATING,
@@ -1454,7 +1460,9 @@ public class 
PipeHistoricalDataRegionTsFileAndDeletionSource
               try {
                 PipeDataNodeResourceManager.tsfile()
                     .unpinTsFileResource(
-                        (TsFileResource) resource, shouldTransferModFile, 
pipeNameWithCreationTime);
+                        (TsFileResource) resource,
+                        pipeNameWithCreationTime,
+                        resourcesWithPinnedModFile.remove(resource));
               } catch (final IOException e) {
                 LOGGER.warn(
                     
DataNodePipeMessages.PIPE_FAILED_TO_UNPIN_TSFILERESOURCE_AFTER_DROPPING,
@@ -1468,6 +1476,7 @@ public class 
PipeHistoricalDataRegionTsFileAndDeletionSource
       pendingQueue = null;
     }
     pendingResource2ReplicateIndexForIoTV2.clear();
+    resourcesWithPinnedModFile.clear();
     historicalProgressReportResources.clear();
     pendingHistoricalProgressIndexToReport = null;
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
index 2af8eccc173..f91969ae32a 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java
@@ -1370,6 +1370,15 @@ public class TableConfigTaskVisitor implements 
AstVisitor<IConfigTask, MPPQueryC
                   
.FAILED_TO_S_PIPE_S_IN_IOTDB_SOURCE_PASSWORD_MUST_BE_SET_WHEN_THE_USERNAME_IS_SPECIFIED,
               isAlter ? DataNodeQueryMessages.ALTER : 
DataNodeQueryMessages.CREATE,
               pipeName));
+    } else {
+      updatePipeUserIdFromUsername(
+          replacedSourceAttributes,
+          sourceParameters,
+          PipeSourceConstant.SOURCE_IOTDB_USER_ID,
+          PipeSourceConstant.EXTRACTOR_IOTDB_USER_KEY,
+          PipeSourceConstant.SOURCE_IOTDB_USER_KEY,
+          PipeSourceConstant.EXTRACTOR_IOTDB_USERNAME_KEY,
+          PipeSourceConstant.SOURCE_IOTDB_USERNAME_KEY);
     }
   }
 
@@ -1442,6 +1451,15 @@ public class TableConfigTaskVisitor implements 
AstVisitor<IConfigTask, MPPQueryC
                   
.FAILED_TO_S_PIPE_S_IN_WRITE_BACK_SINK_PASSWORD_MUST_BE_SET_WHEN_THE_USERNAME_IS,
               isAlter ? DataNodeQueryMessages.ALTER : 
DataNodeQueryMessages.CREATE,
               pipeName));
+    } else {
+      updatePipeUserIdFromUsername(
+          connectorAttributes,
+          connectorParameters,
+          PipeSinkConstant.SINK_IOTDB_USER_ID,
+          PipeSinkConstant.CONNECTOR_IOTDB_USER_KEY,
+          PipeSinkConstant.SINK_IOTDB_USER_KEY,
+          PipeSinkConstant.CONNECTOR_IOTDB_USERNAME_KEY,
+          PipeSinkConstant.SINK_IOTDB_USERNAME_KEY);
     }
   }
 
@@ -1510,6 +1528,14 @@ public class TableConfigTaskVisitor implements 
AstVisitor<IConfigTask, MPPQueryC
         PipeSourceConstant.SOURCE_IOTDB_PASSWORD_KEY)) {
       sourceAttributes.put(
           SystemConstant.SOURCE_AUTHENTICATION_INJECTED_KEY, 
Boolean.FALSE.toString());
+      updatePipeUserIdFromUsername(
+          sourceAttributes,
+          sourceParameters,
+          PipeSourceConstant.SOURCE_IOTDB_USER_ID,
+          PipeSourceConstant.EXTRACTOR_IOTDB_USER_KEY,
+          PipeSourceConstant.SOURCE_IOTDB_USER_KEY,
+          PipeSourceConstant.EXTRACTOR_IOTDB_USERNAME_KEY,
+          PipeSourceConstant.SOURCE_IOTDB_USERNAME_KEY);
     }
   }
 
@@ -1524,6 +1550,26 @@ public class TableConfigTaskVisitor implements 
AstVisitor<IConfigTask, MPPQueryC
         PipeSinkConstant.CONNECTOR_IOTDB_PASSWORD_KEY,
         PipeSinkConstant.SINK_IOTDB_PASSWORD_KEY)) {
       sinkAttributes.put(SystemConstant.SINK_AUTHENTICATION_INJECTED_KEY, 
Boolean.FALSE.toString());
+      updatePipeUserIdFromUsername(
+          sinkAttributes,
+          sinkParameters,
+          PipeSinkConstant.SINK_IOTDB_USER_ID,
+          PipeSinkConstant.CONNECTOR_IOTDB_USER_KEY,
+          PipeSinkConstant.SINK_IOTDB_USER_KEY,
+          PipeSinkConstant.CONNECTOR_IOTDB_USERNAME_KEY,
+          PipeSinkConstant.SINK_IOTDB_USERNAME_KEY);
+    }
+  }
+
+  private static void updatePipeUserIdFromUsername(
+      final Map<String, String> attributes,
+      final PipeParameters parameters,
+      final String userIdAttributeKey,
+      final String... usernameAttributeKeys) {
+    final String username = parameters.getStringByKeys(usernameAttributeKeys);
+    if (username != null) {
+      attributes.put(
+          userIdAttributeKey, 
String.valueOf(AuthorityChecker.getUserId(username).orElse(-1L)));
     }
   }
 
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueue.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueue.java
index 5d0443c6996..743beeeb7ad 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueue.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueue.java
@@ -528,34 +528,39 @@ public class ConsensusPrefetchingQueue {
           Objects.isNull(reason) ? 
SubscriptionQueueRejectionReason.QUEUE_CAPACITY : reason;
       lastOfferRejectionReason.set(effectiveReason);
       rejectionListener.accept(effectiveReason);
-      return false;
+      return effectiveReason != 
SubscriptionQueueRejectionReason.INACTIVE_OR_CLOSED;
     }
 
     @Override
     public boolean offer(final IndexedConsensusRequest request) {
       final boolean offered;
+      boolean wakeupRequired = false;
       synchronized (this) {
         final SubscriptionQueueRejectionReason admissionReason = 
admissionSupplier.get();
         if (admissionReason != SubscriptionQueueRejectionReason.NONE) {
-          return reject(admissionReason);
-        }
-        if (!requestMemoryManager.reserve(request)) {
-          return 
reject(SubscriptionQueueRejectionReason.CONSENSUS_REQUEST_MEMORY_LIMIT);
-        }
-        try {
-          offered = super.offer(request);
-        } catch (final Throwable t) {
-          requestMemoryManager.free(request);
-          throw t;
-        }
-        if (offered) {
-          retainedRequestBytes.addAndGet(request.getRetainedMemorySize());
+          wakeupRequired = reject(admissionReason);
+          offered = false;
+        } else if (!requestMemoryManager.reserve(request)) {
+          wakeupRequired = 
reject(SubscriptionQueueRejectionReason.CONSENSUS_REQUEST_MEMORY_LIMIT);
+          offered = false;
         } else {
-          requestMemoryManager.free(request);
-          reject(SubscriptionQueueRejectionReason.QUEUE_CAPACITY);
+          try {
+            offered = super.offer(request);
+          } catch (final Throwable t) {
+            requestMemoryManager.free(request);
+            throw t;
+          }
+          if (offered) {
+            retainedRequestBytes.addAndGet(request.getRetainedMemorySize());
+          } else {
+            requestMemoryManager.free(request);
+            wakeupRequired = 
reject(SubscriptionQueueRejectionReason.QUEUE_CAPACITY);
+          }
         }
       }
-      if (offered) {
+      // The rejected request remains available in WAL. Wake the replay worker 
so a queue that has
+      // already gone dormant does not wait for a later successful realtime 
offer to make progress.
+      if (offered || wakeupRequired) {
         wakeupHook.run();
       }
       return offered;
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/PipeTsFileInsertionEventTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/PipeTsFileInsertionEventTest.java
index 5d5be1dbade..61e68055f90 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/PipeTsFileInsertionEventTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/event/PipeTsFileInsertionEventTest.java
@@ -35,12 +35,18 @@ import org.apache.iotdb.commons.utils.FileUtils;
 import org.apache.iotdb.db.auth.AuthorityChecker;
 import 
org.apache.iotdb.db.pipe.event.common.tsfile.PipeCompactedTsFileInsertionEvent;
 import org.apache.iotdb.db.pipe.event.common.tsfile.PipeTsFileInsertionEvent;
+import org.apache.iotdb.db.pipe.resource.PipeDataNodeResourceManager;
+import org.apache.iotdb.db.pipe.resource.tsfile.PipeTsFileResourceManager;
 import 
org.apache.iotdb.db.pipe.source.dataregion.realtime.assigner.PipeTsFileEpochProgressIndexKeeper;
 import org.apache.iotdb.db.queryengine.plan.relational.security.AccessControl;
 import 
org.apache.iotdb.db.queryengine.plan.relational.security.TreeAccessCheckContext;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.RelationalAuthorStatement;
 import org.apache.iotdb.db.queryengine.plan.statement.Statement;
 import 
org.apache.iotdb.db.storageengine.dataregion.compaction.utils.CompactionTestFileWriter;
+import 
org.apache.iotdb.db.storageengine.dataregion.modification.DeletionPredicate;
+import 
org.apache.iotdb.db.storageengine.dataregion.modification.ModificationFile;
+import 
org.apache.iotdb.db.storageengine.dataregion.modification.TableDeletionEntry;
+import 
org.apache.iotdb.db.storageengine.dataregion.modification.TagPredicate.FullExactMatch;
 import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
 import 
org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResourceStatus;
 import 
org.apache.iotdb.db.storageengine.dataregion.tsfile.generator.TsFileNameGenerator;
@@ -72,6 +78,108 @@ import static 
org.apache.iotdb.commons.pipe.datastructure.pattern.TreePattern.bu
 import static 
org.apache.iotdb.db.auth.AuthorityChecker.NO_PERMISSION_PROMOTION;
 
 public class PipeTsFileInsertionEventTest {
+  @Test
+  public void testHistoricalModsSurviveOriginalFileRemoval() throws Exception {
+    assertHistoricalModsAfterOriginalFileRemoval(true, true);
+  }
+
+  @Test
+  public void testHistoricalFileWithoutModsSurvivesOriginalFileRemoval() 
throws Exception {
+    assertHistoricalModsAfterOriginalFileRemoval(false, true);
+  }
+
+  @Test
+  public void testHistoricalModsRemainDisabledAfterOriginalFileRemoval() 
throws Exception {
+    assertHistoricalModsAfterOriginalFileRemoval(true, false);
+  }
+
+  private void assertHistoricalModsAfterOriginalFileRemoval(
+      final boolean hasMods, final boolean transferMods) throws Exception {
+    final File tempDir = 
Files.createTempDirectory("pipeHistoricalMods").toFile();
+    final String pipeName = "historical_mods";
+    final long creationTime = 1L;
+    final String resourcePipeName =
+        PipeTsFileResourceManager.getPipeTsFileResourcePipeName(pipeName, 
creationTime);
+    final PipeTsFileResourceManager manager = 
PipeDataNodeResourceManager.tsfile();
+    PipeTsFileInsertionEvent event = null;
+    boolean increased = false;
+    File pinnedTsFile = null;
+    File pinnedModFile = null;
+    try {
+      final File dataDir = new File(tempDir, "unsequence/db1/0/0");
+      Assert.assertTrue(dataDir.mkdirs());
+      final TsFileResource resource =
+          createNonEmptyTsFileResource(dataDir, "1-1-0-0.tsfile", 1L, 0);
+      resource.setStatusForTest(TsFileResourceStatus.NORMAL);
+      final TableDeletionEntry deletion =
+          new TableDeletionEntry(
+              new DeletionPredicate(
+                  "t1",
+                  new FullExactMatch(
+                      IDeviceID.Factory.DEFAULT_FACTORY.create(new String[] 
{"t1", "d1"}))),
+              new TimeRange(1L, 1L));
+      if (hasMods) {
+        resource.getExclusiveModFile().write(deletion);
+        resource.getExclusiveModFile().close();
+      }
+      manager.pinTsFileResource(resource, transferMods, resourcePipeName);
+      pinnedTsFile =
+          PipeTsFileResourceManager.getHardlinkOrCopiedFileInPipeDir(
+              resource.getTsFile(), resourcePipeName);
+      pinnedModFile = ModificationFile.getExclusiveMods(pinnedTsFile);
+
+      // Compaction removes the original after historical selection has pinned 
both files.
+      Assert.assertTrue(resource.remove());
+      Assert.assertFalse(resource.anyModFileExists());
+      Assert.assertEquals(hasMods && transferMods, pinnedModFile.exists());
+      event =
+          new PipeTsFileInsertionEvent(
+              true,
+              "db1",
+              resource,
+              null,
+              transferMods,
+              false,
+              true,
+              Collections.singleton("t1"),
+              pipeName,
+              creationTime,
+              null,
+              null,
+              new TablePattern(true, null, null),
+              null,
+              null,
+              null,
+              true,
+              Long.MIN_VALUE,
+              Long.MAX_VALUE);
+      increased = 
event.internallyIncreaseResourceReferenceCount(getClass().getName());
+      Assert.assertTrue(increased);
+      Assert.assertEquals(hasMods && transferMods, event.isWithMod());
+      if (hasMods && transferMods) {
+        Assert.assertEquals(pinnedModFile, event.getModFile());
+        Assert.assertEquals(
+            Collections.singletonList(deletion),
+            ModificationFile.readAllModifications(event.getTsFile(), true));
+      } else {
+        Assert.assertNull(event.getModFile());
+      }
+    } finally {
+      if (increased) {
+        event.internallyDecreaseResourceReferenceCount(getClass().getName());
+      } else if (event != null) {
+        event.close();
+      }
+      if (pinnedModFile != null) {
+        manager.decreaseFileReference(pinnedModFile, resourcePipeName);
+      }
+      if (pinnedTsFile != null) {
+        manager.decreaseFileReference(pinnedTsFile, resourcePipeName);
+      }
+      FileUtils.deleteFileOrDirectory(tempDir);
+    }
+  }
+
   @Test
   public void testAuthCheck() throws Exception {
     final AccessControl oldControl = AuthorityChecker.getAccessControl();
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/PipeTsFileResourceManagerTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/PipeTsFileResourceManagerTest.java
index d4b8c45f0f2..b823f0fe2bf 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/PipeTsFileResourceManagerTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/PipeTsFileResourceManagerTest.java
@@ -28,6 +28,7 @@ import 
org.apache.iotdb.db.pipe.resource.tsfile.PipeTsFileResourceManager;
 import org.apache.iotdb.db.storageengine.dataregion.modification.ModEntry;
 import 
org.apache.iotdb.db.storageengine.dataregion.modification.ModificationFile;
 import 
org.apache.iotdb.db.storageengine.dataregion.modification.TreeDeletionEntry;
+import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
 
 import org.apache.tsfile.enums.TSDataType;
 import org.apache.tsfile.file.metadata.enums.TSEncoding;
@@ -246,6 +247,57 @@ public class PipeTsFileResourceManagerTest {
     Assert.assertFalse(Files.exists(originModFile.toPath()));
   }
 
+  @Test
+  public void testUnpinAfterOriginalModsRemoved() throws Exception {
+    final TsFileResource resource = new TsFileResource(new File(TS_FILE_NAME));
+    resource
+        .getExclusiveModFile()
+        .write(new TreeDeletionEntry(new MeasurementPath("root.lemming.**"), 
0, Long.MAX_VALUE));
+    resource.getExclusiveModFile().close();
+
+    final boolean pinnedModFile =
+        pipeTsFileResourceManager.pinTsFileResource(resource, true, PIPE_NAME);
+    Assert.assertTrue(pinnedModFile);
+    final File pipeTsFile =
+        
PipeTsFileResourceManager.getHardlinkOrCopiedFileInPipeDir(resource.getTsFile(),
 PIPE_NAME);
+    final File pipeMods = ModificationFile.getExclusiveMods(pipeTsFile);
+    Assert.assertEquals(1, 
pipeTsFileResourceManager.getFileReferenceCount(pipeMods, PIPE_NAME));
+
+    Assert.assertTrue(resource.remove());
+    Assert.assertFalse(resource.exclusiveModFileExists());
+    Assert.assertTrue(pipeMods.exists());
+    pipeTsFileResourceManager.unpinTsFileResource(resource, PIPE_NAME, 
pinnedModFile);
+
+    Assert.assertEquals(0, 
pipeTsFileResourceManager.getFileReferenceCount(pipeTsFile, PIPE_NAME));
+    Assert.assertEquals(0, 
pipeTsFileResourceManager.getFileReferenceCount(pipeMods, PIPE_NAME));
+    Assert.assertEquals(0, 
pipeTsFileResourceManager.getFileReferenceCount(pipeMods, null));
+    Assert.assertFalse(pipeMods.exists());
+  }
+
+  @Test
+  public void testUnpinDoesNotReleaseModsCreatedAfterPinning() throws 
Exception {
+    final TsFileResource resource = new TsFileResource(new File(TS_FILE_NAME));
+    final boolean pinnedModFile =
+        pipeTsFileResourceManager.pinTsFileResource(resource, true, PIPE_NAME);
+    Assert.assertFalse(pinnedModFile);
+
+    // An event can acquire mods created after historical selection pinned the 
TsFile alone.
+    resource
+        .getExclusiveModFile()
+        .write(new TreeDeletionEntry(new MeasurementPath("root.lemming.**"), 
0, Long.MAX_VALUE));
+    resource.getExclusiveModFile().close();
+    final File eventMods =
+        pipeTsFileResourceManager.increaseFileReference(
+            resource.getExclusiveModFile().getFile(), false, PIPE_NAME);
+    pipeTsFileResourceManager.unpinTsFileResource(resource, PIPE_NAME, 
pinnedModFile);
+
+    Assert.assertEquals(1, 
pipeTsFileResourceManager.getFileReferenceCount(eventMods, PIPE_NAME));
+    Assert.assertTrue(eventMods.exists());
+    pipeTsFileResourceManager.decreaseFileReference(eventMods, PIPE_NAME);
+    Assert.assertEquals(0, 
pipeTsFileResourceManager.getFileReferenceCount(eventMods, PIPE_NAME));
+    Assert.assertFalse(eventMods.exists());
+  }
+
   @Test
   public void testConcurrentIncreaseTsFile() throws Exception {
     assertConcurrentIncreaseFileReference(new File(TS_FILE_NAME), true);
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/execution/config/PipeAuthenticationIdentityTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/execution/config/PipeAuthenticationIdentityTest.java
new file mode 100644
index 00000000000..741dd0171ba
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/execution/config/PipeAuthenticationIdentityTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.queryengine.plan.execution.config;
+
+import org.apache.iotdb.commons.audit.UserEntity;
+import org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant;
+import org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant;
+import org.apache.iotdb.commons.pipe.config.constant.SystemConstant;
+import org.apache.iotdb.db.auth.AuthorityChecker;
+import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+
+@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", 
"javax.management.*"})
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(AuthorityChecker.class)
+public class PipeAuthenticationIdentityTest {
+
+  @Test
+  public void testModifiedCredentialsReplaceInjectedSuperUserId() {
+    mockStatic(AuthorityChecker.class);
+    when(AuthorityChecker.getUserId("thulab")).thenReturn(Optional.of(10001L));
+
+    final Map<String, String> sourceAttributes = new HashMap<>();
+    TableConfigTaskVisitor.checkAndEnrichSourceUser(
+        "pipe", sourceAttributes, new UserEntity(0L, "root", "host"), false);
+
+    final Map<String, String> sourceChanges = new HashMap<>();
+    sourceChanges.put("username", "thulab");
+    sourceChanges.put("password", "password");
+    
TableConfigTaskVisitor.markSourceAuthenticationAsExplicitIfNecessary(sourceChanges);
+    new PipeParameters(sourceAttributes)
+        .addOrReplaceEquivalentAttributes(new PipeParameters(sourceChanges));
+
+    final Map<String, String> sinkAttributes = new HashMap<>();
+    sinkAttributes.put(PipeSinkConstant.SINK_KEY, "write-back-sink");
+    TableConfigTaskVisitor.checkAndEnrichSinkUser(
+        "pipe", sinkAttributes, new UserEntity(0L, "root", "host"), false);
+
+    final Map<String, String> sinkChanges = new HashMap<>();
+    sinkChanges.put(PipeSinkConstant.CONNECTOR_IOTDB_USER_KEY, "thulab");
+    sinkChanges.put(PipeSinkConstant.CONNECTOR_IOTDB_PASSWORD_KEY, "password");
+    
TableConfigTaskVisitor.markSinkAuthenticationAsExplicitIfNecessary(sinkChanges);
+    new PipeParameters(sinkAttributes)
+        .addOrReplaceEquivalentAttributes(new PipeParameters(sinkChanges));
+
+    assertEquals("10001", 
sourceAttributes.get(PipeSourceConstant.SOURCE_IOTDB_USER_ID));
+    assertEquals(
+        Boolean.FALSE.toString(),
+        
sourceAttributes.get(SystemConstant.SOURCE_AUTHENTICATION_INJECTED_KEY));
+    assertEquals("10001", 
sinkAttributes.get(PipeSinkConstant.SINK_IOTDB_USER_ID));
+    assertEquals(
+        Boolean.FALSE.toString(),
+        sinkAttributes.get(SystemConstant.SINK_AUTHENTICATION_INJECTED_KEY));
+  }
+
+  @Test
+  public void testExplicitCredentialsUseTheirOwnUserIdOnCreateAndReplace() {
+    mockStatic(AuthorityChecker.class);
+    when(AuthorityChecker.getUserId("thulab")).thenReturn(Optional.of(10001L));
+
+    for (final boolean isAlter : new boolean[] {false, true}) {
+      final Map<String, String> sourceAttributes = new HashMap<>();
+      sourceAttributes.put(PipeSourceConstant.EXTRACTOR_IOTDB_USER_KEY, 
"thulab");
+      sourceAttributes.put(PipeSourceConstant.EXTRACTOR_IOTDB_PASSWORD_KEY, 
"password");
+      TableConfigTaskVisitor.checkAndEnrichSourceUser(
+          "pipe", sourceAttributes, new UserEntity(0L, "root", "host"), 
isAlter);
+
+      final Map<String, String> sinkAttributes = new HashMap<>();
+      sinkAttributes.put(PipeSinkConstant.SINK_KEY, "write-back-sink");
+      sinkAttributes.put(PipeSinkConstant.SINK_IOTDB_USERNAME_KEY, "thulab");
+      sinkAttributes.put(PipeSinkConstant.SINK_IOTDB_PASSWORD_KEY, "password");
+      TableConfigTaskVisitor.checkAndEnrichSinkUser(
+          "pipe", sinkAttributes, new UserEntity(0L, "root", "host"), isAlter);
+
+      assertEquals("10001", 
sourceAttributes.get(PipeSourceConstant.SOURCE_IOTDB_USER_ID));
+      assertEquals("10001", 
sinkAttributes.get(PipeSinkConstant.SINK_IOTDB_USER_ID));
+    }
+  }
+
+  @Test
+  public void testPasswordOnlyModificationPreservesExistingUserId() {
+    final Map<String, String> sourceAttributes = new HashMap<>();
+    sourceAttributes.put(PipeSourceConstant.SOURCE_IOTDB_USER_ID, "10001");
+    final Map<String, String> sourceChanges = new HashMap<>();
+    sourceChanges.put("password", "new-password");
+    
TableConfigTaskVisitor.markSourceAuthenticationAsExplicitIfNecessary(sourceChanges);
+    new PipeParameters(sourceAttributes)
+        .addOrReplaceEquivalentAttributes(new PipeParameters(sourceChanges));
+
+    final Map<String, String> sinkAttributes = new HashMap<>();
+    sinkAttributes.put(PipeSinkConstant.SINK_IOTDB_USER_ID, "10001");
+    final Map<String, String> sinkChanges = new HashMap<>();
+    sinkChanges.put("password", "new-password");
+    
TableConfigTaskVisitor.markSinkAuthenticationAsExplicitIfNecessary(sinkChanges);
+    new PipeParameters(sinkAttributes)
+        .addOrReplaceEquivalentAttributes(new PipeParameters(sinkChanges));
+
+    assertEquals("10001", 
sourceAttributes.get(PipeSourceConstant.SOURCE_IOTDB_USER_ID));
+    assertEquals("10001", 
sinkAttributes.get(PipeSinkConstant.SINK_IOTDB_USER_ID));
+  }
+}
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueueTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueueTest.java
index 61493475115..e3de8b31279 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueueTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueueTest.java
@@ -1197,11 +1197,12 @@ public class ConsensusPrefetchingQueueTest {
     final double previousQueueRatio = 
memoryManager.getMaxMemoryRatioForQueue();
     final AtomicLongMemoryBlock testMemoryBlock =
         new AtomicLongMemoryBlock("SubscriptionPendingTest", null, 100L);
+    final AtomicInteger firstQueueWakeups = new AtomicInteger();
     final BlockingQueue<IndexedConsensusRequest> firstQueue =
         (BlockingQueue<IndexedConsensusRequest>)
             constructor.newInstance(
                 8,
-                (Runnable) () -> {},
+                (Runnable) () -> firstQueueWakeups.incrementAndGet(),
                 (Supplier<SubscriptionQueueRejectionReason>)
                     () -> SubscriptionQueueRejectionReason.NONE,
                 (Consumer<SubscriptionQueueRejectionReason>) reason -> {});
@@ -1218,12 +1219,14 @@ public class ConsensusPrefetchingQueueTest {
     try {
       final IndexedConsensusRequest sharedRequest = createSizedRequest(1L, 
10L, 30);
       assertTrue(firstQueue.offer(sharedRequest));
+      assertEquals(1, firstQueueWakeups.get());
       assertTrue(secondQueue.offer(sharedRequest));
       assertEquals(40L, testMemoryBlock.getUsedMemoryInBytes());
       assertEquals(40L, retainedBytesMethod.invoke(firstQueue));
       assertEquals(40L, retainedBytesMethod.invoke(secondQueue));
 
       assertFalse(firstQueue.offer(createSizedRequest(2L, 10L, 30)));
+      assertEquals(2, firstQueueWakeups.get());
       assertEquals(
           SubscriptionQueueRejectionReason.CONSENSUS_REQUEST_MEMORY_LIMIT,
           ((SubscriptionQueueAdmission) firstQueue).getLastRejectionReason());

Reply via email to