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 6527fb0e8ca [Pipe] Wake parser workers when admission is available 
(#18843) (#18865)
6527fb0e8ca is described below

commit 6527fb0e8ca471f49747c38acfed96d0a20e1163
Author: Caideyipi <[email protected]>
AuthorDate: Wed Oct 14 16:12:37 2026 +0800

    [Pipe] Wake parser workers when admission is available (#18843) (#18865)
    
    * Fix pipe parser admission wake-ups
    
    * Avoid reflective test dependency
    
    (cherry picked from commit aec1ad6139cb4dd1450725f53418cf40c26efb4e)
---
 .../subtask/processor/PipeProcessorSubtask.java    |   4 +
 .../PipeProcessorSubtaskExecutionGuard.java        |  16 +++
 .../processor/PipeProcessorSubtaskWorker.java      |  29 +++-
 .../common/tsfile/PipeTsFileInsertionEvent.java    |   4 +
 .../pipe/metric/overview/PipeResourceMetrics.java  |  47 +++++++
 .../db/pipe/resource/memory/PipeMemoryManager.java | 154 +++++++++++++++------
 .../PipeProcessorSubtaskExecutionGuardTest.java    |  10 +-
 .../processor/PipeProcessorSubtaskWorkerTest.java  | 154 +++++++++++++++++++++
 .../resource/memory/PipeMemoryManagerTest.java     |  30 ++++
 .../iotdb/commons/service/metric/enums/Metric.java |   5 +
 10 files changed, 403 insertions(+), 50 deletions(-)

diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtask.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtask.java
index 84485bc0c04..6e20498b784 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtask.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtask.java
@@ -271,6 +271,10 @@ public class PipeProcessorSubtask extends 
PipeReportableSubtask {
     // and the worker will be submitted to the executor
   }
 
+  void setWorkerWakeUpCallback(final Runnable wakeUpCallback) {
+    executionGuard.setWorkerWakeUpCallback(wakeUpCallback);
+  }
+
   @Override
   protected void onAllowSubmittingSelf() {
     executionGuard.start();
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtaskExecutionGuard.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtaskExecutionGuard.java
index a4eeab3482d..8510f58ab08 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtaskExecutionGuard.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtaskExecutionGuard.java
@@ -19,6 +19,8 @@
 
 package org.apache.iotdb.db.pipe.agent.task.subtask.processor;
 
+import org.apache.iotdb.db.pipe.metric.overview.PipeResourceMetrics;
+
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 
@@ -38,6 +40,7 @@ public class PipeProcessorSubtaskExecutionGuard {
   private final AtomicBoolean isRunning = new AtomicBoolean(false);
   private final AtomicLong executionEpoch = new AtomicLong(0);
   private final ThreadLocal<Long> invocationEpoch = new ThreadLocal<>();
+  private volatile Runnable workerWakeUpCallback;
 
   public PipeProcessorSubtaskExecutionGuard() {
     this(true);
@@ -55,9 +58,21 @@ public class PipeProcessorSubtaskExecutionGuard {
     return enabled;
   }
 
+  void setWorkerWakeUpCallback(final Runnable workerWakeUpCallback) {
+    this.workerWakeUpCallback = workerWakeUpCallback;
+  }
+
+  public Runnable getWorkerWakeUpCallback() {
+    return workerWakeUpCallback;
+  }
+
   void start() {
     if (enabled) {
       isRunning.set(true);
+      final Runnable wakeUpCallback = workerWakeUpCallback;
+      if (wakeUpCallback != null) {
+        wakeUpCallback.run();
+      }
     }
   }
 
@@ -105,6 +120,7 @@ public class PipeProcessorSubtaskExecutionGuard {
   }
 
   public void yieldIfParserNotAdmitted() {
+    PipeResourceMetrics.getInstance().recordTsFileParserAdmissionYield();
     throw PipeProcessorSubtaskYieldException.parserNotAdmitted();
   }
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtaskWorker.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtaskWorker.java
index ad1548d9432..4572d217e91 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtaskWorker.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtaskWorker.java
@@ -49,6 +49,9 @@ public class PipeProcessorSubtaskWorker extends 
WrappedRunnable {
   private long sleepingTimeInMilliSecond = 50;
 
   private final Set<PipeProcessorSubtask> subtasks;
+  private final Object wakeUpMonitor = new Object();
+  private final Runnable wakeUpCallback = this::wakeUp;
+  private boolean isWakeUpRequested;
 
   private volatile Thread workerThread;
   private volatile PipeProcessorSubtask currentSubtask;
@@ -115,10 +118,18 @@ public class PipeProcessorSubtaskWorker extends 
WrappedRunnable {
     return canSleepBeforeNextRound;
   }
 
-  private void sleepIfNecessary(final boolean canSleepBeforeNextRound) {
+  @VisibleForTesting
+  void sleepIfNecessary(final boolean canSleepBeforeNextRound) {
     if (canSleepBeforeNextRound) {
       try {
-        Thread.sleep(sleepingTimeInMilliSecond);
+        synchronized (wakeUpMonitor) {
+          // A parser can become eligible before this worker starts waiting. 
Keep that notification
+          // until the idle round consumes it, instead of sleeping with the 
fair queue head ready.
+          if (!isWakeUpRequested) {
+            wakeUpMonitor.wait(sleepingTimeInMilliSecond);
+          }
+          isWakeUpRequested = false;
+        }
       } catch (final InterruptedException e) {
         LOGGER.warn("subtask worker is interrupted", e);
         Thread.currentThread().interrupt();
@@ -128,6 +139,11 @@ public class PipeProcessorSubtaskWorker extends 
WrappedRunnable {
     }
   }
 
+  @VisibleForTesting
+  void setSleepingTimeInMilliSecond(final long sleepingTimeInMilliSecond) {
+    this.sleepingTimeInMilliSecond = sleepingTimeInMilliSecond;
+  }
+
   private void adjustSleepingTimeIfNecessary() {
     if (totalRoundInAdjustmentInterval % 
SLEEP_INTERVAL_ADJUSTMENT_ROUND_INTERVAL == 0) {
       final double workingRatioInAdjustmentInterval =
@@ -147,7 +163,16 @@ public class PipeProcessorSubtaskWorker extends 
WrappedRunnable {
   }
 
   public void schedule(final PipeProcessorSubtask pipeProcessorSubtask) {
+    pipeProcessorSubtask.setWorkerWakeUpCallback(wakeUpCallback);
     subtasks.add(pipeProcessorSubtask);
+    wakeUp();
+  }
+
+  private void wakeUp() {
+    synchronized (wakeUpMonitor) {
+      isWakeUpRequested = true;
+      wakeUpMonitor.notifyAll();
+    }
   }
 
   void watchLongRunningEvent() {
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 6258dfee1fd..0d379db7831 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
@@ -923,6 +923,10 @@ public class PipeTsFileInsertionEvent extends EnrichedEvent
       final PipeProcessorSubtaskExecutionGuard processorExecutionGuard,
       final long expectedParserStateGeneration)
       throws InterruptedException {
+    // Register before enqueueing: memory may be released as soon as admission 
fails, before this
+    // invocation yields or its worker enters the idle wait.
+    tsFileParserMemoryReservationKey.setWakeUpCallback(
+        processorExecutionGuard.getWorkerWakeUpCallback());
     if (!processorExecutionGuard.isEnabled()) {
       return waitForResourceEnough4Parsing(
           (long) ((1 + Math.random()) * 20 * 1000), 
expectedParserStateGeneration);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/overview/PipeResourceMetrics.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/overview/PipeResourceMetrics.java
index dd41d032f80..85ce5047b85 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/overview/PipeResourceMetrics.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/overview/PipeResourceMetrics.java
@@ -29,9 +29,12 @@ import org.apache.iotdb.metrics.AbstractMetricService;
 import org.apache.iotdb.metrics.impl.DoNothingMetricManager;
 import org.apache.iotdb.metrics.metricsets.IMetricSet;
 import org.apache.iotdb.metrics.type.Counter;
+import org.apache.iotdb.metrics.type.Timer;
 import org.apache.iotdb.metrics.utils.MetricLevel;
 import org.apache.iotdb.metrics.utils.MetricType;
 
+import java.util.concurrent.TimeUnit;
+
 public class PipeResourceMetrics implements IMetricSet {
 
   private static final String PIPE_USED_MEMORY = "PipeUsedMemory";
@@ -44,6 +47,11 @@ public class PipeResourceMetrics implements IMetricSet {
   private static final String PIPE_TOTAL_MEMORY = "PipeTotalMemory";
   private static final String PIPE_FLOATING_MEMORY = "PipeFloatingMemory";
   private Counter diskIOCounter = DoNothingMetricManager.DO_NOTHING_COUNTER;
+  private volatile Counter tsFileParserAdmissionRejectCounter =
+      DoNothingMetricManager.DO_NOTHING_COUNTER;
+  private volatile Counter tsFileParserAdmissionYieldCounter =
+      DoNothingMetricManager.DO_NOTHING_COUNTER;
+  private volatile Timer tsFileParserAdmissionWaitTimer = 
DoNothingMetricManager.DO_NOTHING_TIMER;
 
   //////////////////////////// bindTo & unbindFrom (metric framework) 
////////////////////////////
 
@@ -102,6 +110,25 @@ public class PipeResourceMetrics implements IMetricSet {
     diskIOCounter =
         metricService.getOrCreateCounter(
             Metric.PIPE_TSFILE_SEND_DISK_IO.toString(), MetricLevel.IMPORTANT);
+    tsFileParserAdmissionRejectCounter =
+        metricService.getOrCreateCounter(
+            Metric.PIPE_TSFILE_PARSER_ADMISSION_REJECT.toString(), 
MetricLevel.IMPORTANT);
+    tsFileParserAdmissionYieldCounter =
+        metricService.getOrCreateCounter(
+            Metric.PIPE_TSFILE_PARSER_ADMISSION_YIELD.toString(), 
MetricLevel.IMPORTANT);
+    tsFileParserAdmissionWaitTimer =
+        metricService.getOrCreateTimer(
+            Metric.PIPE_TSFILE_PARSER_ADMISSION_WAIT.toString(), 
MetricLevel.IMPORTANT);
+    metricService.createAutoGauge(
+        Metric.PIPE_TSFILE_PARSER_IN_FLIGHT.toString(),
+        MetricLevel.IMPORTANT,
+        PipeDataNodeResourceManager.memory(),
+        PipeMemoryManager::getReservedTsFileParserCount);
+    metricService.createAutoGauge(
+        Metric.PIPE_TSFILE_PARSER_WAITING.toString(),
+        MetricLevel.IMPORTANT,
+        PipeDataNodeResourceManager.memory(),
+        PipeMemoryManager::getWaitingTsFileParserCount);
   }
 
   @Override
@@ -138,12 +165,32 @@ public class PipeResourceMetrics implements IMetricSet {
     metricService.remove(MetricType.AUTO_GAUGE, 
Metric.PIPE_PHANTOM_REFERENCE_COUNT.toString());
 
     metricService.remove(MetricType.COUNTER, 
Metric.PIPE_TSFILE_SEND_DISK_IO.toString());
+    metricService.remove(MetricType.COUNTER, 
Metric.PIPE_TSFILE_PARSER_ADMISSION_REJECT.toString());
+    metricService.remove(MetricType.COUNTER, 
Metric.PIPE_TSFILE_PARSER_ADMISSION_YIELD.toString());
+    metricService.remove(MetricType.TIMER, 
Metric.PIPE_TSFILE_PARSER_ADMISSION_WAIT.toString());
+    metricService.remove(MetricType.AUTO_GAUGE, 
Metric.PIPE_TSFILE_PARSER_IN_FLIGHT.toString());
+    metricService.remove(MetricType.AUTO_GAUGE, 
Metric.PIPE_TSFILE_PARSER_WAITING.toString());
+    tsFileParserAdmissionRejectCounter = 
DoNothingMetricManager.DO_NOTHING_COUNTER;
+    tsFileParserAdmissionYieldCounter = 
DoNothingMetricManager.DO_NOTHING_COUNTER;
+    tsFileParserAdmissionWaitTimer = DoNothingMetricManager.DO_NOTHING_TIMER;
   }
 
   public void recordDiskIO(final long bytes) {
     diskIOCounter.inc(bytes);
   }
 
+  public void recordTsFileParserAdmissionReject() {
+    tsFileParserAdmissionRejectCounter.inc();
+  }
+
+  public void recordTsFileParserAdmissionYield() {
+    tsFileParserAdmissionYieldCounter.inc();
+  }
+
+  public void recordTsFileParserAdmissionWait(final long waitTimeInNanos) {
+    tsFileParserAdmissionWaitTimer.update(waitTimeInNanos, 
TimeUnit.NANOSECONDS);
+  }
+
   //////////////////////////// singleton ////////////////////////////
 
   private static class PipeResourceMetricsHolder {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManager.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManager.java
index 5f03991d350..8fc3d49536a 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManager.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManager.java
@@ -23,6 +23,7 @@ import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeOutOfMemoryCriticalExc
 import org.apache.iotdb.commons.pipe.config.PipeConfig;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
 import org.apache.iotdb.db.pipe.agent.PipeDataNodeAgent;
+import org.apache.iotdb.db.pipe.metric.overview.PipeResourceMetrics;
 import 
org.apache.iotdb.db.pipe.resource.memory.strategy.ThresholdAllocationStrategy;
 
 import org.slf4j.Logger;
@@ -66,6 +67,7 @@ public class PipeMemoryManager {
   private volatile long usedMemorySizeInBytesOfTsFiles;
 
   private volatile long reservedTsFileParserCount;
+  private volatile long waitingTsFileParserCount;
 
   private final Map<PipeIdentity, Integer> reservedTsFileParserCountByPipe = 
new HashMap<>();
   private final Map<PipeRegionIdentity, Integer> 
reservedTsFileParserCountByPipeRegion =
@@ -109,30 +111,25 @@ public class PipeMemoryManager {
   // 3. The sum of the memory proportion occupied by the tablet memory block 
and the tsfile memory
   // block does not exceed TABLET_MEMORY_REJECT_THRESHOLD + 
TS_FILE_MEMORY_REJECT_THRESHOLD
 
-  private double allowedMaxMemorySizeInBytesOfTabletsAndTsFiles() {
-    return (PipeConfig.getInstance()
-                
.getPipeDataStructureTabletMemoryBlockAllocationRejectThreshold()
-            + PipeConfig.getInstance()
-                
.getPipeDataStructureTsFileMemoryBlockAllocationRejectThreshold())
-        * getTotalNonFloatingMemorySizeInBytes();
+  private double allowedMaxMemorySizeInBytesOfTabletsAndTsFiles(
+      final long totalNonFloatingMemorySizeInBytes) {
+    return 
(PIPE_CONFIG.getPipeDataStructureTabletMemoryBlockAllocationRejectThreshold()
+            + 
PIPE_CONFIG.getPipeDataStructureTsFileMemoryBlockAllocationRejectThreshold())
+        * totalNonFloatingMemorySizeInBytes;
   }
 
-  private double allowedMaxMemorySizeInBytesOfTablets() {
-    return (PipeConfig.getInstance()
-                
.getPipeDataStructureTabletMemoryBlockAllocationRejectThreshold()
-            + PipeConfig.getInstance()
-                    
.getPipeDataStructureTsFileMemoryBlockAllocationRejectThreshold()
-                / 2)
-        * getTotalNonFloatingMemorySizeInBytes();
+  private double allowedMaxMemorySizeInBytesOfTablets(
+      final long totalNonFloatingMemorySizeInBytes) {
+    return 
(PIPE_CONFIG.getPipeDataStructureTabletMemoryBlockAllocationRejectThreshold()
+            + 
PIPE_CONFIG.getPipeDataStructureTsFileMemoryBlockAllocationRejectThreshold() / 
2)
+        * totalNonFloatingMemorySizeInBytes;
   }
 
-  private double allowedMaxMemorySizeInBytesOfTsTiles() {
-    return (PipeConfig.getInstance()
-                
.getPipeDataStructureTsFileMemoryBlockAllocationRejectThreshold()
-            + PipeConfig.getInstance()
-                    
.getPipeDataStructureTabletMemoryBlockAllocationRejectThreshold()
-                / 2)
-        * getTotalNonFloatingMemorySizeInBytes();
+  private double allowedMaxMemorySizeInBytesOfTsTiles(
+      final long totalNonFloatingMemorySizeInBytes) {
+    return 
(PIPE_CONFIG.getPipeDataStructureTsFileMemoryBlockAllocationRejectThreshold()
+            + 
PIPE_CONFIG.getPipeDataStructureTabletMemoryBlockAllocationRejectThreshold() / 
2)
+        * totalNonFloatingMemorySizeInBytes;
   }
 
   private static long getTsFileParserMemorySizeInBytes() {
@@ -144,39 +141,48 @@ public class PipeMemoryManager {
     return reservedTsFileParserCount * getTsFileParserMemorySizeInBytes();
   }
 
-  private boolean isEnough4TabletParsingWithReservedParserMemory(final long 
extraMemoryInBytes) {
+  private boolean isEnough4TabletParsingWithReservedParserMemory(
+      final long extraMemoryInBytes, final long 
totalNonFloatingMemorySizeInBytes) {
     final double tabletMemoryWithParserMemory =
         (double) usedMemorySizeInBytesOfTablets
             + getReservedTsFileParserMemorySizeInBytes()
             + extraMemoryInBytes;
     return tabletMemoryWithParserMemory + (double) 
usedMemorySizeInBytesOfTsFiles
-            < EXCEED_PROTECT_THRESHOLD * 
allowedMaxMemorySizeInBytesOfTabletsAndTsFiles()
+            < EXCEED_PROTECT_THRESHOLD
+                * 
allowedMaxMemorySizeInBytesOfTabletsAndTsFiles(totalNonFloatingMemorySizeInBytes)
         && tabletMemoryWithParserMemory
-            < EXCEED_PROTECT_THRESHOLD * 
allowedMaxMemorySizeInBytesOfTablets();
+            < EXCEED_PROTECT_THRESHOLD
+                * 
allowedMaxMemorySizeInBytesOfTablets(totalNonFloatingMemorySizeInBytes);
   }
 
   private boolean isHardEnough4TabletParsingWithReservedParserMemory(
-      final long extraMemoryInBytes) {
+      final long extraMemoryInBytes, final long 
totalNonFloatingMemorySizeInBytes) {
     final double tabletMemoryWithParserMemory =
         (double) usedMemorySizeInBytesOfTablets
             + getReservedTsFileParserMemorySizeInBytes()
             + extraMemoryInBytes;
     return tabletMemoryWithParserMemory + (double) 
usedMemorySizeInBytesOfTsFiles
-            < allowedMaxMemorySizeInBytesOfTabletsAndTsFiles()
-        && tabletMemoryWithParserMemory < 
allowedMaxMemorySizeInBytesOfTablets();
+            < 
allowedMaxMemorySizeInBytesOfTabletsAndTsFiles(totalNonFloatingMemorySizeInBytes)
+        && tabletMemoryWithParserMemory
+            < 
allowedMaxMemorySizeInBytesOfTablets(totalNonFloatingMemorySizeInBytes);
   }
 
   public boolean isEnough4TabletParsing() {
+    final long totalNonFloatingMemorySizeInBytes = 
getTotalNonFloatingMemorySizeInBytes();
     return (double) usedMemorySizeInBytesOfTablets + (double) 
usedMemorySizeInBytesOfTsFiles
-            < EXCEED_PROTECT_THRESHOLD * 
allowedMaxMemorySizeInBytesOfTabletsAndTsFiles()
+            < EXCEED_PROTECT_THRESHOLD
+                * 
allowedMaxMemorySizeInBytesOfTabletsAndTsFiles(totalNonFloatingMemorySizeInBytes)
         && (double) usedMemorySizeInBytesOfTablets
-            < EXCEED_PROTECT_THRESHOLD * 
allowedMaxMemorySizeInBytesOfTablets();
+            < EXCEED_PROTECT_THRESHOLD
+                * 
allowedMaxMemorySizeInBytesOfTablets(totalNonFloatingMemorySizeInBytes);
   }
 
   private boolean isHardEnough4TabletParsing() {
+    final long totalNonFloatingMemorySizeInBytes = 
getTotalNonFloatingMemorySizeInBytes();
     return (double) usedMemorySizeInBytesOfTablets + (double) 
usedMemorySizeInBytesOfTsFiles
-            < allowedMaxMemorySizeInBytesOfTabletsAndTsFiles()
-        && (double) usedMemorySizeInBytesOfTablets < 
allowedMaxMemorySizeInBytesOfTablets();
+            < 
allowedMaxMemorySizeInBytesOfTabletsAndTsFiles(totalNonFloatingMemorySizeInBytes)
+        && (double) usedMemorySizeInBytesOfTablets
+            < 
allowedMaxMemorySizeInBytesOfTablets(totalNonFloatingMemorySizeInBytes);
   }
 
   public synchronized boolean tryReserveTsFileParserMemory(
@@ -202,16 +208,24 @@ public class PipeMemoryManager {
         reservedTsFileParserCountByPipeRegion.getOrDefault(pipeRegionIdentity, 
0);
     if (reservedTsFileParserCount >= globalLimit
         || reservedCountOfPipeRegion >= perPipeRegionLimit) {
+      PipeResourceMetrics.getInstance().recordTsFileParserAdmissionReject();
       notifyNextTsFileParserMemoryReservationInternal();
       return false;
     }
 
     final long parserMemorySizeInBytes = getTsFileParserMemorySizeInBytes();
+    // Reuse one live snapshot for both thresholds. The supplier traverses all 
pipe metadata, so
+    // repeatedly sampling it while holding this monitor amplifies contention 
with many pipes.
+    final long totalNonFloatingMemorySizeInBytes =
+        PIPE_MEMORY_MANAGEMENT_ENABLED ? 
getTotalNonFloatingMemorySizeInBytes() : 0;
     final boolean isSoftMemoryEnough =
         !PIPE_MEMORY_MANAGEMENT_ENABLED
-            || 
isEnough4TabletParsingWithReservedParserMemory(parserMemorySizeInBytes);
+            || isEnough4TabletParsingWithReservedParserMemory(
+                parserMemorySizeInBytes, totalNonFloatingMemorySizeInBytes);
     if (!isSoftMemoryEnough
-        && 
!isHardEnough4TabletParsingWithReservedParserMemory(parserMemorySizeInBytes)) {
+        && !isHardEnough4TabletParsingWithReservedParserMemory(
+            parserMemorySizeInBytes, totalNonFloatingMemorySizeInBytes)) {
+      PipeResourceMetrics.getInstance().recordTsFileParserAdmissionReject();
       return false;
     }
 
@@ -222,13 +236,17 @@ public class PipeMemoryManager {
     if (!pipeRegionIdentity.equals(nextPipeRegion)
         || requestsOfPipeRegion == null
         || !reservationKey.equals(requestsOfPipeRegion.iterator().next())) {
-      notifyNextTsFileParserMemoryReservationInternal();
+      PipeResourceMetrics.getInstance().recordTsFileParserAdmissionReject();
+      signalTsFileParserMemoryReservation(nextPipeRegion);
       return false;
     }
 
     removeTsFileParserReservationRequest(pipeRegionIdentity, reservationKey, 
true);
     if (wasRequestAlreadyWaiting) {
       lastAdmittedWaitingTsFileParserPipe = pipeIdentity;
+      PipeResourceMetrics.getInstance()
+          .recordTsFileParserAdmissionWait(
+              System.nanoTime() - reservationKey.waitingStartTimeInNanos);
     }
     reservedTsFileParserCount++;
     reservedTsFileParserCountByPipe.merge(pipeIdentity, 1, Integer::sum);
@@ -301,7 +319,12 @@ public class PipeMemoryManager {
               regionOrder.addLast(key);
               return new LinkedHashSet<>();
             });
-    return !requestsOfPipeRegion.add(reservationKey);
+    if (requestsOfPipeRegion.add(reservationKey)) {
+      waitingTsFileParserCount++;
+      reservationKey.waitingStartTimeInNanos = System.nanoTime();
+      return false;
+    }
+    return true;
   }
 
   public synchronized void notifyNextTsFileParserMemoryReservation() {
@@ -310,16 +333,20 @@ public class PipeMemoryManager {
 
   private void notifyNextTsFileParserMemoryReservationInternal() {
     final int globalLimit = Math.max(1, 
PIPE_CONFIG.getPipeTsFileParserInFlightMaxNum());
-    if (reservedTsFileParserCount >= globalLimit) {
+    if (waitingTsFileParserPipeOrder.isEmpty() || reservedTsFileParserCount >= 
globalLimit) {
       return;
     }
 
     final long parserMemorySizeInBytes = getTsFileParserMemorySizeInBytes();
+    final long totalNonFloatingMemorySizeInBytes =
+        PIPE_MEMORY_MANAGEMENT_ENABLED ? 
getTotalNonFloatingMemorySizeInBytes() : 0;
     final boolean isSoftMemoryEnough =
         !PIPE_MEMORY_MANAGEMENT_ENABLED
-            || 
isEnough4TabletParsingWithReservedParserMemory(parserMemorySizeInBytes);
+            || isEnough4TabletParsingWithReservedParserMemory(
+                parserMemorySizeInBytes, totalNonFloatingMemorySizeInBytes);
     if (!isSoftMemoryEnough
-        && 
!isHardEnough4TabletParsingWithReservedParserMemory(parserMemorySizeInBytes)) {
+        && !isHardEnough4TabletParsingWithReservedParserMemory(
+            parserMemorySizeInBytes, totalNonFloatingMemorySizeInBytes)) {
       return;
     }
 
@@ -328,6 +355,10 @@ public class PipeMemoryManager {
             1, Math.min(globalLimit, 
PIPE_CONFIG.getPipeTsFileParserInFlightMaxNumPerPipeRegion()));
     final PipeRegionIdentity nextPipeRegion =
         getNextEligibleTsFileParserPipeRegion(perPipeRegionLimit, 
!isSoftMemoryEnough);
+    signalTsFileParserMemoryReservation(nextPipeRegion);
+  }
+
+  private void signalTsFileParserMemoryReservation(final PipeRegionIdentity 
nextPipeRegion) {
     if (nextPipeRegion == null) {
       return;
     }
@@ -398,6 +429,7 @@ public class PipeMemoryManager {
     if (requestsOfPipeRegion == null || 
!requestsOfPipeRegion.remove(reservationKey)) {
       return;
     }
+    waitingTsFileParserCount--;
 
     final PipeIdentity pipeIdentity = pipeRegionIdentity.pipeIdentity;
     final ArrayDeque<PipeRegionIdentity> regionOrder =
@@ -431,6 +463,14 @@ public class PipeMemoryManager {
   public static final class TsFileParserMemoryReservation {
 
     private boolean isSignaled;
+    private volatile Runnable wakeUpCallback;
+    // Accessed only under the PipeMemoryManager monitor while this request is 
queued.
+    private long waitingStartTimeInNanos;
+
+    /** The callback must only wake the worker; it runs while the memory 
manager monitor is held. */
+    public void setWakeUpCallback(final Runnable wakeUpCallback) {
+      this.wakeUpCallback = wakeUpCallback;
+    }
 
     public synchronized void await(final long timeoutInMs) throws 
InterruptedException {
       if (!isSignaled) {
@@ -442,6 +482,10 @@ public class PipeMemoryManager {
     private synchronized void signal() {
       isSignaled = true;
       notify();
+      final Runnable callback = wakeUpCallback;
+      if (callback != null) {
+        callback.run();
+      }
     }
   }
 
@@ -464,7 +508,8 @@ public class PipeMemoryManager {
       return elapsedTimeInMs >= maxRetryTimeInMs;
     }
 
-    if (!isHardEnough4TabletParsingWithReservedParserMemory(0)) {
+    if (!isHardEnough4TabletParsingWithReservedParserMemory(
+        0, getTotalNonFloatingMemorySizeInBytes())) {
       return true;
     }
 
@@ -473,35 +518,44 @@ public class PipeMemoryManager {
   }
 
   public boolean isEnough4TsFileSlicing() {
+    final long totalNonFloatingMemorySizeInBytes = 
getTotalNonFloatingMemorySizeInBytes();
     return (double) usedMemorySizeInBytesOfTablets + (double) 
usedMemorySizeInBytesOfTsFiles
-            < EXCEED_PROTECT_THRESHOLD * 
allowedMaxMemorySizeInBytesOfTabletsAndTsFiles()
+            < EXCEED_PROTECT_THRESHOLD
+                * 
allowedMaxMemorySizeInBytesOfTabletsAndTsFiles(totalNonFloatingMemorySizeInBytes)
         && (double) usedMemorySizeInBytesOfTsFiles
-            < EXCEED_PROTECT_THRESHOLD * 
allowedMaxMemorySizeInBytesOfTsTiles();
+            < EXCEED_PROTECT_THRESHOLD
+                * 
allowedMaxMemorySizeInBytesOfTsTiles(totalNonFloatingMemorySizeInBytes);
   }
 
   private boolean isHardEnough4TsFileSlicing() {
+    final long totalNonFloatingMemorySizeInBytes = 
getTotalNonFloatingMemorySizeInBytes();
     return (double) usedMemorySizeInBytesOfTablets + (double) 
usedMemorySizeInBytesOfTsFiles
-            < allowedMaxMemorySizeInBytesOfTabletsAndTsFiles()
-        && (double) usedMemorySizeInBytesOfTsFiles < 
allowedMaxMemorySizeInBytesOfTsTiles();
+            < 
allowedMaxMemorySizeInBytesOfTabletsAndTsFiles(totalNonFloatingMemorySizeInBytes)
+        && (double) usedMemorySizeInBytesOfTsFiles
+            < 
allowedMaxMemorySizeInBytesOfTsTiles(totalNonFloatingMemorySizeInBytes);
   }
 
   private boolean isHardEnoughForResizing(
       final PipeMemoryBlock block, final long extraMemoryInBytes) {
+    if (!(block instanceof PipeTabletMemoryBlock) && !(block instanceof 
PipeTsFileMemoryBlock)) {
+      return true;
+    }
+    final long totalNonFloatingMemorySizeInBytes = 
getTotalNonFloatingMemorySizeInBytes();
     if (block instanceof PipeTabletMemoryBlock) {
       return (double) usedMemorySizeInBytesOfTablets
                   + (double) extraMemoryInBytes
                   + (double) usedMemorySizeInBytesOfTsFiles
-              < allowedMaxMemorySizeInBytesOfTabletsAndTsFiles()
+              < 
allowedMaxMemorySizeInBytesOfTabletsAndTsFiles(totalNonFloatingMemorySizeInBytes)
           && (double) usedMemorySizeInBytesOfTablets + (double) 
extraMemoryInBytes
-              < allowedMaxMemorySizeInBytesOfTablets();
+              < 
allowedMaxMemorySizeInBytesOfTablets(totalNonFloatingMemorySizeInBytes);
     }
     if (block instanceof PipeTsFileMemoryBlock) {
       return (double) usedMemorySizeInBytesOfTablets
                   + (double) usedMemorySizeInBytesOfTsFiles
                   + (double) extraMemoryInBytes
-              < allowedMaxMemorySizeInBytesOfTabletsAndTsFiles()
+              < 
allowedMaxMemorySizeInBytesOfTabletsAndTsFiles(totalNonFloatingMemorySizeInBytes)
           && (double) usedMemorySizeInBytesOfTsFiles + (double) 
extraMemoryInBytes
-              < allowedMaxMemorySizeInBytesOfTsTiles();
+              < 
allowedMaxMemorySizeInBytesOfTsTiles(totalNonFloatingMemorySizeInBytes);
     }
     return true;
   }
@@ -1077,6 +1131,14 @@ public class PipeMemoryManager {
     return usedMemorySizeInBytesOfTsFiles;
   }
 
+  public long getReservedTsFileParserCount() {
+    return reservedTsFileParserCount;
+  }
+
+  public long getWaitingTsFileParserCount() {
+    return waitingTsFileParserCount;
+  }
+
   public long getFreeMemorySizeInBytes() {
     return Math.max(0, getTotalNonFloatingMemorySizeInBytes() - 
usedMemorySizeInBytes);
   }
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtaskExecutionGuardTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtaskExecutionGuardTest.java
index b7c5fdc5542..a9e49892e83 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtaskExecutionGuardTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtaskExecutionGuardTest.java
@@ -90,7 +90,10 @@ public class PipeProcessorSubtaskExecutionGuardTest {
       final PipeTsFileInsertionEvent eventToConsume = event;
       final PipeProcessorSubtaskExecutionGuard executionGuard =
           new PipeProcessorSubtaskExecutionGuard();
+      final AtomicInteger wakeUpCount = new AtomicInteger();
+      executionGuard.setWorkerWakeUpCallback(wakeUpCount::incrementAndGet);
       executionGuard.start();
+      wakeUpCount.set(0);
       executionGuard.enter();
 
       final long startTimeInNanos = System.nanoTime();
@@ -108,10 +111,12 @@ public class PipeProcessorSubtaskExecutionGuardTest {
       Assert.assertTrue(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - 
startTimeInNanos) < 1000);
       executionGuard.exit();
 
-      executionGuard.stop();
-      event.cancelTsFileParserMemoryReservationIfPending();
+      // A yielded invocation must keep its fair queue position and receive 
the release signal.
       memoryManager.releaseTsFileParserMemory("blocker", 0, "0");
       isBlockerReserved = false;
+      Assert.assertEquals(1, wakeUpCount.get());
+      executionGuard.stop();
+      event.cancelTsFileParserMemoryReservationIfPending();
       isCompetitorReserved =
           memoryManager.tryReserveTsFileParserMemory("competitor", 0, "0", 
competitorReservation);
       Assert.assertTrue(isCompetitorReserved);
@@ -120,6 +125,7 @@ public class PipeProcessorSubtaskExecutionGuardTest {
 
       final AtomicInteger consumedTabletCount = new AtomicInteger(0);
       executionGuard.start();
+      Assert.assertEquals(2, wakeUpCount.get());
       executionGuard.enter();
       event.consumeTabletInsertionEventsWithRetry(
           parsedEvent -> {
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtaskWorkerTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtaskWorkerTest.java
index d1c34ccf471..d1d24c11837 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtaskWorkerTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/subtask/processor/PipeProcessorSubtaskWorkerTest.java
@@ -19,7 +19,12 @@
 
 package org.apache.iotdb.db.pipe.agent.task.subtask.processor;
 
+import org.apache.iotdb.commons.conf.CommonConfig;
+import org.apache.iotdb.commons.conf.CommonDescriptor;
 import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
+import org.apache.iotdb.db.pipe.resource.PipeDataNodeResourceManager;
+import org.apache.iotdb.db.pipe.resource.memory.PipeMemoryManager;
+import 
org.apache.iotdb.db.pipe.resource.memory.PipeMemoryManager.TsFileParserMemoryReservation;
 
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.ListeningScheduledExecutorService;
@@ -28,12 +33,22 @@ import org.junit.Assert;
 import org.junit.Test;
 import org.mockito.InOrder;
 
+import java.util.ArrayList;
 import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
 
+import static org.awaitility.Awaitility.await;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -42,6 +57,145 @@ import static org.mockito.Mockito.when;
 
 public class PipeProcessorSubtaskWorkerTest {
 
+  @Test(timeout = 30000)
+  public void testOneHundredWaitingPipesDoNotWaitForWorkerPolling() throws 
Exception {
+    final CommonConfig config = CommonDescriptor.getInstance().getConfig();
+    final PipeMemoryManager memoryManager = 
PipeDataNodeResourceManager.memory();
+    final long originalParserMemory = config.getPipeTsFileParserMemory();
+    final int originalGlobalLimit = config.getPipeTsFileParserInFlightMaxNum();
+    final int originalRegionLimit = 
config.getPipeTsFileParserInFlightMaxNumPerPipeRegion();
+    final List<PipeProcessorSubtaskWorker> workers = new ArrayList<>();
+    final List<AtomicReference<Thread>> workerThreads = new ArrayList<>();
+    final List<TsFileParserMemoryReservation> requests = new ArrayList<>();
+    final List<Future<?>> futures = new ArrayList<>();
+    final CountDownLatch processed = new CountDownLatch(100);
+    final ExecutorService executor = Executors.newFixedThreadPool(5);
+    final boolean[] reservedBlockers = new boolean[4];
+    try {
+      config.setPipeTsFileParserMemory(1);
+      config.setPipeTsFileParserInFlightMaxNum(4);
+      config.setPipeTsFileParserInFlightMaxNumPerPipeRegion(1);
+      for (int i = 0; i < 4; i++) {
+        Assert.assertTrue(
+            memoryManager.tryReserveTsFileParserMemory(
+                "blocker" + i, 0, "0", new TsFileParserMemoryReservation()));
+        reservedBlockers[i] = true;
+      }
+      for (int i = 0; i < 5; i++) {
+        final PipeProcessorSubtaskWorker worker =
+            new PipeProcessorSubtaskWorker(new LinkedHashSet<>());
+        workers.add(worker);
+        workerThreads.add(new AtomicReference<>());
+      }
+      for (int i = 0; i < 100; i++) {
+        final String pipeName = "waitingPipe" + i;
+        final TsFileParserMemoryReservation request = new 
TsFileParserMemoryReservation();
+        requests.add(request);
+        final PipeProcessorSubtask subtask = createRunnableSubtask(pipeName);
+        doAnswer(
+                invocation -> {
+                  request.setWakeUpCallback(invocation.getArgument(0));
+                  return null;
+                })
+            .when(subtask)
+            .setWorkerWakeUpCallback(any());
+        final AtomicBoolean hasProcessed = new AtomicBoolean();
+        when(subtask.call())
+            .thenAnswer(
+                invocation -> {
+                  if (hasProcessed.get()) {
+                    return false;
+                  }
+                  if (!memoryManager.tryReserveTsFileParserMemory(pipeName, 0, 
"0", request)) {
+                    throw 
PipeProcessorSubtaskYieldException.parserNotAdmitted();
+                  }
+                  hasProcessed.set(true);
+                  memoryManager.releaseTsFileParserMemory(pipeName, 0, "0");
+                  processed.countDown();
+                  return true;
+                });
+        workers.get(i % workers.size()).schedule(subtask);
+        
Assert.assertFalse(memoryManager.tryReserveTsFileParserMemory(pipeName, 0, "0", 
request));
+      }
+      Assert.assertEquals(100, memoryManager.getWaitingTsFileParserCount());
+      for (int i = 0; i < workers.size(); i++) {
+        final PipeProcessorSubtaskWorker worker = workers.get(i);
+        final AtomicReference<Thread> workerThread = workerThreads.get(i);
+        // Consume the scheduling notification before all workers become idle 
on admission.
+        worker.sleepIfNecessary(true);
+        // Make a missed wake-up observable without relying on a tight 
performance assertion.
+        worker.setSleepingTimeInMilliSecond(60000L);
+        futures.add(
+            executor.submit(
+                () -> {
+                  workerThread.set(Thread.currentThread());
+                  while (!Thread.currentThread().isInterrupted()) {
+                    worker.sleepIfNecessary(worker.runSubtasks());
+                  }
+                }));
+      }
+      await()
+          .atMost(5, TimeUnit.SECONDS)
+          .until(
+              () ->
+                  workerThreads.stream()
+                      .allMatch(
+                          thread ->
+                              thread.get() != null
+                                  && thread.get().getState() == 
Thread.State.TIMED_WAITING));
+      for (int i = 0; i < 4; i++) {
+        memoryManager.releaseTsFileParserMemory("blocker" + i, 0, "0");
+        reservedBlockers[i] = false;
+      }
+      Assert.assertTrue(
+          "Admission notifications must wake every queue head",
+          processed.await(5, TimeUnit.SECONDS));
+      Assert.assertEquals(0, memoryManager.getWaitingTsFileParserCount());
+      Assert.assertEquals(0, memoryManager.getReservedTsFileParserCount());
+    } finally {
+      futures.forEach(future -> future.cancel(true));
+      executor.shutdownNow();
+      Assert.assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
+      for (int i = 0; i < requests.size(); i++) {
+        memoryManager.cancelTsFileParserMemoryReservation(
+            "waitingPipe" + i, 0, "0", requests.get(i));
+      }
+      for (int i = 0; i < reservedBlockers.length; i++) {
+        if (reservedBlockers[i]) {
+          memoryManager.releaseTsFileParserMemory("blocker" + i, 0, "0");
+        }
+      }
+      config.setPipeTsFileParserMemory(originalParserMemory);
+      config.setPipeTsFileParserInFlightMaxNum(originalGlobalLimit);
+      
config.setPipeTsFileParserInFlightMaxNumPerPipeRegion(originalRegionLimit);
+    }
+  }
+
+  @Test(timeout = 10000)
+  public void testNotificationBeforeIdleWaitIsNotLost() throws Exception {
+    final PipeProcessorSubtaskWorker worker = new 
PipeProcessorSubtaskWorker(new LinkedHashSet<>());
+    worker.setSleepingTimeInMilliSecond(60000L);
+    final PipeProcessorSubtask subtask = createRunnableSubtask("waitingPipe");
+    final AtomicReference<Runnable> wakeUpCallback = new AtomicReference<>();
+    doAnswer(
+            invocation -> {
+              wakeUpCallback.set(invocation.getArgument(0));
+              return null;
+            })
+        .when(subtask)
+        .setWorkerWakeUpCallback(any());
+    worker.schedule(subtask);
+    worker.sleepIfNecessary(true);
+    final ExecutorService executor = Executors.newSingleThreadExecutor();
+    try {
+      wakeUpCallback.get().run();
+      executor.submit(() -> worker.sleepIfNecessary(true)).get(3, 
TimeUnit.SECONDS);
+    } finally {
+      executor.shutdownNow();
+      Assert.assertTrue(executor.awaitTermination(3, TimeUnit.SECONDS));
+    }
+  }
+
   @Test
   public void testYieldingPipesDoNotBlockAnotherPipeOnSameWorker() throws 
Exception {
     final PipeProcessorSubtaskWorker worker = new 
PipeProcessorSubtaskWorker(new LinkedHashSet<>());
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManagerTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManagerTest.java
index 3a50002667e..6b681c5dd49 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManagerTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManagerTest.java
@@ -38,6 +38,7 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 public class PipeMemoryManagerTest {
 
@@ -74,6 +75,35 @@ public class PipeMemoryManagerTest {
     commonConfig.setPipeTsFileParserMemory(originalParserMemoryInBytes);
   }
 
+  @Test
+  public void testAdmissionNotificationOnlyWakesEligibleWaitingPipe() {
+    commonConfig.setPipeTsFileParserInFlightMaxNum(1);
+    commonConfig.setPipeTsFileParserInFlightMaxNumPerPipeRegion(1);
+    final Reservation blocker = new Reservation("blocker", 0);
+    final Reservation pipeA = new Reservation("pipeA", 1);
+    final Reservation pipeB = new Reservation("pipeB", 2);
+    final AtomicInteger pipeAWakeUps = new AtomicInteger();
+    final AtomicInteger pipeBWakeUps = new AtomicInteger();
+    pipeA.key.setWakeUpCallback(pipeAWakeUps::incrementAndGet);
+    pipeB.key.setWakeUpCallback(pipeBWakeUps::incrementAndGet);
+    Assert.assertTrue(tryAcquire(blocker));
+    Assert.assertFalse(tryAcquire(pipeA));
+    Assert.assertFalse(tryAcquire(pipeB));
+    Assert.assertEquals(2, memoryManager.getWaitingTsFileParserCount());
+    Assert.assertEquals(0, pipeAWakeUps.get());
+    Assert.assertEquals(0, pipeBWakeUps.get());
+
+    release(blocker);
+    Assert.assertEquals(1, pipeAWakeUps.get());
+    Assert.assertEquals(0, pipeBWakeUps.get());
+    Assert.assertTrue(tryAcquire(pipeA));
+    Assert.assertEquals(1, memoryManager.getWaitingTsFileParserCount());
+    release(pipeA);
+    Assert.assertEquals(1, pipeBWakeUps.get());
+    Assert.assertTrue(tryAcquire(pipeB));
+    Assert.assertEquals(0, memoryManager.getWaitingTsFileParserCount());
+  }
+
   @Test
   public void testWaitingPipesAreAdmittedInRoundRobinOrder() {
     commonConfig.setPipeTsFileParserInFlightMaxNum(1);
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/enums/Metric.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/enums/Metric.java
index 8a01eb3a1bf..2a53b1f940b 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/enums/Metric.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/service/metric/enums/Metric.java
@@ -165,6 +165,11 @@ public enum Metric {
   PIPE_HEARTBEAT_EVENT("pipe_heartbeat_event"),
   PIPE_EXTRACTOR_TSFILE_EPOCH_STATE("pipe_extractor_tsfile_epoch_state"),
   PIPE_MEM("pipe_mem"),
+  PIPE_TSFILE_PARSER_ADMISSION_REJECT("pipe_tsfile_parser_admission_reject"),
+  PIPE_TSFILE_PARSER_ADMISSION_YIELD("pipe_tsfile_parser_admission_yield"),
+  PIPE_TSFILE_PARSER_ADMISSION_WAIT("pipe_tsfile_parser_admission_wait"),
+  PIPE_TSFILE_PARSER_IN_FLIGHT("pipe_tsfile_parser_in_flight"),
+  PIPE_TSFILE_PARSER_WAITING("pipe_tsfile_parser_waiting"),
   PIPE_LINKED_TSFILE_COUNT("pipe_linked_tsfile_count"),
   PIPE_LINKED_TSFILE_SIZE("pipe_linked_tsfile_size"),
   PIPE_PHANTOM_REFERENCE_COUNT("pipe_phantom_reference_count"),

Reply via email to