3636import java .util .Collections ;
3737import java .util .HashMap ;
3838import java .util .HashSet ;
39+ import java .util .LinkedHashMap ;
3940import java .util .LinkedHashSet ;
4041import java .util .List ;
4142import java .util .Map ;
@@ -74,6 +75,9 @@ public class PipeMemoryManager {
7475 private final Map <PipeIdentity , Integer > reservedTsFileParserCountByPipe = new HashMap <>();
7576 private final Map <PipeRegionIdentity , Integer > reservedTsFileParserCountByPipeRegion =
7677 new HashMap <>();
78+ private final Map <PipeRegionIdentity , Map <TsFileParserMemoryReservation , Runnable >>
79+ reservedTsFileParserWorkersByPipeRegion = new HashMap <>();
80+ private final Map <Runnable , Integer > reservedTsFileParserCountByWorker = new HashMap <>();
7781 private final Map <PipeRegionIdentity , LinkedHashSet <TsFileParserMemoryReservation >>
7882 waitingTsFileParserRequestsByPipeRegion = new HashMap <>();
7983 private final Map <PipeIdentity , ArrayDeque <PipeRegionIdentity >>
@@ -242,8 +246,10 @@ public synchronized boolean tryReserveTsFileParserMemory(
242246 1 , Math .min (globalLimit , PIPE_CONFIG .getPipeTsFileParserInFlightMaxNumPerPipeRegion ()));
243247 final int reservedCountOfPipeRegion =
244248 reservedTsFileParserCountByPipeRegion .getOrDefault (pipeRegionIdentity , 0 );
249+ final Runnable worker = reservationKey .wakeUpCallback ;
245250 if (reservedTsFileParserCount >= globalLimit
246- || reservedCountOfPipeRegion >= perPipeRegionLimit ) {
251+ || reservedCountOfPipeRegion >= perPipeRegionLimit
252+ || (worker != null && reservedTsFileParserCountByWorker .containsKey (worker ))) {
247253 PipeResourceMetrics .getInstance ()
248254 .recordTsFileParserAdmissionReject (TsFileParserAdmissionRejectReason .CAP );
249255 notifyNextTsFileParserMemoryReservationInternal ();
@@ -291,6 +297,12 @@ public synchronized boolean tryReserveTsFileParserMemory(
291297 reservedTsFileParserCount ++;
292298 reservedTsFileParserCountByPipe .merge (pipeIdentity , 1 , Integer ::sum );
293299 reservedTsFileParserCountByPipeRegion .put (pipeRegionIdentity , reservedCountOfPipeRegion + 1 );
300+ reservedTsFileParserWorkersByPipeRegion
301+ .computeIfAbsent (pipeRegionIdentity , key -> new LinkedHashMap <>())
302+ .put (reservationKey , worker );
303+ if (worker != null ) {
304+ reservedTsFileParserCountByWorker .merge (worker , 1 , Integer ::sum );
305+ }
294306 notifyNextTsFileParserMemoryReservationInternal ();
295307 return true ;
296308 }
@@ -312,12 +324,23 @@ public synchronized void cancelTsFileParserMemoryReservation(
312324
313325 public synchronized void releaseTsFileParserMemory (
314326 final String pipeName , final long creationTime , final String dataRegionId ) {
327+ releaseTsFileParserMemory (pipeName , creationTime , dataRegionId , null );
328+ }
329+
330+ public synchronized void releaseTsFileParserMemory (
331+ final String pipeName ,
332+ final long creationTime ,
333+ final String dataRegionId ,
334+ final TsFileParserMemoryReservation reservationKey ) {
315335 final PipeIdentity pipeIdentity = new PipeIdentity (pipeName , creationTime );
316336 final PipeRegionIdentity pipeRegionIdentity =
317337 new PipeRegionIdentity (pipeIdentity , dataRegionId );
318338 final int reservedCountOfPipeRegion =
319339 reservedTsFileParserCountByPipeRegion .getOrDefault (pipeRegionIdentity , 0 );
320- if (reservedCountOfPipeRegion <= 0 ) {
340+ final Map <TsFileParserMemoryReservation , Runnable > workersOfPipeRegion =
341+ reservedTsFileParserWorkersByPipeRegion .get (pipeRegionIdentity );
342+ if (reservedCountOfPipeRegion <= 0
343+ || (reservationKey != null && !workersOfPipeRegion .containsKey (reservationKey ))) {
321344 LOGGER .warn (
322345 "Failed to release TsFile parser memory for pipe {} (creation time {}) in DataRegion {} because no reservation exists." ,
323346 pipeName ,
@@ -326,6 +349,19 @@ public synchronized void releaseTsFileParserMemory(
326349 return ;
327350 }
328351
352+ final Runnable worker =
353+ workersOfPipeRegion .remove (
354+ reservationKey != null
355+ ? reservationKey
356+ : workersOfPipeRegion .keySet ().iterator ().next ());
357+ if (workersOfPipeRegion .isEmpty ()) {
358+ reservedTsFileParserWorkersByPipeRegion .remove (pipeRegionIdentity );
359+ }
360+ if (worker != null ) {
361+ reservedTsFileParserCountByWorker .computeIfPresent (
362+ worker , (key , count ) -> count == 1 ? null : count - 1 );
363+ }
364+
329365 if (reservedCountOfPipeRegion == 1 ) {
330366 reservedTsFileParserCountByPipeRegion .remove (pipeRegionIdentity );
331367 } else {
@@ -406,6 +442,7 @@ private List<TsFileParserMemoryReservation> getEligibleTsFileParserMemoryReserva
406442 final List <TsFileParserMemoryReservation > eligibleRequests = new ArrayList <>();
407443 final Map <PipeRegionIdentity , Integer > selectedRequestsByPipeRegion = new HashMap <>();
408444 final Set <PipeIdentity > selectedPipes = new HashSet <>();
445+ final Set <Runnable > selectedWorkers = new HashSet <>();
409446 PipeIdentity lastSelectedPipe = lastAdmittedWaitingTsFileParserPipe ;
410447 // Expose one fair turn per available slot, instead of serializing all free slots behind a
411448 // single worker. Account for every earlier turn's memory even if its worker has not run yet.
@@ -429,6 +466,7 @@ private List<TsFileParserMemoryReservation> getEligibleTsFileParserMemoryReserva
429466 lastSelectedPipe ,
430467 selectedRequestsByPipeRegion ,
431468 selectedPipes ,
469+ selectedWorkers ,
432470 true );
433471 final PipeRegionIdentity nextRegionWhenNoOtherPipeIsEligible =
434472 nextPipeRegion == null
@@ -438,6 +476,7 @@ private List<TsFileParserMemoryReservation> getEligibleTsFileParserMemoryReserva
438476 lastSelectedPipe ,
439477 selectedRequestsByPipeRegion ,
440478 selectedPipes ,
479+ selectedWorkers ,
441480 false )
442481 : nextPipeRegion ;
443482 if (nextRegionWhenNoOtherPipeIsEligible == null ) {
@@ -453,7 +492,11 @@ private List<TsFileParserMemoryReservation> getEligibleTsFileParserMemoryReserva
453492 i --) {
454493 requests .next ();
455494 }
456- eligibleRequests .add (requests .next ());
495+ final TsFileParserMemoryReservation request = requests .next ();
496+ eligibleRequests .add (request );
497+ if (request .wakeUpCallback != null ) {
498+ selectedWorkers .add (request .wakeUpCallback );
499+ }
457500 selectedRequestsByPipeRegion .merge (nextRegionWhenNoOtherPipeIsEligible , 1 , Integer ::sum );
458501 selectedPipes .add (nextRegionWhenNoOtherPipeIsEligible .pipeIdentity );
459502 lastSelectedPipe = nextRegionWhenNoOtherPipeIsEligible .pipeIdentity ;
@@ -467,6 +510,7 @@ private PipeRegionIdentity getNextEligibleTsFileParserPipeRegion(
467510 final PipeIdentity lastSelectedPipe ,
468511 final Map <PipeRegionIdentity , Integer > selectedRequestsByPipeRegion ,
469512 final Set <PipeIdentity > selectedPipes ,
513+ final Set <Runnable > selectedWorkers ,
470514 final boolean requireUnselectedPipe ) {
471515 PipeRegionIdentity firstEligiblePipeRegion = null ;
472516 boolean hasVisitedLastAdmittedPipe = lastSelectedPipe == null ;
@@ -499,6 +543,20 @@ private PipeRegionIdentity getNextEligibleTsFileParserPipeRegion(
499543 && reservedTsFileParserCountByPipeRegion .getOrDefault (pipeRegionIdentity , 0 )
500544 + selectedCount
501545 < perPipeRegionLimit ) {
546+ final java .util .Iterator <TsFileParserMemoryReservation > requests =
547+ waitingTsFileParserRequestsByPipeRegion .get (pipeRegionIdentity ).iterator ();
548+ for (int i = 0 ; i < selectedCount ; i ++) {
549+ requests .next ();
550+ }
551+ final Runnable worker = requests .next ().wakeUpCallback ;
552+ // A processor worker executes its subtasks serially. Leaving multiple fair turns for
553+ // that worker strands free parser slots while other workers have runnable requests.
554+ // Requests without a processor worker (for example subscriptions) remain independent.
555+ if (worker != null
556+ && (reservedTsFileParserCountByWorker .containsKey (worker )
557+ || selectedWorkers .contains (worker ))) {
558+ continue ;
559+ }
502560 if (eligiblePipeRegion == null ) {
503561 eligiblePipeRegion = pipeRegionIdentity ;
504562 }
@@ -579,7 +637,10 @@ public static final class TsFileParserMemoryReservation {
579637 // Accessed only under the PipeMemoryManager monitor while this request is queued.
580638 private long waitingStartTimeInNanos ;
581639
582- /** The callback must only wake the worker; it runs while the memory manager monitor is held. */
640+ /**
641+ * The callback must only wake the worker; it runs while the memory manager monitor is held.
642+ * Requests on the same serial processor worker must share the same callback instance.
643+ */
583644 public void setWakeUpCallback (final Runnable wakeUpCallback ) {
584645 this .wakeUpCallback = wakeUpCallback ;
585646 }
0 commit comments