capistrant commented on code in PR #20805:
URL: https://github.com/apache/druid/pull/20805#discussion_r4482475345


##########
multi-stage-query/src/main/java/org/apache/druid/msq/querykit/ReadableInputQueue.java:
##########
@@ -331,47 +355,218 @@ private ListenableFuture<NumberedReadableInput> 
nextChannelInput()
   @Nullable
   private ListenableFuture<NumberedReadableInput> nextSegmentInput()
   {
-    // Pick a loadahead future, preferring ones that are already loaded.
-    ListenableFuture<NumberedReadableInput> selectedLoadaheadFuture = null;
+    final List<ListenableFuture<NumberedReadableInput>> skippedFutures = new 
ArrayList<>();
+    ListenableFuture<NumberedReadableInput> unskippedFuture = null;
+    LoadableSegment unskippedSegment = null;
+
     synchronized (this) {
-      for (ListenableFuture<NumberedReadableInput> f : pendingNextInputs) {
-        if (selectedLoadaheadFuture == null || f.isDone()) {
-          selectedLoadaheadFuture = f;
-          if (f.isDone()) {
-            break;
-          }
+      acquireCachedInNextGroup();
+      while (unskippedFuture == null && !pendingNextInputs.isEmpty()) {
+        final ListenableFuture<NumberedReadableInput> selectedFuture = 
selectPendingInput();
+        final PendingInput selectedInput = 
pendingNextInputs.remove(selectedFuture);
+        if (!selectedInput.cached()) {
+          addLoadaheadFuture();
+        }
+        // The selected segment may have been pruned since its load was 
started, check if it can be skipped once more
+        // before handing it out.
+        if (canSkipSegment(selectedInput.segment())) {
+          skippedFutures.add(selectedFuture);
+          // Skipping may have moved the next input to a new interval group.
+          acquireCachedInNextGroup();
+        } else {
+          unskippedFuture = selectedFuture;
+          unskippedSegment = selectedInput.segment();
         }
       }
+    }
 
-      if (selectedLoadaheadFuture != null) {
-        pendingNextInputs.remove(selectedLoadaheadFuture);
-        if (pendingNextInputs.size() < loadahead) {
-          addLoadaheadFuture(); // Replace the one we just took out.
-        }
-        return selectedLoadaheadFuture;
+    releaseSkippedInputs(skippedFutures);
+    if (unskippedFuture != null) {
+      registerCountOnSuccess(unskippedFuture, unskippedSegment);
+      return unskippedFuture;
+    }
+
+    // Acquires on demand when loadahead is 0. With loadahead, no unpruned 
segments remain here.
+    final NumberedItem<LoadableSegment> nextItem = pollUnprunedSegment();
+    if (nextItem == null) {
+      return null;
+    }
+    final ListenableFuture<NumberedReadableInput> future = 
acquireSegment(nextItem);
+    registerCountOnSuccess(future, nextItem.item());
+    return future;
+  }
+
+  /**
+   * Registers a callback that counts {@code segment} as read once {@code 
future} succeeds, which is when the input is
+   * handed off for processing.
+   */
+  private static void registerCountOnSuccess(
+      final ListenableFuture<NumberedReadableInput> future,
+      final LoadableSegment segment
+  )
+  {
+    Futures.addCallback(
+        future,
+        new FutureCallback<>()
+        {
+          @Override
+          public void onSuccess(final NumberedReadableInput result)
+          {
+            segment.countHandedOut();
+          }
+
+          @Override
+          public void onFailure(final Throwable t)
+          {
+            // Failed inputs are never processed, so there is nothing to count.
+          }
+        },
+        MoreExecutors.directExecutor()
+    );
+  }
+
+  /**
+   * Picks the pending input to hand out next. Never null; only called when 
{@link #pendingNextInputs} is nonempty.
+   */
+  @GuardedBy("this")
+  private ListenableFuture<NumberedReadableInput> selectPendingInput()
+  {
+    final Iterator<Map.Entry<ListenableFuture<NumberedReadableInput>, 
PendingInput>> iterator =
+        pendingNextInputs.entrySet().iterator();
+    final Map.Entry<ListenableFuture<NumberedReadableInput>, PendingInput> 
head = iterator.next();
+    if (head.getKey().isDone()) {
+      // Eagerly hand out a ready head input.
+      return head.getKey();
+    }
+
+    // head is not ready, seek for the next ready input who compares equal to 
head. for ordered inputs that means
+    // matching intervals, for unordered inputs any ready input is fine.
+    final Interval headInterval = head.getValue().interval();
+    while (iterator.hasNext()) {
+      final Map.Entry<ListenableFuture<NumberedReadableInput>, PendingInput> 
entry = iterator.next();
+      if (entry.getKey().isDone() && intervalOrder.compare(headInterval, 
entry.getValue().interval()) == 0) {
+        return entry.getKey();
+      }
+    }
+
+    // Nothing level with the head is loaded, so hand out the head and let the 
caller wait for it.
+    return head.getKey();
+  }
+
+  /**
+   * Acquires the locally-cached segments of the next interval group to be 
handed out and adds them to
+   * {@link #pendingNextInputs}, once per group.
+   * <p>
+   * A group is the leading run of {@link #loadableSegments} whose intervals 
compare equal under {@link #intervalOrder};
+   * with {@link Order#NONE}, that is every segment. Segments that {@link 
#inputPruner} rules out are dropped.
+   */
+  @GuardedBy("this")
+  private void acquireCachedInNextGroup()
+  {
+    final Interval nextInterval;
+    if (!pendingNextInputs.isEmpty()) {
+      nextInterval = pendingNextInputs.values().iterator().next().interval();
+    } else if (!loadableSegments.isEmpty()) {
+      nextInterval = loadableSegments.peek().item().descriptor().getInterval();
+    } else {
+      return;
+    }
+
+    if (probedGroupInterval != null && 
intervalOrder.compare(probedGroupInterval, nextInterval) == 0) {
+      return;
+    }
+    probedGroupInterval = nextInterval;
+
+    final List<NumberedItem<LoadableSegment>> notCached = new ArrayList<>();
+    NumberedItem<LoadableSegment> nextItem;
+    while ((nextItem = loadableSegments.peek()) != null
+           && intervalOrder.compare(nextInterval, 
nextItem.item().descriptor().getInterval()) == 0) {
+      loadableSegments.poll();
+      if (canSkipSegment(nextItem.item())) {
+        continue;
+      }
+      final ListenableFuture<NumberedReadableInput> cachedFuture = 
acquireIfCached(nextItem);
+      if (cachedFuture != null) {
+        pendingNextInputs.put(cachedFuture, new PendingInput(nextItem.item(), 
true));
+      } else {
+        notCached.add(nextItem);
       }
     }
 
-    return loadNextSegment();
+    // Put non-cached segments back at the front of loadableSegments.
+    for (int i = notCached.size() - 1; i >= 0; i--) {
+      loadableSegments.addFirst(notCached.get(i));
+    }
   }
 
   /**
-   * Load the next segment from {@link #loadableSegments} and return a future 
to its reference. Returns null
-   * if {@link #loadableSegments} is empty, or if {@link #canSkipRemaining} is 
true.
+   * Removes and returns the next item from {@link #loadableSegments} that 
{@link #inputPruner} does not rule out, or
+   * null if none remain.
    */
   @Nullable
-  private ListenableFuture<NumberedReadableInput> loadNextSegment()
+  private NumberedItem<LoadableSegment> pollUnprunedSegment()
   {
-    if (canSkipRemaining.getAsBoolean()) {
-      return null;
+    synchronized (this) {
+      NumberedItem<LoadableSegment> nextItem;
+      do {
+        nextItem = loadableSegments.poll();
+      } while (nextItem != null && canSkipSegment(nextItem.item()));
+      return nextItem;
     }
+  }
+
+  /**
+   * Returns whether processing the given segment can be safely skipped.
+   */
+  private boolean canSkipSegment(final LoadableSegment segment)
+  {
+    return inputPruner.canSkip(segment.descriptor().getInterval());
+  }
 
+  /**
+   * Acquires the segment in {@code nextItem}: immediately if it is cached, 
otherwise by starting to load it.
+   */
+  private ListenableFuture<NumberedReadableInput> acquireSegment(final 
NumberedItem<LoadableSegment> nextItem)
+  {
     synchronized (this) {
-      final NumberedItem<LoadableSegment> nextItem = loadableSegments.poll();
-      if (nextItem == null) {
-        return null;
+      final ListenableFuture<NumberedReadableInput> cachedFuture = 
acquireIfCached(nextItem);
+      if (cachedFuture != null) {
+        return cachedFuture;
       }
+    }
+
+    return loadSegment(nextItem);

Review Comment:
   hmm. I took a look and tried to consolidate where the monitor is taken. my 
first commit in this work split it up and I think that is what exposed these 
windows where a close could orphan segments



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to