You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dolphinscheduler.apache.org by zi...@apache.org on 2026/10/14 02:36:26 UTC

(dolphinscheduler) branch dev updated: [Fix-18692][Master] Fix ignored dependent task check interval (#18694)

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 fa0fe2eab9 [Fix-18692][Master] Fix ignored dependent task check interval (#18694)
fa0fe2eab9 is described below

commit fa0fe2eab93abc2032af9558f1a7ffb6548152c8
Author: huangsheng <hu...@163.com>
AuthorDate: Wed Oct 14 10:36:19 2026 +0800

    [Fix-18692][Master] Fix ignored dependent task check interval (#18694)
---
 .../plugin/dependent/DependentLogicTask.java       |  57 ++++++-
 .../engine/executor/LogicTaskExecutorTest.java     | 145 ++++++++++++++++
 .../plugin/dependent/DependentLogicTaskTest.java   | 189 +++++++++++++++++++++
 3 files changed, 386 insertions(+), 5 deletions(-)

diff --git a/dolphinscheduler-master/src/main/java/org/apache/dolphinscheduler/server/master/engine/executor/plugin/dependent/DependentLogicTask.java b/dolphinscheduler-master/src/main/java/org/apache/dolphinscheduler/server/master/engine/executor/plugin/dependent/DependentLogicTask.java
index 484c7ef360..5dae467643 100644
--- a/dolphinscheduler-master/src/main/java/org/apache/dolphinscheduler/server/master/engine/executor/plugin/dependent/DependentLogicTask.java
+++ b/dolphinscheduler-master/src/main/java/org/apache/dolphinscheduler/server/master/engine/executor/plugin/dependent/DependentLogicTask.java
@@ -32,17 +32,29 @@ import org.apache.dolphinscheduler.server.master.engine.executor.plugin.ITaskPar
 import org.apache.dolphinscheduler.server.master.engine.workflow.execution.IWorkflowExecution;
 import org.apache.dolphinscheduler.server.master.exception.MasterTaskExecuteException;
 
+import java.util.concurrent.TimeUnit;
+import java.util.function.LongSupplier;
+
 import lombok.extern.slf4j.Slf4j;
 
 import com.fasterxml.jackson.core.type.TypeReference;
+import com.google.common.annotations.VisibleForTesting;
 
 @Slf4j
 public class DependentLogicTask extends AbstractLogicTask<DependentParameters> {
 
+    private static final int DEFAULT_CHECK_INTERVAL_SECONDS = 10;
+
     private final TaskExecutionContext taskExecutionContext;
 
     private final DependentTaskTracker dependentTaskTracker;
 
+    private final long checkIntervalNanos;
+
+    private final LongSupplier nanoTimeSupplier;
+
+    private Long lastCheckTimeNanos;
+
     public DependentLogicTask(TaskExecutionContext taskExecutionContext,
                               ProjectDao projectDao,
                               WorkflowDefinitionDao workflowDefinitionDao,
@@ -51,8 +63,26 @@ public class DependentLogicTask extends AbstractLogicTask<DependentParameters> {
                               WorkflowInstanceDao workflowInstanceDao,
                               IWorkflowExecution workflowExecution,
                               TaskInstanceContextDao taskInstanceContextDao) {
+        this(taskExecutionContext, projectDao, workflowDefinitionDao, taskDefinitionDao, taskInstanceDao,
+                workflowInstanceDao, workflowExecution, taskInstanceContextDao, System::nanoTime);
+    }
+
+    @VisibleForTesting
+    DependentLogicTask(TaskExecutionContext taskExecutionContext,
+                       ProjectDao projectDao,
+                       WorkflowDefinitionDao workflowDefinitionDao,
+                       TaskDefinitionDao taskDefinitionDao,
+                       TaskInstanceDao taskInstanceDao,
+                       WorkflowInstanceDao workflowInstanceDao,
+                       IWorkflowExecution workflowExecution,
+                       TaskInstanceContextDao taskInstanceContextDao,
+                       LongSupplier nanoTimeSupplier) {
         super(taskExecutionContext);
         this.taskExecutionContext = taskExecutionContext;
+        this.nanoTimeSupplier = nanoTimeSupplier;
+        final Integer checkInterval = taskParameters.getDependence().getCheckInterval();
+        this.checkIntervalNanos = TimeUnit.SECONDS.toNanos(
+                checkInterval == null ? DEFAULT_CHECK_INTERVAL_SECONDS : checkInterval);
         this.dependentTaskTracker = new DependentTaskTracker(
                 taskExecutionContext,
                 taskParameters,
@@ -72,22 +102,39 @@ public class DependentLogicTask extends AbstractLogicTask<DependentParameters> {
 
     @Override
     public TaskExecutionStatus getTaskExecutionState() {
-        if (isRunning()) {
-            taskExecutionStatus = dependentTaskTracker.getDependentTaskStatus();
+        synchronized (this) {
+            if (!isRunning()) {
+                return taskExecutionStatus;
+            }
+            final long now = nanoTimeSupplier.getAsLong();
+            if (lastCheckTimeNanos != null && now - lastCheckTimeNanos < checkIntervalNanos) {
+                return taskExecutionStatus;
+            }
+            lastCheckTimeNanos = now;
+        }
+        final TaskExecutionStatus dependentState = dependentTaskTracker.getDependentTaskStatus();
+        synchronized (this) {
+            // A pause or kill may have completed while the dependency query was running.
+            if (isRunning()) {
+                taskExecutionStatus = dependentState;
+            }
             return taskExecutionStatus;
         }
-        return taskExecutionStatus;
     }
 
     @Override
     public void pause() throws MasterTaskExecuteException {
-        onTaskPaused();
+        synchronized (this) {
+            onTaskPaused();
+        }
         log.info("Pause task : {} success", taskExecutionContext.getTaskName());
     }
 
     @Override
     public void kill() throws MasterTaskExecuteException {
-        onTaskKilled();
+        synchronized (this) {
+            onTaskKilled();
+        }
         log.info("Kill task : {} success", taskExecutionContext.getTaskName());
     }
 
diff --git a/dolphinscheduler-master/src/test/java/org/apache/dolphinscheduler/server/master/engine/executor/LogicTaskExecutorTest.java b/dolphinscheduler-master/src/test/java/org/apache/dolphinscheduler/server/master/engine/executor/LogicTaskExecutorTest.java
new file mode 100644
index 0000000000..131ca9ac44
--- /dev/null
+++ b/dolphinscheduler-master/src/test/java/org/apache/dolphinscheduler/server/master/engine/executor/LogicTaskExecutorTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.executor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import org.apache.dolphinscheduler.common.enums.Flag;
+import org.apache.dolphinscheduler.plugin.task.api.TaskExecutionContext;
+import org.apache.dolphinscheduler.plugin.task.api.enums.TaskExecutionStatus;
+import org.apache.dolphinscheduler.plugin.task.api.parameters.AbstractParameters;
+import org.apache.dolphinscheduler.server.master.engine.executor.plugin.ILogicTask;
+import org.apache.dolphinscheduler.server.master.engine.executor.plugin.ILogicTaskPluginFactory;
+import org.apache.dolphinscheduler.server.master.engine.executor.plugin.LogicTaskPluginFactoryBuilder;
+import org.apache.dolphinscheduler.server.master.engine.executor.plugin.dependent.DependentLogicTask;
+import org.apache.dolphinscheduler.server.master.engine.executor.plugin.dependent.DependentTaskTracker;
+import org.apache.dolphinscheduler.task.executor.TaskExecutorState;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.MockedConstruction;
+
+class LogicTaskExecutorTest {
+
+    private static final long FRAMEWORK_CHECK_INTERVAL_MILLIS = TimeUnit.SECONDS.toMillis(10);
+
+    private MockedConstruction<DependentTaskTracker> dependentTaskTrackers;
+
+    @BeforeEach
+    void setUp() {
+        dependentTaskTrackers = mockConstruction(DependentTaskTracker.class,
+                (tracker, context) -> when(tracker.getDependentTaskStatus())
+                        .thenReturn(TaskExecutionStatus.RUNNING_EXECUTION));
+    }
+
+    @AfterEach
+    void tearDown() {
+        dependentTaskTrackers.close();
+    }
+
+    @ParameterizedTest
+    @NullSource
+    @ValueSource(ints = {1, 5, 10, 20})
+    void frameworkUsesFixedCheckInterval(Integer checkInterval) throws Exception {
+        LogicTaskExecutor taskExecutor = createDependentTaskExecutor(checkInterval);
+
+        assertThat(taskExecutor.getRemainingTrackDelay()).isZero();
+        long beforeTrack = System.currentTimeMillis();
+        assertThat(taskExecutor.trackTaskExecutorState()).isEqualTo(TaskExecutorState.RUNNING);
+        long remainingDelay = taskExecutor.getRemainingTrackDelay();
+        long afterTrack = System.currentTimeMillis();
+
+        assertThat(remainingDelay).isBetween(
+                FRAMEWORK_CHECK_INTERVAL_MILLIS - (afterTrack - beforeTrack), FRAMEWORK_CHECK_INTERVAL_MILLIS);
+        verify(dependentTaskTrackers.constructed().get(0)).getDependentTaskStatus();
+    }
+
+    @Test
+    void pauseIsReportedOnNextFrameworkCheck() throws Exception {
+        LogicTaskExecutor taskExecutor = createDependentTaskExecutor(20);
+        taskExecutor.trackTaskExecutorState();
+
+        taskExecutor.pause();
+
+        assertThat(taskExecutor.getRemainingTrackDelay()).isPositive()
+                .isLessThanOrEqualTo(FRAMEWORK_CHECK_INTERVAL_MILLIS);
+        assertThat(taskExecutor.trackTaskExecutorState()).isEqualTo(TaskExecutorState.PAUSED);
+        DependentTaskTracker tracker = dependentTaskTrackers.constructed().get(0);
+        verify(tracker).getDependentTaskStatus();
+        verifyNoMoreInteractions(tracker);
+    }
+
+    @Test
+    void killIsReportedOnNextFrameworkCheck() throws Exception {
+        LogicTaskExecutor taskExecutor = createDependentTaskExecutor(20);
+        taskExecutor.trackTaskExecutorState();
+
+        taskExecutor.kill();
+
+        assertThat(taskExecutor.getRemainingTrackDelay()).isPositive()
+                .isLessThanOrEqualTo(FRAMEWORK_CHECK_INTERVAL_MILLIS);
+        assertThat(taskExecutor.trackTaskExecutorState()).isEqualTo(TaskExecutorState.KILLED);
+        DependentTaskTracker tracker = dependentTaskTrackers.constructed().get(0);
+        verify(tracker).getDependentTaskStatus();
+        verifyNoMoreInteractions(tracker);
+    }
+
+    private LogicTaskExecutor createDependentTaskExecutor(Integer checkInterval) throws Exception {
+        TaskExecutionContext taskExecutionContext = new TaskExecutionContext();
+        taskExecutionContext.setTaskType("DEPENDENT");
+        taskExecutionContext.setTaskName("dependent");
+        taskExecutionContext.setDryRun(Flag.NO.getCode());
+        taskExecutionContext.setTaskParams(checkInterval == null ? "{\"dependence\":{}}"
+                : "{\"dependence\":{\"checkInterval\":" + checkInterval + "}}");
+
+        return createTaskExecutor(taskExecutionContext, new DependentLogicTask(
+                taskExecutionContext, null, null, null, null, null, null, null));
+    }
+
+    @SuppressWarnings("unchecked")
+    private LogicTaskExecutor createTaskExecutor(TaskExecutionContext taskExecutionContext,
+                                                 ILogicTask<? extends AbstractParameters> logicTask) throws Exception {
+        ILogicTaskPluginFactory<? extends ILogicTask<? extends AbstractParameters>> taskPluginFactory =
+                mock(ILogicTaskPluginFactory.class);
+        when(taskPluginFactory.getTaskType()).thenReturn(taskExecutionContext.getTaskType());
+        when(taskPluginFactory.createLogicTask(any())).thenAnswer(invocation -> logicTask);
+        List<ILogicTaskPluginFactory<? extends ILogicTask<? extends AbstractParameters>>> taskPluginFactories =
+                new ArrayList<>();
+        taskPluginFactories.add(taskPluginFactory);
+        LogicTaskExecutor taskExecutor = new LogicTaskExecutor(LogicTaskExecutorBuilder.builder()
+                .taskExecutionContext(taskExecutionContext)
+                .logicTaskPluginFactoryBuilder(new LogicTaskPluginFactoryBuilder(taskPluginFactories))
+                .build());
+        taskExecutor.start();
+        return taskExecutor;
+    }
+}
diff --git a/dolphinscheduler-master/src/test/java/org/apache/dolphinscheduler/server/master/engine/executor/plugin/dependent/DependentLogicTaskTest.java b/dolphinscheduler-master/src/test/java/org/apache/dolphinscheduler/server/master/engine/executor/plugin/dependent/DependentLogicTaskTest.java
new file mode 100644
index 0000000000..46bcca16bf
--- /dev/null
+++ b/dolphinscheduler-master/src/test/java/org/apache/dolphinscheduler/server/master/engine/executor/plugin/dependent/DependentLogicTaskTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.executor.plugin.dependent;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import org.apache.dolphinscheduler.plugin.task.api.TaskExecutionContext;
+import org.apache.dolphinscheduler.plugin.task.api.enums.TaskExecutionStatus;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.mockito.MockedConstruction;
+
+class DependentLogicTaskTest {
+
+    private static final int ASYNC_TEST_TIMEOUT_SECONDS = 5;
+
+    private final AtomicLong currentTimeNanos = new AtomicLong();
+
+    private MockedConstruction<DependentTaskTracker> dependentTaskTrackers;
+
+    @BeforeEach
+    void setUp() {
+        dependentTaskTrackers = mockConstruction(DependentTaskTracker.class,
+                (tracker, context) -> when(tracker.getDependentTaskStatus())
+                        .thenReturn(TaskExecutionStatus.RUNNING_EXECUTION));
+    }
+
+    @AfterEach
+    void tearDown() {
+        dependentTaskTrackers.close();
+    }
+
+    @ParameterizedTest
+    @CsvSource(value = {"null, 10", "1, 1", "5, 5", "10, 10", "20, 20"}, nullValues = "null")
+    void dependencyQueriesUseConfiguredInterval(Integer checkInterval, long expectedIntervalSeconds) {
+        DependentLogicTask logicTask = createDependentLogicTask(checkInterval);
+        DependentTaskTracker tracker = dependentTaskTrackers.constructed().get(0);
+        when(tracker.getDependentTaskStatus())
+                .thenReturn(TaskExecutionStatus.RUNNING_EXECUTION, TaskExecutionStatus.SUCCESS);
+
+        assertThat(logicTask.getTaskExecutionState()).isEqualTo(TaskExecutionStatus.RUNNING_EXECUTION);
+        assertThat(logicTask.getTaskExecutionState()).isEqualTo(TaskExecutionStatus.RUNNING_EXECUTION);
+
+        long expectedIntervalNanos = TimeUnit.SECONDS.toNanos(expectedIntervalSeconds);
+        currentTimeNanos.set(expectedIntervalNanos - 1);
+        assertThat(logicTask.getTaskExecutionState()).isEqualTo(TaskExecutionStatus.RUNNING_EXECUTION);
+        verify(tracker).getDependentTaskStatus();
+
+        currentTimeNanos.set(expectedIntervalNanos);
+        assertThat(logicTask.getTaskExecutionState()).isEqualTo(TaskExecutionStatus.SUCCESS);
+        verify(tracker, times(2)).getDependentTaskStatus();
+        verifyNoMoreInteractions(tracker);
+    }
+
+    @ParameterizedTest
+    @CsvSource({"10, 2, 3", "15, 1, 2", "20, 1, 2"})
+    void frameworkChecksReturnCachedStateUntilQueryIsDue(int checkInterval, int queriesAtTenSeconds,
+                                                         int queriesAtTwentySeconds) {
+        DependentLogicTask logicTask = createDependentLogicTask(checkInterval);
+        DependentTaskTracker tracker = dependentTaskTrackers.constructed().get(0);
+
+        assertThat(logicTask.getTaskExecutionState()).isEqualTo(TaskExecutionStatus.RUNNING_EXECUTION);
+        verify(tracker).getDependentTaskStatus();
+
+        currentTimeNanos.set(TimeUnit.SECONDS.toNanos(10));
+        assertThat(logicTask.getTaskExecutionState()).isEqualTo(TaskExecutionStatus.RUNNING_EXECUTION);
+        verify(tracker, times(queriesAtTenSeconds)).getDependentTaskStatus();
+
+        currentTimeNanos.set(TimeUnit.SECONDS.toNanos(20));
+        assertThat(logicTask.getTaskExecutionState()).isEqualTo(TaskExecutionStatus.RUNNING_EXECUTION);
+        verify(tracker, times(queriesAtTwentySeconds)).getDependentTaskStatus();
+        verifyNoMoreInteractions(tracker);
+    }
+
+    @ParameterizedTest
+    @EnumSource(value = TaskExecutionStatus.class, names = {"PAUSE", "KILL"})
+    void controlStateIsReturnedBeforeNextDependencyQuery(TaskExecutionStatus controlState) throws Exception {
+        DependentLogicTask logicTask = createDependentLogicTask(20);
+        DependentTaskTracker tracker = dependentTaskTrackers.constructed().get(0);
+        assertThat(logicTask.getTaskExecutionState()).isEqualTo(TaskExecutionStatus.RUNNING_EXECUTION);
+
+        currentTimeNanos.set(TimeUnit.SECONDS.toNanos(10));
+        controlTask(logicTask, controlState);
+        assertThat(logicTask.getTaskExecutionState()).isEqualTo(controlState);
+
+        currentTimeNanos.set(TimeUnit.SECONDS.toNanos(40));
+        assertThat(logicTask.getTaskExecutionState()).isEqualTo(controlState);
+        verify(tracker).getDependentTaskStatus();
+        verifyNoMoreInteractions(tracker);
+    }
+
+    @ParameterizedTest
+    @EnumSource(value = TaskExecutionStatus.class, names = {"SUCCESS", "FAILURE"})
+    void finishedTaskDoesNotQueryDependenciesAgain(TaskExecutionStatus finalState) {
+        DependentLogicTask logicTask = createDependentLogicTask(20);
+        DependentTaskTracker tracker = dependentTaskTrackers.constructed().get(0);
+        when(tracker.getDependentTaskStatus()).thenReturn(finalState);
+
+        assertThat(logicTask.getTaskExecutionState()).isEqualTo(finalState);
+        currentTimeNanos.set(TimeUnit.SECONDS.toNanos(20));
+        assertThat(logicTask.getTaskExecutionState()).isEqualTo(finalState);
+        verify(tracker).getDependentTaskStatus();
+        verifyNoMoreInteractions(tracker);
+    }
+
+    @ParameterizedTest
+    @CsvSource({"PAUSE, RUNNING_EXECUTION", "PAUSE, SUCCESS", "PAUSE, FAILURE",
+            "KILL, RUNNING_EXECUTION", "KILL, SUCCESS", "KILL, FAILURE"})
+    void dependentCheckDoesNotOverwriteConcurrentControlState(TaskExecutionStatus expectedState,
+                                                              TaskExecutionStatus dependentState) throws Exception {
+        DependentLogicTask logicTask = createDependentLogicTask(20);
+        DependentTaskTracker tracker = dependentTaskTrackers.constructed().get(0);
+        CountDownLatch checkStarted = new CountDownLatch(1);
+        CountDownLatch finishCheck = new CountDownLatch(1);
+        when(tracker.getDependentTaskStatus()).thenAnswer(invocation -> {
+            checkStarted.countDown();
+            assertThat(finishCheck.await(ASYNC_TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)).isTrue();
+            return dependentState;
+        });
+
+        ExecutorService stateTracker = Executors.newSingleThreadExecutor();
+        try {
+            Future<TaskExecutionStatus> trackedState = stateTracker.submit(logicTask::getTaskExecutionState);
+            assertThat(checkStarted.await(ASYNC_TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)).isTrue();
+
+            controlTask(logicTask, expectedState);
+            assertThat(logicTask.getTaskExecutionState()).isEqualTo(expectedState);
+            finishCheck.countDown();
+
+            assertThat(trackedState.get(ASYNC_TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)).isEqualTo(expectedState);
+            assertThat(logicTask.getTaskExecutionState()).isEqualTo(expectedState);
+            verify(tracker).getDependentTaskStatus();
+            verifyNoMoreInteractions(tracker);
+        } finally {
+            finishCheck.countDown();
+            stateTracker.shutdownNow();
+            assertThat(stateTracker.awaitTermination(ASYNC_TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS)).isTrue();
+        }
+    }
+
+    private void controlTask(DependentLogicTask logicTask, TaskExecutionStatus controlState) throws Exception {
+        if (controlState == TaskExecutionStatus.PAUSE) {
+            logicTask.pause();
+        } else {
+            logicTask.kill();
+        }
+    }
+
+    private DependentLogicTask createDependentLogicTask(Integer checkInterval) {
+        TaskExecutionContext taskExecutionContext = new TaskExecutionContext();
+        taskExecutionContext.setTaskName("dependent");
+        taskExecutionContext.setTaskParams(checkInterval == null ? "{\"dependence\":{}}"
+                : "{\"dependence\":{\"checkInterval\":" + checkInterval + "}}");
+
+        return new DependentLogicTask(taskExecutionContext, null, null, null, null, null, null, null,
+                currentTimeNanos::get);
+    }
+}