mattcasters commented on code in PR #8544:
URL: https://github.com/apache/hop/pull/8544#discussion_r4236909163


##########
plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutor.java:
##########
@@ -232,63 +232,130 @@ private void executePipeline(List<String> 
incomingFieldValues) throws HopExcepti
     }
     pipelineExecutorData.groupTimeStart = System.currentTimeMillis();
 
-    if (first) {
-      discardLogLines(pipelineExecutorData);
+    List<String> parameterValues = incomingFieldValues;
+    if (parameterValues == null) {
+      List<String> lastIncomingFieldValues = getLastIncomingFieldValues();
+      parameterValues = !Utils.isEmpty(lastIncomingFieldValues) ? 
lastIncomingFieldValues : null;
     }
 
+    // Execute the pipeline with retries if necessary
+    Result result = executeWithRetries(parameterValues);
+
+    collectPipelineResults(result);
+    collectExecutionResults(result);
+    collectExecutionResultFiles(result);
+
+    pipelineExecutorData.groupBuffer.clear();
+  }
+
+  @VisibleForTesting
+  Result executePipelineAttempt(List<String> parameterValues, long timeoutMs) 
throws HopException {
+    PipelineExecutorData pipelineExecutorData = getData();
+    cleanupPreviousAttemptPipeline(pipelineExecutorData);
+
     IPipelineEngine<PipelineMeta> executorPipeline = createInternalPipeline();
     pipelineExecutorData.setExecutorPipeline(executorPipeline);
-    if (incomingFieldValues != null) {
-      // Pass parameter values
-      passParametersToPipeline(incomingFieldValues);
-    } else {
-      List<String> lastIncomingFieldValues = getLastIncomingFieldValues();
-      // incomingFieldValues == null-  There are no more rows - Last Case - 
pass previous values if
-      // exists
-      // If not still pass the null parameter values
-      passParametersToPipeline(
-          !Utils.isEmpty(lastIncomingFieldValues) ? lastIncomingFieldValues : 
incomingFieldValues);
-    }
+
+    passParametersToPipeline(parameterValues);
 
     // keep track for drill down in HopGui...
     getPipeline().addActiveSubPipeline(getTransformName(), executorPipeline);
 
-    Result result = new Result();
-    result.setRows(pipelineExecutorData.groupBuffer);
-    executorPipeline.setPreviousResult(result);
+    Result previousResult = new Result();
+    previousResult.setRows(pipelineExecutorData.groupBuffer);
+    executorPipeline.setPreviousResult(previousResult);
 
+    Result result = new Result();
     try {
       executorPipeline.prepareExecution();
 
       // run pipeline
       executorPipeline.startThreads();
 
       // Wait a while until we're done with the pipeline
-      long timeoutMs = ExecutionWait.parseTimeoutMs(this, 
meta.getWaitTimeout());
       boolean finishedInTime = ExecutionWait.waitForPipeline(executorPipeline, 
timeoutMs);
 
       result = executorPipeline.getResult();
+      if (result == null) {
+        result = new Result();
+      }
       if (!finishedInTime) {
         logError(
             BaseMessages.getString(
                 PKG, "PipelineExecutor.Log.WaitTimeoutReached", 
Long.toString(timeoutMs)));
-        if (result == null) {
-          result = new Result();
-        }
         result.setResult(false);
         result.setNrErrors(Math.max(1, result.getNrErrors()));
       }
     } catch (HopException e) {
       logError("An error occurred executing the pipeline: ", e);
       result.setResult(false);
       result.setNrErrors(1);
+      cleanupPreviousAttemptPipeline(pipelineExecutorData);
     }
+    return result;
+  }
 
-    collectPipelineResults(result);
-    collectExecutionResults(result);
-    collectExecutionResultFiles(result);
+  private boolean isFailedResult(Result result) {
+    if (result == null) {
+      return true;
+    }
+    return !result.isResult() || result.getNrErrors() > 0;
+  }
 
-    pipelineExecutorData.groupBuffer.clear();
+  @VisibleForTesting
+  Result executeWithRetries(List<String> parameterValues) throws HopException {
+    int retryAttempts = Math.max(0, 
Const.toIntExpanded(resolve(meta.getRetryAttempts()), 0));
+    long retryDelayMs = Math.max(0L, 
Const.toLongExpanded(resolve(meta.getRetryDelay()), 0L));
+    long configuredTimeoutMs = ExecutionWait.parseTimeoutMs(this, 
meta.getWaitTimeout());
+
+    Result result = null;
+    for (int attempt = 0; attempt <= retryAttempts; attempt++) {
+      if (isStopped()) {
+        if (result == null) {
+          result = new Result();
+          result.setResult(false);
+          result.setNrErrors(1);
+        }
+        break;
+      }
+
+      result = executePipelineAttempt(parameterValues, configuredTimeoutMs);
+      if (!isFailedResult(result) || attempt == retryAttempts) {
+        break;
+      }
+      if (!waitRetryDelay(retryDelayMs)) {
+        break;
+      }
+    }
+    return result;
+  }
+
+  private boolean waitRetryDelay(long retryDelayMs) {
+    long remainingDelayMs = retryDelayMs;
+    while (remainingDelayMs > 0) {
+      if (isStopped()) {
+        return false;
+      }
+      long sleepMs = Math.min(remainingDelayMs, 100L);
+      try {
+        Thread.sleep(sleepMs);
+      } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+        logError(BaseMessages.getString(PKG, 
"PipelineExecutor.Log.RetryInterrupted"), e);
+        return false;
+      }
+      remainingDelayMs -= sleepMs;
+    }
+    return !isStopped();
+  }
+
+  @VisibleForTesting
+  void cleanupPreviousAttemptPipeline(PipelineExecutorData 
pipelineExecutorData) {
+    IPipelineEngine<PipelineMeta> executorPipeline = 
pipelineExecutorData.getExecutorPipeline();
+    if (executorPipeline != null && executorPipeline.isRunning()) {
+      executorPipeline.stopAll();
+    }
+    discardLogLines(pipelineExecutorData);

Review Comment:
   **[bug]** `discardLogLines` runs for every caller of 
`cleanupPreviousAttemptPipeline`, and both callers drop a log channel the 
execution-results row still needs.
   
   `executePipelineAttempt` calls this at the start of every attempt. Group 
size still defaults to 1 and retries default to 0, so the second input row 
deletes the previous child's channel. `collectExecutionResults` has already 
copied `ExecutionLogText` onto the output row, and the `ExecutionLogChannelId` 
on that row then points at a channel `HopLogStore.discardLines` has removed. 
Previously this discard ran only when `first` was still true, and `processRow` 
clears `first` before `executePipeline`, so those channels stayed available 
across groups.
   
   The `HopException` catch calls this again, before `executePipeline` reaches 
`collectExecutionResults`. When `prepareExecution` or `startThreads` throws on 
the last attempt, the execution-log field is read from a buffer this method 
just cleared. That catch used to leave the child log in place. 
`executePipelineAttemptStopsCurrentChildWhenStartThreadsThrows` only verifies 
`stopAll()`, so it still passes if the log is kept.
   
   **Suggestion:** In the catch, stop the engine when it is still running and 
leave its log channel alone. Call `discardLogLines` only when a later retry is 
about to start, so the last attempt's log is still there for 
`collectExecutionResults`. Leave the previous group's channel in place when the 
next input group starts its first attempt.



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