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


##########
indexing-service/src/main/java/org/apache/druid/indexing/common/actions/LocalTaskActionClient.java:
##########
@@ -52,10 +58,28 @@ public <RetType> RetType submit(TaskAction<RetType> 
taskAction)
     log.debug("Performing action for task[%s]: %s", task.getId(), taskAction);
     final long performStartTime = System.currentTimeMillis();
     final RetType result = performAction(taskAction);
-    emitTimerMetric("task/action/run/time", taskAction, 
System.currentTimeMillis() - performStartTime);
+    emitTimerMetric(RUN_TIME_METRIC, taskAction, System.currentTimeMillis() - 
performStartTime);
     return result;
   }
 
+  @Override
+  public <RetType> ListenableFuture<RetType> submitAsync(TaskAction<RetType> 
taskAction)
+  {
+    if (taskAction.canPerformAsync(task, toolbox)) {
+      final Stopwatch actionRunTime = Stopwatch.createStarted();
+      return Futures.transform(
+          taskAction.performAsync(task, toolbox),
+          v -> {
+            emitTimerMetric(RUN_TIME_METRIC, taskAction, 
actionRunTime.millisElapsed());
+            return v;
+          },
+          MoreExecutors.directExecutor()
+      );
+    } else {
+      return Futures.immediateFuture(submit(taskAction));

Review Comment:
   [P1] Return synchronous action failures as failed futures
   
   **Finding:** The fallback evaluates submit(taskAction) before 
immediateFuture can be created, and the async branch can likewise throw while 
evaluating canPerformAsync or performAsync. Those exceptions escape submitAsync 
instead of reaching the future callback. OverlordResource has already called 
startAsync() and invokes submitAsync before registering its timeout listener, 
so an ordinary action failure can leave the HTTP request without a response, 
timeout, or completion.
   
   **Suggestion:** Convert synchronous failures from the async decision and 
both execution paths into failed futures, or guard the call in the resource so 
every failure completes the servlet context through the existing error callback.



##########
indexing-service/src/main/java/org/apache/druid/indexing/overlord/http/OverlordResource.java:
##########
@@ -506,34 +515,57 @@ public Response getWorkerConfigHistory(
   @Path("/action")
   @Produces(MediaType.APPLICATION_JSON)
   @ResourceFilters(StateResourceFilter.class)
-  public Response doAction(final TaskActionHolder holder)
+  public void doAction(
+      final TaskActionHolder holder,
+      @Nullable @PathParam("timeout") final Long timeoutMillis,
+      @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();
+        })
+    );
+
+    // Use a default timeout of 5 minutes
+    asyncContext.setTimeout(Configs.valueOrDefault(timeoutMillis, 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(),
+                result == null ? Map.of() : Map.of("result", result)

Review Comment:
   [P2] Preserve the result field for null task-action results
   
   **Finding:** The previous synchronous endpoint always serialized a result 
property, including {"result":null} for Void actions. The async success path 
now serializes {} when the action returns null. RemoteTaskActionClient 
tolerates the missing property, but callers that rely on the stable response 
shape will observe an API regression.
   
   **Suggestion:** Use a null-tolerant response representation that retains the 
existing result field for successful null-valued actions.



##########
indexing-service/src/main/java/org/apache/druid/indexing/overlord/http/OverlordResource.java:
##########
@@ -506,34 +515,57 @@ public Response getWorkerConfigHistory(
   @Path("/action")
   @Produces(MediaType.APPLICATION_JSON)
   @ResourceFilters(StateResourceFilter.class)
-  public Response doAction(final TaskActionHolder holder)
+  public void doAction(
+      final TaskActionHolder holder,
+      @Nullable @PathParam("timeout") final Long timeoutMillis,

Review Comment:
   [P1] Make the async action timeout cover publish waits
   
   **Finding:** The /action route has no {timeout} path segment, so 
@PathParam("timeout") is never populated and setTimeout always uses five 
minutes. The supervisor's default completionTimeout is 30 minutes; a legitimate 
pending task group that takes longer than five minutes causes the timeout 
listener to cancel the action and complete the request before the readiness 
future finishes. The worker then sees an I/O or empty-response failure, and the 
publish retry path does not retry that timeout, so the new blocking behavior 
can fail a task instead of waiting for the prior group.
   
   **Suggestion:** Bind the timeout to a real request parameter or route and 
propagate a deadline that covers the configured supervisor completion wait, 
with an explicit retry-safe timeout response.



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