javanna commented on code in PR #12689:
URL: https://github.com/apache/lucene/pull/12689#discussion_r1369190050


##########
lucene/core/src/java/org/apache/lucene/search/TaskExecutor.java:
##########
@@ -64,64 +67,124 @@ public final class TaskExecutor {
    * @param <T> the return type of the task execution
    */
   public <T> List<T> invokeAll(Collection<Callable<T>> callables) throws 
IOException {
-    List<Task<T>> tasks = new ArrayList<>(callables.size());
-    boolean runOnCallerThread = numberOfRunningTasksInCurrentThread.get() > 0;
-    for (Callable<T> callable : callables) {
-      Task<T> task = new Task<>(callable);
-      tasks.add(task);
-      if (runOnCallerThread) {
-        task.run();
-      } else {
-        executor.execute(task);
+    TaskGroup<T> taskGroup = new TaskGroup<>(callables);
+    return taskGroup.invokeAll(executor);
+  }
+
+  /**
+   * Holds all the sub-tasks that a certain operation gets split into as it 
gets parallelized and
+   * exposes the ability to invoke such tasks and wait for them all to 
complete their execution and
+   * provide their results. Ensures that each task does not get parallelized 
further: this is
+   * important to avoid a deadlock in situations where one executor thread 
waits on other executor
+   * threads to complete before it can progress. This happens in situations 
where for instance
+   * {@link Query#createWeight(IndexSearcher, ScoreMode, float)} is called as 
part of searching each
+   * slice, like {@link TopFieldCollector#populateScores(ScoreDoc[], 
IndexSearcher, Query)} does.
+   * Additionally, if one task throws an exception, all other tasks from the 
same group are
+   * cancelled, to avoid needless computation as their results would not be 
exposed anyways. Creates
+   * one {@link FutureTask} for each {@link Callable} provided
+   *
+   * @param <T> the return type of all the callables
+   */
+  private static final class TaskGroup<T> {
+    private final Collection<RunnableFuture<T>> futures;
+
+    TaskGroup(Collection<Callable<T>> callables) {
+      List<RunnableFuture<T>> tasks = new ArrayList<>(callables.size());
+      for (Callable<T> callable : callables) {
+        tasks.add(createTask(callable));
       }
+      this.futures = Collections.unmodifiableCollection(tasks);
     }
 
-    Throwable exc = null;
-    final List<T> results = new ArrayList<>();
-    for (Future<T> future : tasks) {
-      try {
-        results.add(future.get());
-      } catch (InterruptedException e) {
-        var newException = new ThreadInterruptedException(e);
-        if (exc == null) {
-          exc = newException;
-        } else {
-          exc.addSuppressed(newException);
+    private FutureTask<T> createTask(Callable<T> callable) {
+      AtomicBoolean started = new AtomicBoolean(false);
+      return new FutureTask<>(callable) {

Review Comment:
   I tried wrapping FutureTask, but the methods that I was calling I could only 
call from a sub-class, as they are protected. I did update my PR to wrap the 
callable, which requires less customization of FutureTask (only cancel and 
isCancelled need to be overwritten). I am now wondering if it still makes sense 
to have our own RunnableFuture instead of just subclassing FutureTask. the 
former seems overkill to me at this point, what do you think?



-- 
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: issues-unsubscr...@lucene.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscr...@lucene.apache.org
For additional commands, e-mail: issues-h...@lucene.apache.org

Reply via email to