det101 commented on code in PR #18463:
URL: 
https://github.com/apache/dolphinscheduler/pull/18463#discussion_r3733184331


##########
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/executor/logging/LogClientDelegate.java:
##########
@@ -109,4 +114,90 @@ private boolean checkNodeExists(TaskInstance taskInstance) 
{
         return exists;
     }
 
+    /**
+     * Stream the entire task instance log to {@code outputStream} using 
chunked RPC.
+     *
+     * <p>Strategy:
+     * <ul>
+     *   <li>If the worker node is gone, read straight from remote log storage 
(archive).</li>
+     *   <li>Otherwise try the chunk RPC. If it fails before any byte is 
written (e.g. an old worker
+     *       that does not implement {@code getTaskInstanceLogFileChunk}), 
fall back to the legacy
+     *       whole-file worker RPC; if that also fails, fall back to remote 
storage.</li>
+     *   <li>If the failure happens mid-stream (bytes already written), throw 
IOException to avoid
+     *       corrupting the download.</li>
+     * </ul>
+     */
+    public void streamWholeLog(final TaskInstance taskInstance, final 
OutputStream outputStream) throws IOException {
+        checkArgs(taskInstance);
+        if (!checkNodeExists(taskInstance)) {
+            writeRemoteLegacy(taskInstance, outputStream);
+            return;
+        }
+        long offset = 0;
+        try {
+            while (true) {
+                final TaskInstanceLogFileDownloadResponse chunk =
+                        localLogClient.getLogChunk(taskInstance, offset, 
LOG_CHUNK_SIZE);
+                if (chunk.getCode() != LogResponseStatus.SUCCESS) {
+                    if (offset == 0) {
+                        log.warn("First chunk failed for task instance {}: {}, 
falling back to legacy whole-file RPC",
+                                taskInstance.getId(), chunk.getMessage());
+                        writeLocalLegacy(taskInstance, outputStream);
+                        return;
+                    }
+                    throw new IOException("Worker chunk failed at offset " + 
offset + ": " + chunk.getMessage());
+                }
+                final byte[] data = chunk.getLogBytes();
+                if (data != null && data.length > 0) {
+                    outputStream.write(data);
+                    offset += data.length;
+                }
+                if (chunk.isEof() || (data == null || data.length == 0)) {
+                    return;
+                }
+            }
+        } catch (Exception e) {
+            if (offset == 0) {
+                log.warn("Chunked streaming failed before any byte written for 
task instance {}, "
+                        + "falling back to legacy whole-file RPC", 
taskInstance.getId(), e);
+                writeLocalLegacy(taskInstance, outputStream);
+                return;
+            }
+            throw new IOException("Log streaming failed at offset " + offset, 
e);
+        }
+    }
+
+    /**
+     * Fall back to the legacy whole-file worker RPC ({@code 
getTaskInstanceWholeLogFileBytes}),
+     * which every worker — including old ones built before the chunk RPC 
existed — implements. If
+     * that returns nothing or fails, fall through to remote log storage as a 
last resort.
+     */
+    private void writeLocalLegacy(final TaskInstance taskInstance,
+                                  final OutputStream outputStream) throws 
IOException {
+        try {
+            final TaskInstanceLogFileDownloadResponse response = 
localLogClient.getWholeLog(taskInstance);
+            if (response != null && response.getCode() == 
LogResponseStatus.SUCCESS) {
+                final byte[] bytes = response.getLogBytes();
+                if (bytes != null && bytes.length > 0) {
+                    outputStream.write(bytes);
+                    outputStream.flush();
+                    return;
+                }
+            }
+        } catch (Exception e) {
+            log.warn("Legacy whole-file RPC failed for task instance {}, 
falling back to remote storage",
+                    taskInstance.getId(), e);
+        }
+        writeRemoteLegacy(taskInstance, outputStream);
+    }
+
+    private void writeRemoteLegacy(final TaskInstance taskInstance,
+                                   final OutputStream outputStream) throws 
IOException {
+        final byte[] bytes = remoteLogClient.getWholeLog(taskInstance);
+        if (bytes != null && bytes.length > 0) {
+            outputStream.write(bytes);
+        }
+        outputStream.flush();

Review Comment:
   If remote returns null/empty (archive missing), this still flush()es and the 
download ends as HTTP 200 with only the log header. Please throw when bytes are 
absent so a missing remote log is not reported as a successful download.



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

Reply via email to