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

gianm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new 66f294f2958 fix: Missed cleanup in runAllFully on certain 
cancellations. (#20322)
66f294f2958 is described below

commit 66f294f29589b92464f04c9bfdbd2d6c8f93129b
Author: Gian Merlino <[email protected]>
AuthorDate: Tue Oct 20 15:29:26 2026 -0700

    fix: Missed cleanup in runAllFully on certain cancellations. (#20322)
    
    Previously, with no processors outstanding, "finished" was never set,
    so the processor manager was never closed and its resources could leak.
---
 .../druid/frame/processor/RunAllFullyWidget.java   |  2 +
 .../frame/processor/RunAllFullyWidgetTest.java     | 49 ++++++++++++++++++++++
 2 files changed, 51 insertions(+)

diff --git 
a/processing/src/main/java/org/apache/druid/frame/processor/RunAllFullyWidget.java
 
b/processing/src/main/java/org/apache/druid/frame/processor/RunAllFullyWidget.java
index 4b096c01c31..ba465cd8b33 100644
--- 
a/processing/src/main/java/org/apache/druid/frame/processor/RunAllFullyWidget.java
+++ 
b/processing/src/main/java/org/apache/druid/frame/processor/RunAllFullyWidget.java
@@ -41,6 +41,7 @@ import java.util.IdentityHashMap;
 import java.util.Optional;
 import java.util.Queue;
 import java.util.Set;
+import java.util.concurrent.CancellationException;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -160,6 +161,7 @@ public class RunAllFullyWidget<T, ResultType>
 
                   // Cleanup if there were no outstanding processors. (If 
there were some outstanding ones, future
                   // cancellation above would trigger cleanup.)
+                  finished.compareAndSet(null, Either.error(new 
CancellationException("Canceled")));
                   cleanupIfNoMoreProcessors();
                 }
               }
diff --git 
a/processing/src/test/java/org/apache/druid/frame/processor/RunAllFullyWidgetTest.java
 
b/processing/src/test/java/org/apache/druid/frame/processor/RunAllFullyWidgetTest.java
index 5a2ac401b31..b958608183a 100644
--- 
a/processing/src/test/java/org/apache/druid/frame/processor/RunAllFullyWidgetTest.java
+++ 
b/processing/src/test/java/org/apache/druid/frame/processor/RunAllFullyWidgetTest.java
@@ -467,6 +467,29 @@ public class RunAllFullyWidgetTest extends 
FrameProcessorExecutorTest.BaseFrameP
     Assertions.assertEquals(0, exec.cancelableProcessorCount());
   }
 
+  @Test
+  public void test_runAllFully_cancelWhileWaitingForFirstProcessor()
+  {
+    final String cancellationId = "xyzzy";
+    exec.registerCancellationId(cancellationId);
+
+    // This manager never returns a processor, so all runnables park with no 
processors outstanding.
+    final ListenableFuture<Long> future = exec.runAllFully(
+        possiblyDelay(ensureClose(new NeverResolvingProcessorManager())),
+        maxOutstandingProcessors,
+        bouncer,
+        cancellationId
+    );
+
+    exec.cancel(cancellationId);
+
+    Assertions.assertTrue(future.isCancelled());
+
+    // The processor manager must be closed even though no processor was ever 
outstanding. (tearDown checks this
+    // too, and additionally checks that it happens exactly once.)
+    Assertions.assertEquals(1, closed.get(), "Processor manager closed");
+  }
+
   /**
    * Wrap in {@link DelayedProcessorManager} if {@link #delayed} is set.
    */
@@ -644,6 +667,32 @@ public class RunAllFullyWidgetTest extends 
FrameProcessorExecutorTest.BaseFrameP
     }
   }
 
+  /**
+   * Processor manager whose {@link #next()} future never resolves, so callers 
park with no processors outstanding.
+   */
+  private static class NeverResolvingProcessorManager implements 
ProcessorManager<Object, Long>
+  {
+    private final SettableFuture<Optional<ProcessorAndCallback<Object>>> 
future = SettableFuture.create();
+
+    @Override
+    public ListenableFuture<Optional<ProcessorAndCallback<Object>>> next()
+    {
+      return future;
+    }
+
+    @Override
+    public Long result()
+    {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void close()
+    {
+      future.cancel(false);
+    }
+  }
+
   /**
    * Processor manager that effectively delays future resolution by deferring 
it through {@link #exec}.
    * Especially useful on single-threaded test cases. This helps us ensure 
that things work when channels don't have


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

Reply via email to