FrankChen021 commented on code in PR #20505:
URL: https://github.com/apache/druid/pull/20505#discussion_r4218899918


##########
indexing-service/src/main/java/org/apache/druid/indexing/seekablestream/supervisor/SeekableStreamSupervisor.java:
##########
@@ -385,6 +417,37 @@ public String toString()
     }
   }
 
+  /**
+   * Represents a task that waits until all prior task groups that were 
publishing
+   * to overlapping partitions have completed.
+   */
+  private class TaskWaitingToPublish
+  {
+    final CountDownLatch latch;
+    final SettableFuture<Boolean> readyToPublish = SettableFuture.create();
+    final Map<TaskGroup, Set<PartitionIdType>> blockingTaskGroups;
+
+    TaskWaitingToPublish(String taskId, Map<TaskGroup, Set<PartitionIdType>> 
blockingTaskGroups)
+    {
+      this.latch = new CountDownLatch(blockingTaskGroups.size());
+      this.blockingTaskGroups = new HashMap<>(blockingTaskGroups);
+
+      tasksWaitingToPublish.put(taskId, this);
+
+      for (TaskGroup group : blockingTaskGroups.keySet()) {
+        group.addCompletionListener(() -> {
+          latch.countDown();
+          blockingTaskGroups.remove(group);

Review Comment:
   [P2] Avoid mutating the map being iterated
   
   **Finding:** The constructor iterates `blockingTaskGroups.keySet()` while 
each completion callback removes from that same constructor argument. If a 
prior group completes during registration, `addCompletionListener` can run the 
callback inline (or concurrently), modifying the `HashMap` during iteration and 
throwing `ConcurrentModificationException` before the readiness future is 
returned.
   
   **Suggestion:** Register listeners from an immutable snapshot and update a 
separate thread-safe tracking structure from the callbacks.



##########
indexing-service/src/main/java/org/apache/druid/indexing/common/actions/SegmentTransactionalAppendAction.java:
##########
@@ -219,12 +222,31 @@ public SegmentPublishResult perform(Task task, 
TaskActionToolbox toolbox)
     }
 
     IndexTaskUtils.emitSegmentPublishMetrics(retVal, task, toolbox);
+    return retVal;
+  }
 
-    if (toolbox.shouldFailSegmentPublishImmediately(retVal, task, 
supervisorId, startMetadata)) {
-      return SegmentPublishResult.fail(retVal.getErrorMsg());
-    } else {
-      return retVal;
-    }
+  @Override
+  public boolean canPerformAsync(Task task, TaskActionToolbox toolbox)
+  {
+    return supervisorId != null && startMetadata != null;
+  }
+
+  @Override
+  public ListenableFuture<SegmentPublishResult> performAsync(Task task, 
TaskActionToolbox toolbox)
+  {
+    final ListenableFuture<Boolean> taskReadyToPublishFuture
+        = toolbox.isTaskReadyToPublish(task, supervisorId, startMetadata);
+    return Futures.transform(
+        taskReadyToPublishFuture,
+        taskIsReadyToPublish -> {
+          if (Boolean.TRUE.equals(taskIsReadyToPublish)) {
+            return 
SegmentPublishResult.doNotRetryOffsetMismatchFailure(perform(task, toolbox));
+          } else {
+            return SegmentPublishResult.retryableFailure("Task is not ready to 
publish yet");
+          }
+        },
+        MoreExecutors.directExecutor()

Review Comment:
   [P2] Keep publish work off the supervisor notice thread
   
   **Finding:** The readiness future is completed by `TaskGroup.onCompleted` 
during `checkPendingCompletionTasks`, which runs on this supervisor's 
single-threaded notice executor. With a direct executor, this transform calls 
`perform` inline; that method enters the task-lock critical section and 
performs metadata-store publication, so a slow publish blocks this supervisor 
from processing subsequent status checks and notices. The same executor choice 
is present in the insert action.
   
   **Suggestion:** Dispatch the publish operation to a dedicated executor when 
readiness completes, leaving the supervisor notice thread free to process its 
queue.



##########
indexing-service/src/main/java/org/apache/druid/indexing/overlord/http/OverlordResource.java:
##########
@@ -506,34 +515,50 @@ public Response getWorkerConfigHistory(
   @Path("/action")
   @Produces(MediaType.APPLICATION_JSON)
   @ResourceFilters(StateResourceFilter.class)
-  public Response doAction(final TaskActionHolder holder)
+  public void doAction(
+      final TaskActionHolder holder,
+      @Context HttpServletRequest request
+  )
   {
-    return asLeaderWith(
-        taskMaster.getTaskActionClient(holder.getTask()),
-        new Function<>()
+    final AsyncContext asyncContext = request.startAsync();
+    final Optional<TaskActionClient> taskActionClient = 
taskMaster.getTaskActionClient(holder.getTask());
+    if (!taskActionClient.isPresent()) {
+      // Encourage client to try again soon, when we'll likely have a redirect 
set up
+      completeRequest(asyncContext, 
Status.SERVICE_UNAVAILABLE.getStatusCode(), null);
+      return;
+    }
+
+    final ListenableFuture<?> future = 
taskActionClient.get().submitAsync(holder.getAction());
+    asyncContext.addListener(
+        ServletResourceUtils.createAsyncTimeoutListener(event -> {
+          // HTTP 204 NO_CONTENT is sent to the client.
+          future.cancel(true);
+          event.getAsyncContext().complete();
+        })
+    );
+    asyncContext.setTimeout(5 * 60_000);
+
+    Futures.addCallback(
+        future,
+        new FutureCallback<Object>()
         {
           @Override
-          public Response apply(TaskActionClient taskActionClient)
+          public void onSuccess(Object result)
           {
-            final Map<String, Object> retMap;
-
-            // It would be great to verify that this worker is actually 
supposed to be running the task before
-            // actually doing the action.  Some ideas for how that could be 
done would be using some sort of attempt_id
-            // or token that gets passed around.
+            completeRequest(asyncContext, Status.OK.getStatusCode(), 
Map.of("result", result));

Review Comment:
   [P1] Preserve null task-action results
   
   **Finding:** Several existing actions, including `LockReleaseAction` and 
`UpdateLocationAction`, implement `TaskAction<Void>` and return `null`. 
`Map.of` rejects null values, so these successful actions throw from 
`onSuccess` before `completeRequest` can send the result, leaving the async 
request unanswered.
   
   **Suggestion:** Build the response with a null-tolerant map so the existing 
`result: null` response contract is preserved.



##########
indexing-service/src/main/java/org/apache/druid/indexing/overlord/http/OverlordResource.java:
##########
@@ -884,4 +909,19 @@ private List<TaskStatusPlus> securedTaskStatusPlus(
         )
     );
   }
+
+  private void completeRequest(AsyncContext context, int statusCode, Object 
result)
+  {
+    try {
+      final HttpServletResponse response = (HttpServletResponse) 
context.getResponse();
+      response.setStatus(statusCode);
+      if (result != null) {
+        response.setContentType(MediaType.APPLICATION_JSON);
+        jsonMapper.writeValue(response.getOutputStream(), result);
+      }
+    }
+    catch (Exception e) {
+      log.noStackTrace().warn(e, "Request timed out or is closed already");
+    }
+  }

Review Comment:
   [P1] Complete the async response on every path
   
   **Finding:** `doAction` starts an `AsyncContext`, but this helper only sets 
the status and optionally writes a body; it never completes the context. 
Consequently, even a finished task action leaves the HTTP exchange open until 
the async timeout, and the no-leader path returns here before installing any 
timeout listener.
   
   **Suggestion:** Complete the async context after the response is written, 
including when no response body is present.



-- 
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