This is an automated email from the ASF dual-hosted git repository.

SbloodyS pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/dolphinscheduler.git


The following commit(s) were added to refs/heads/dev by this push:
     new 22cf6e67a8 [Fix-18538][Master] Schedule task retry at endTime + 
retryInterval (#18539)
22cf6e67a8 is described below

commit 22cf6e67a889eb8cfcc78cf394cdd5c0ec589592
Author: Sepuri Sai Krishna <[email protected]>
AuthorDate: Tue Aug 11 11:41:30 2026 +0530

    [Fix-18538][Master] Schedule task retry at endTime + retryInterval (#18539)
---
 .../lifecycle/event/TaskRetryLifecycleEvent.java   |  9 ++-
 .../event/TaskRetryLifecycleEventTest.java         | 91 ++++++++++++++++++++++
 2 files changed, 97 insertions(+), 3 deletions(-)

diff --git 
a/dolphinscheduler-master/src/main/java/org/apache/dolphinscheduler/server/master/engine/task/lifecycle/event/TaskRetryLifecycleEvent.java
 
b/dolphinscheduler-master/src/main/java/org/apache/dolphinscheduler/server/master/engine/task/lifecycle/event/TaskRetryLifecycleEvent.java
index 587aea82e0..c865091f0d 100644
--- 
a/dolphinscheduler-master/src/main/java/org/apache/dolphinscheduler/server/master/engine/task/lifecycle/event/TaskRetryLifecycleEvent.java
+++ 
b/dolphinscheduler-master/src/main/java/org/apache/dolphinscheduler/server/master/engine/task/lifecycle/event/TaskRetryLifecycleEvent.java
@@ -43,7 +43,7 @@ public class TaskRetryLifecycleEvent extends 
AbstractTaskLifecycleEvent {
     public static TaskRetryLifecycleEvent of(final ITaskExecution 
taskExecution) {
         final TaskInstance taskInstance = taskExecution.getTaskInstance();
         checkState(taskInstance != null, "The task instance must be 
initialized before retrying.");
-        final int delayTime = taskInstance.getRetryInterval();
+        final int retryInterval = taskInstance.getRetryInterval();
 
         final int retryTimes = taskInstance.getRetryTimes();
         final int maxRetryTimes = taskInstance.getMaxRetryTimes();
@@ -51,8 +51,11 @@ public class TaskRetryLifecycleEvent extends 
AbstractTaskLifecycleEvent {
                 "The task retry times: %s must smaller then maxRetryTimes: 
%s.",
                 retryTimes,
                 maxRetryTimes);
-        final long remainingTime =
-                TimeUnit.MINUTES.toMillis(delayTime) + 
System.currentTimeMillis() - taskInstance.getEndTime().getTime();
+        // The task should be retried at (endTime + retryInterval), so the 
remaining delay is the retry interval
+        // minus the time which has already elapsed since the task ended. When 
the retry interval has already
+        // passed, e.g. the event is created during failover, the task should 
be retried immediately.
+        final long elapsedTime = System.currentTimeMillis() - 
taskInstance.getEndTime().getTime();
+        final long remainingTime = Math.max(0, 
TimeUnit.MINUTES.toMillis(retryInterval) - elapsedTime);
         return new TaskRetryLifecycleEvent(taskExecution, remainingTime);
     }
 
diff --git 
a/dolphinscheduler-master/src/test/java/org/apache/dolphinscheduler/server/master/engine/task/lifecycle/event/TaskRetryLifecycleEventTest.java
 
b/dolphinscheduler-master/src/test/java/org/apache/dolphinscheduler/server/master/engine/task/lifecycle/event/TaskRetryLifecycleEventTest.java
new file mode 100644
index 0000000000..ac98498540
--- /dev/null
+++ 
b/dolphinscheduler-master/src/test/java/org/apache/dolphinscheduler/server/master/engine/task/lifecycle/event/TaskRetryLifecycleEventTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dolphinscheduler.server.master.engine.task.lifecycle.event;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.when;
+
+import org.apache.dolphinscheduler.dao.entity.TaskInstance;
+import 
org.apache.dolphinscheduler.server.master.engine.task.execution.ITaskExecution;
+
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class TaskRetryLifecycleEventTest {
+
+    private static final int RETRY_INTERVAL_MINUTES = 5;
+
+    private static final int MAX_RETRY_TIMES = 3;
+
+    @Mock
+    private ITaskExecution taskExecution;
+
+    @Test
+    @DisplayName("Test the retry is delayed until (endTime + retryInterval) 
when the interval has not elapsed")
+    void testOf_retryIntervalNotElapsed_delayUntilRetryIntervalIsOver() {
+        final long elapsedTime = TimeUnit.SECONDS.toMillis(30);
+        
when(taskExecution.getTaskInstance()).thenReturn(createFailedTaskInstance(elapsedTime));
+
+        final TaskRetryLifecycleEvent event = 
TaskRetryLifecycleEvent.of(taskExecution);
+
+        final long expectedDelay = 
TimeUnit.MINUTES.toMillis(RETRY_INTERVAL_MINUTES) - elapsedTime;
+        
assertThat(event.getDelay(TimeUnit.MILLISECONDS)).isAtMost(expectedDelay);
+        assertThat(event.getDelay(TimeUnit.MILLISECONDS))
+                .isAtLeast(expectedDelay - TimeUnit.SECONDS.toMillis(10));
+    }
+
+    @Test
+    @DisplayName("Test the retry is triggered immediately when the retry 
interval has already elapsed")
+    void testOf_retryIntervalAlreadyElapsed_triggerRetryImmediately() {
+        // This happens when the retry event is recreated long after the task 
failed, e.g. the master which owned
+        // the workflow crashed and another master takes the workflow over by 
failover.
+        final long elapsedTime = TimeUnit.HOURS.toMillis(2);
+        
when(taskExecution.getTaskInstance()).thenReturn(createFailedTaskInstance(elapsedTime));
+
+        final TaskRetryLifecycleEvent event = 
TaskRetryLifecycleEvent.of(taskExecution);
+
+        assertThat(event.getDelay(TimeUnit.MILLISECONDS)).isAtMost(0L);
+    }
+
+    @Test
+    @DisplayName("Test creating the retry event failed when the task has no 
remaining retry times")
+    void testOf_retryTimesExhausted_throwIllegalStateException() {
+        final TaskInstance taskInstance = createFailedTaskInstance(0);
+        taskInstance.setRetryTimes(MAX_RETRY_TIMES);
+        when(taskExecution.getTaskInstance()).thenReturn(taskInstance);
+
+        assertThrows(IllegalStateException.class, () -> 
TaskRetryLifecycleEvent.of(taskExecution));
+    }
+
+    private TaskInstance createFailedTaskInstance(final long 
elapsedTimeSinceTaskEnded) {
+        final TaskInstance taskInstance = new TaskInstance();
+        taskInstance.setRetryTimes(0);
+        taskInstance.setMaxRetryTimes(MAX_RETRY_TIMES);
+        taskInstance.setRetryInterval(RETRY_INTERVAL_MINUTES);
+        taskInstance.setEndTime(new Date(System.currentTimeMillis() - 
elapsedTimeSinceTaskEnded));
+        return taskInstance;
+    }
+}

Reply via email to