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

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 60f98a006c Issue #2383 : Add wait timeout to nested pipeline and 
workflow execution (#8043)
60f98a006c is described below

commit 60f98a006cf372695b3fb55b98850449051bf43d
Author: Matt Casters <[email protected]>
AuthorDate: Fri Aug 21 09:29:25 2026 +0200

    Issue #2383 : Add wait timeout to nested pipeline and workflow execution 
(#8043)
    
    Stop after (milliseconds) on Pipeline/Workflow actions (when wait-to-finish 
is enabled) and on Pipeline/Workflow Executor transforms. Empty or 0 waits 
indefinitely; on timeout the child is stopped and the nested execution fails.
---
 .../pipeline/transforms/pipeline-executor.adoc     |   1 +
 .../pipeline/transforms/workflow-executor.adoc     |   1 +
 .../ROOT/pages/workflow/actions/pipeline.adoc      |   1 +
 .../ROOT/pages/workflow/actions/workflow.adoc      |   1 +
 .../org/apache/hop/execution/ExecutionWait.java    | 155 +++++++++++
 .../apache/hop/execution/ExecutionWaitTest.java    | 119 +++++++++
 integration-tests/actions/0022-fast-pipeline.hpl   |  69 +++++
 integration-tests/actions/0022-fast-workflow.hwf   |  74 ++++++
 integration-tests/actions/0022-slow-pipeline.hpl   |  94 +++++++
 integration-tests/actions/0022-slow-workflow.hwf   |  93 +++++++
 .../actions/main-0022-wait-timeout.hwf             | 253 ++++++++++++++++++
 .../transforms/0107-fast-pipeline.hpl              |  69 +++++
 .../transforms/0107-fast-workflow.hwf              |  74 ++++++
 .../transforms/0107-pipeline-executor-ok.hpl       | 105 ++++++++
 .../transforms/0107-pipeline-executor-timeout.hpl  | 105 ++++++++
 .../transforms/0107-slow-pipeline.hpl              |  94 +++++++
 .../transforms/0107-slow-workflow.hwf              |  93 +++++++
 .../transforms/0107-workflow-executor-ok.hpl       | 102 ++++++++
 .../transforms/0107-workflow-executor-timeout.hpl  | 102 ++++++++
 .../transforms/main-0107-executor-wait-timeout.hwf | 289 +++++++++++++++++++++
 .../workflow/actions/pipeline/ActionPipeline.java  |  27 +-
 .../actions/pipeline/ActionPipelineDialog.java     |  31 +++
 .../pipeline/messages/messages_en_US.properties    |   3 +
 .../pipeline/ActionPipelineWaitTimeoutTest.java    |  36 +++
 .../workflow/actions/workflow/ActionWorkflow.java  |  43 ++-
 .../actions/workflow/ActionWorkflowDialog.java     |  31 +++
 .../workflow/messages/messages_en_US.properties    |   3 +
 .../workflow/ActionWorkflowWaitTimeoutTest.java    |  36 +++
 .../pipelineexecutor/PipelineExecutor.java         |  14 +-
 .../pipelineexecutor/PipelineExecutorDialog.java   |  27 +-
 .../pipelineexecutor/PipelineExecutorMeta.java     |   7 +
 .../messages/messages_en_US.properties             |   3 +
 .../pipelineexecutor/PipelineExecutorMetaTest.java |   2 +
 .../test/resources/pipeline-executor-transform.xml |   1 +
 .../workflowexecutor/WorkflowExecutor.java         |  35 ++-
 .../workflowexecutor/WorkflowExecutorDialog.java   |  27 +-
 .../workflowexecutor/WorkflowExecutorMeta.java     |   7 +
 .../messages/messages_en_US.properties             |   3 +
 .../workflowexecutor/WorkflowExecutorMetaTest.java |   2 +
 .../test/resources/workflow-executor-transform.xml |   1 +
 40 files changed, 2216 insertions(+), 17 deletions(-)

diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/pipeline-executor.adoc
 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/pipeline-executor.adoc
index ed50603192..d908af8b6a 100644
--- 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/pipeline-executor.adoc
+++ 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/pipeline-executor.adoc
@@ -104,6 +104,7 @@ The filename may contain variables (for example, you can 
use the built-in Intern
 
 *NOTE:* It must be considered that, by specifying the pipeline file name using 
this option, we can experience a little performance penalty because the 
pipeline that will be executed will be initialized at runtime and not during 
the initialization phase as usual. On the other side this option gives greater 
flexibility in specifying the pipeline filename dynamically and this could be 
useful for many use-cases.
 |Run configuration|Specify the Pipeline Run Configuration to be used for 
execution.
+|Stop after (milliseconds)|Maximum time to wait for the child pipeline to 
complete. Empty or 0 means wait indefinitely. When the time is reached the 
child pipeline is stopped and the execution-results stream reports a failure 
(`ExecutionResult=N`, `ExecutionNrErrors>=1`). The value may contain a variable.
 
 |===
 
diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/workflow-executor.adoc
 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/workflow-executor.adoc
index eea35fd6ab..60fc0a5c59 100644
--- 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/workflow-executor.adoc
+++ 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/workflow-executor.adoc
@@ -80,6 +80,7 @@ The filename may contain variables (for example, you can use 
the built-in Intern
 
 *NOTE:* It must be considered that, by specifying the workflow file name using 
this option, we can experience a little performance penalty because the 
workflow that will be executed will be initialized at runtime and not during 
the initialization phase as usual. On the other side this option gives greater 
flexibility in specifying the workflow filename dynamically and this could be 
useful for many use-cases.
 |Run configuration|Specify the Workflow Run Configuration to be used for 
execution.
+|Stop after (milliseconds)|Maximum time to wait for the child workflow to 
complete. Empty or 0 means wait indefinitely. When the time is reached the 
child workflow is stopped and the execution-results stream reports a failure 
(`ExecutionResult=N`, `ExecutionNrErrors>=1`). The value may contain a variable.
 |===
 
 === Parameters Tab
diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/pipeline.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/pipeline.adoc
index 917e006ca2..ff43ea0722 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/pipeline.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/pipeline.adoc
@@ -65,6 +65,7 @@ Select the desired run configuration to control where and how 
the pipeline is ex
 |Clear results rows before execution|Makes sure the results rows are cleared 
before the pipeline starts.
 |Clear results files before execution|Makes sure the results files are cleared 
before the pipeline starts.
 |Wait for remote pipeline to finish|If you selected Server as your environment 
type, choose this option to block the workflow until the pipeline runs on the 
server.
+|Stop after (milliseconds)|Maximum time to wait for the pipeline to complete. 
Empty or 0 means wait indefinitely. When the time is reached the pipeline is 
stopped and this action fails. Only used when wait-to-finish is enabled. The 
value may contain a variable.
 |Follow local abort to remote pipeline|If you selected Server as your 
environment type, choose this option to send the local abort signal remotely.
 |===
 
diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/workflow.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/workflow.adoc
index e33fb08572..b167b90d49 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/workflow.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/workflow/actions/workflow.adoc
@@ -69,6 +69,7 @@ For example, if your `{openvar}PROJECT_HOME{closevar}` is 
`/home/admin/hop/proje
 One row is passed to the workflow at every execution.
 For example, you can execute a workflow for each file found in a 
directory.|false
 |Wait for the remote workflow to finish?|Enable to block until the workflow on 
the Hop Server has finished|true
+|Stop after (milliseconds)|Maximum time to wait for the workflow to complete. 
Empty or 0 means wait indefinitely. When the time is reached the workflow is 
stopped and this action fails. Only used when wait-to-finish is enabled. The 
value may contain a variable.|empty (no limit)
 |===
 
 === Logging Settings Tab
diff --git a/engine/src/main/java/org/apache/hop/execution/ExecutionWait.java 
b/engine/src/main/java/org/apache/hop/execution/ExecutionWait.java
new file mode 100644
index 0000000000..c9cd0ef2c5
--- /dev/null
+++ b/engine/src/main/java/org/apache/hop/execution/ExecutionWait.java
@@ -0,0 +1,155 @@
+/*
+ * 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.hop.execution;
+
+import java.util.function.BooleanSupplier;
+import org.apache.hop.core.Const;
+import org.apache.hop.core.util.Utils;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.pipeline.engine.IPipelineEngine;
+
+/**
+ * Wait helpers for nested pipeline and workflow execution with an optional 
millisecond timeout.
+ *
+ * <p>Empty, blank, {@code 0} or a non-numeric timeout means wait indefinitely.
+ */
+public final class ExecutionWait {
+
+  private static final long POLL_MS = 50L;
+
+  private ExecutionWait() {
+    // utility
+  }
+
+  /**
+   * Resolve and parse a wait-timeout specification.
+   *
+   * @param variables variable space used to resolve the spec
+   * @param spec milliseconds as a number or variable expression
+   * @return timeout in milliseconds, or {@code 0} for no limit
+   */
+  public static long parseTimeoutMs(IVariables variables, String spec) {
+    String resolved = Const.trim(variables == null ? spec : 
variables.resolve(Const.NVL(spec, "")));
+    if (Utils.isEmpty(resolved)) {
+      return 0L;
+    }
+    long value = Const.toLong(resolved, 0L);
+    return Math.max(0L, value);
+  }
+
+  /**
+   * Wait until {@code done} or {@code abort} is true, or until {@code 
timeoutMs} elapses.
+   *
+   * @param done returns true when the work finished
+   * @param abort optional extra stop condition (parent stopped); may be 
{@code null}
+   * @param timeoutMs maximum wait in milliseconds; {@code <= 0} means no limit
+   * @return {@code false} if the timeout elapsed; {@code true} if done or 
aborted first
+   */
+  public static boolean waitFor(BooleanSupplier done, BooleanSupplier abort, 
long timeoutMs) {
+    long deadline = timeoutMs <= 0 ? Long.MAX_VALUE : 
System.currentTimeMillis() + timeoutMs;
+    while (!safeGet(done) && !safeGet(abort)) {
+      if (timeoutMs > 0 && System.currentTimeMillis() >= deadline) {
+        return false;
+      }
+      try {
+        Thread.sleep(POLL_MS);
+      } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Wait for a pipeline using the engine's own {@code waitUntilFinished()}. 
When a positive timeout
+   * elapses the pipeline is stopped and this method waits until that stop 
completes.
+   *
+   * @return {@code true} if the pipeline finished (or was already stopped) 
before the timeout;
+   *     {@code false} if the timeout elapsed and the pipeline was stopped 
because of it
+   */
+  public static boolean waitForPipeline(IPipelineEngine<?> pipeline, long 
timeoutMs) {
+    if (pipeline == null) {
+      return true;
+    }
+    if (timeoutMs <= 0) {
+      pipeline.waitUntilFinished();
+      return true;
+    }
+
+    Thread waiter =
+        new Thread(pipeline::waitUntilFinished, "wait-pipeline-" + 
pipeline.getLogChannelId());
+    waiter.setDaemon(true);
+    waiter.start();
+    try {
+      waiter.join(timeoutMs);
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+    }
+    if (!waiter.isAlive()) {
+      return true;
+    }
+
+    pipeline.stopAll();
+    joinQuietly(waiter);
+    return false;
+  }
+
+  /**
+   * Wait for a worker thread to finish. On timeout the caller is expected to 
stop the engine
+   * running on that thread, then join the thread.
+   *
+   * @return {@code false} if the timeout elapsed while the thread was still 
alive
+   */
+  public static boolean waitForThread(Thread thread, BooleanSupplier abort, 
long timeoutMs) {
+    if (thread == null) {
+      return true;
+    }
+    long deadline = timeoutMs <= 0 ? Long.MAX_VALUE : 
System.currentTimeMillis() + timeoutMs;
+    while (thread.isAlive() && !safeGet(abort)) {
+      if (timeoutMs > 0 && System.currentTimeMillis() >= deadline) {
+        return false;
+      }
+      joinQuietly(thread, POLL_MS);
+    }
+    return !thread.isAlive() || safeGet(abort);
+  }
+
+  public static void joinQuietly(Thread thread) {
+    joinQuietly(thread, 0L);
+  }
+
+  public static void joinQuietly(Thread thread, long millis) {
+    if (thread == null) {
+      return;
+    }
+    try {
+      if (millis > 0) {
+        thread.join(millis);
+      } else {
+        thread.join();
+      }
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+    }
+  }
+
+  private static boolean safeGet(BooleanSupplier supplier) {
+    return supplier != null && supplier.getAsBoolean();
+  }
+}
diff --git 
a/engine/src/test/java/org/apache/hop/execution/ExecutionWaitTest.java 
b/engine/src/test/java/org/apache/hop/execution/ExecutionWaitTest.java
new file mode 100644
index 0000000000..a5df453ff4
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/execution/ExecutionWaitTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.hop.execution;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.pipeline.engine.IPipelineEngine;
+import org.junit.jupiter.api.Test;
+
+class ExecutionWaitTest {
+
+  @Test
+  void parseTimeoutMsTreatsEmptyAndInvalidAsUnlimited() {
+    Variables vars = new Variables();
+    assertEquals(0L, ExecutionWait.parseTimeoutMs(vars, null));
+    assertEquals(0L, ExecutionWait.parseTimeoutMs(vars, ""));
+    assertEquals(0L, ExecutionWait.parseTimeoutMs(vars, "  "));
+    assertEquals(0L, ExecutionWait.parseTimeoutMs(vars, "0"));
+    assertEquals(0L, ExecutionWait.parseTimeoutMs(vars, "soon"));
+    assertEquals(0L, ExecutionWait.parseTimeoutMs(vars, "-5"));
+    assertEquals(1500L, ExecutionWait.parseTimeoutMs(vars, "1500"));
+    vars.setVariable("T", "250");
+    assertEquals(250L, ExecutionWait.parseTimeoutMs(vars, "${T}"));
+  }
+
+  @Test
+  void waitForReturnsImmediatelyWhenAlreadyDone() {
+    assertTrue(ExecutionWait.waitFor(() -> true, () -> false, 5_000L));
+  }
+
+  @Test
+  void waitForTimesOutWhenNeverDone() {
+    long start = System.currentTimeMillis();
+    assertFalse(ExecutionWait.waitFor(() -> false, () -> false, 80L));
+    assertTrue(System.currentTimeMillis() - start < 2_000L);
+  }
+
+  @Test
+  void waitForReturnsTrueWhenAborted() {
+    AtomicBoolean abort = new AtomicBoolean(true);
+    assertTrue(ExecutionWait.waitFor(() -> false, abort::get, 5_000L));
+  }
+
+  @Test
+  void waitForPipelineWithoutTimeoutDelegatesToEngine() {
+    @SuppressWarnings("unchecked")
+    IPipelineEngine<?> engine = mock(IPipelineEngine.class);
+    assertTrue(ExecutionWait.waitForPipeline(engine, 0L));
+    verify(engine).waitUntilFinished();
+    verify(engine, never()).stopAll();
+  }
+
+  @Test
+  void waitForPipelineStopsEngineWhenTimeoutElapses() throws Exception {
+    CountDownLatch started = new CountDownLatch(1);
+    CountDownLatch released = new CountDownLatch(1);
+
+    @SuppressWarnings("unchecked")
+    IPipelineEngine<?> engine = mock(IPipelineEngine.class);
+    when(engine.getLogChannelId()).thenReturn("test-channel");
+    doAnswer(
+            invocation -> {
+              started.countDown();
+              released.await();
+              return null;
+            })
+        .when(engine)
+        .waitUntilFinished();
+    doAnswer(
+            invocation -> {
+              released.countDown();
+              return null;
+            })
+        .when(engine)
+        .stopAll();
+
+    long start = System.currentTimeMillis();
+    boolean finishedInTime = ExecutionWait.waitForPipeline(engine, 80L);
+    assertFalse(finishedInTime);
+    assertTrue(System.currentTimeMillis() - start < 2_000L);
+    verify(engine).stopAll();
+    started.await();
+  }
+
+  @Test
+  void waitForPipelineReturnsTrueWhenEngineFinishesBeforeTimeout() {
+    @SuppressWarnings("unchecked")
+    IPipelineEngine<?> engine = mock(IPipelineEngine.class);
+    when(engine.getLogChannelId()).thenReturn("fast");
+    assertTrue(ExecutionWait.waitForPipeline(engine, 5_000L));
+    verify(engine).waitUntilFinished();
+    verify(engine, never()).stopAll();
+  }
+}
diff --git a/integration-tests/actions/0022-fast-pipeline.hpl 
b/integration-tests/actions/0022-fast-pipeline.hpl
new file mode 100644
index 0000000000..ffc52b8369
--- /dev/null
+++ b/integration-tests/actions/0022-fast-pipeline.hpl
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+  <info>
+    <name>0022-fast-pipeline</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description/>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <parameters>
+    </parameters>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <created_user>-</created_user>
+    <created_date>2026/04/20 00:00:00.000</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/04/20 00:00:00.000</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+  </order>
+  <transform>
+    <name>Generate rows</name>
+    <type>RowGenerator</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <fields>
+    </fields>
+    <interval_in_ms>5000</interval_in_ms>
+    <last_time_field>FiveSecondsAgo</last_time_field>
+    <limit>1</limit>
+    <never_ending>N</never_ending>
+    <row_time_field>now</row_time_field>
+    <attributes/>
+    <GUI>
+      <xloc>160</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/actions/0022-fast-workflow.hwf 
b/integration-tests/actions/0022-fast-workflow.hwf
new file mode 100644
index 0000000000..64e049e1e0
--- /dev/null
+++ b/integration-tests/actions/0022-fast-workflow.hwf
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<workflow>
+  <name>0022-fast-workflow</name>
+  <name_sync_with_filename>Y</name_sync_with_filename>
+  <description/>
+  <extended_description/>
+  <workflow_version/>
+  <created_user>-</created_user>
+  <created_date>2026/04/20 00:00:00.000</created_date>
+  <modified_user>-</modified_user>
+  <modified_date>2026/04/20 00:00:00.000</modified_date>
+  <parameters>
+    </parameters>
+  <actions>
+    <action>
+      <name>Start</name>
+      <description/>
+      <type>SPECIAL</type>
+      <attributes/>
+      <DayOfMonth>1</DayOfMonth>
+      <hour>12</hour>
+      <intervalMinutes>60</intervalMinutes>
+      <intervalSeconds>0</intervalSeconds>
+      <minutes>0</minutes>
+      <repeat>N</repeat>
+      <schedulerType>0</schedulerType>
+      <weekDay>1</weekDay>
+      <parallel>N</parallel>
+      <xloc>128</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Success</name>
+      <description/>
+      <type>SUCCESS</type>
+      <attributes/>
+      <parallel>N</parallel>
+      <xloc>288</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+  </actions>
+  <hops>
+    <hop>
+      <from>Start</from>
+      <to>Success</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>Y</unconditional>
+    </hop>
+  </hops>
+  <notepads>
+  </notepads>
+  <attributes/>
+</workflow>
diff --git a/integration-tests/actions/0022-slow-pipeline.hpl 
b/integration-tests/actions/0022-slow-pipeline.hpl
new file mode 100644
index 0000000000..373bd0fe7d
--- /dev/null
+++ b/integration-tests/actions/0022-slow-pipeline.hpl
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+  <info>
+    <name>0022-slow-pipeline</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description/>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <parameters>
+    </parameters>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <created_user>-</created_user>
+    <created_date>2026/04/20 00:00:00.000</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/04/20 00:00:00.000</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>Generate rows</from>
+      <to>Delay row</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>Generate rows</name>
+    <type>RowGenerator</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <fields>
+    </fields>
+    <interval_in_ms>5000</interval_in_ms>
+    <last_time_field>FiveSecondsAgo</last_time_field>
+    <limit>1</limit>
+    <never_ending>N</never_ending>
+    <row_time_field>now</row_time_field>
+    <attributes/>
+    <GUI>
+      <xloc>160</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Delay row</name>
+    <type>Delay</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <scaletime>seconds</scaletime>
+    <scaletime_from_field>N</scaletime_from_field>
+    <timeout>5</timeout>
+    <attributes/>
+    <GUI>
+      <xloc>352</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/actions/0022-slow-workflow.hwf 
b/integration-tests/actions/0022-slow-workflow.hwf
new file mode 100644
index 0000000000..6cf4947aff
--- /dev/null
+++ b/integration-tests/actions/0022-slow-workflow.hwf
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<workflow>
+  <name>0022-slow-workflow</name>
+  <name_sync_with_filename>Y</name_sync_with_filename>
+  <description/>
+  <extended_description/>
+  <workflow_version/>
+  <created_user>-</created_user>
+  <created_date>2026/04/20 00:00:00.000</created_date>
+  <modified_user>-</modified_user>
+  <modified_date>2026/04/20 00:00:00.000</modified_date>
+  <parameters>
+    </parameters>
+  <actions>
+    <action>
+      <name>Start</name>
+      <description/>
+      <type>SPECIAL</type>
+      <attributes/>
+      <DayOfMonth>1</DayOfMonth>
+      <hour>12</hour>
+      <intervalMinutes>60</intervalMinutes>
+      <intervalSeconds>0</intervalSeconds>
+      <minutes>0</minutes>
+      <repeat>N</repeat>
+      <schedulerType>0</schedulerType>
+      <weekDay>1</weekDay>
+      <parallel>N</parallel>
+      <xloc>128</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Wait 5s</name>
+      <description/>
+      <type>DELAY</type>
+      <attributes/>
+      <maximumTimeout>5</maximumTimeout>
+      <scaletime>0</scaletime>
+      <parallel>N</parallel>
+      <xloc>288</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Success</name>
+      <description/>
+      <type>SUCCESS</type>
+      <attributes/>
+      <parallel>N</parallel>
+      <xloc>448</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+  </actions>
+  <hops>
+    <hop>
+      <from>Start</from>
+      <to>Wait 5s</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>Y</unconditional>
+    </hop>
+    <hop>
+      <from>Wait 5s</from>
+      <to>Success</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+  </hops>
+  <notepads>
+  </notepads>
+  <attributes/>
+</workflow>
diff --git a/integration-tests/actions/main-0022-wait-timeout.hwf 
b/integration-tests/actions/main-0022-wait-timeout.hwf
new file mode 100644
index 0000000000..18bbb3f8a6
--- /dev/null
+++ b/integration-tests/actions/main-0022-wait-timeout.hwf
@@ -0,0 +1,253 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<workflow>
+  <name>main-0022-wait-timeout</name>
+  <name_sync_with_filename>Y</name_sync_with_filename>
+  <description/>
+  <extended_description/>
+  <workflow_version/>
+  <created_user>-</created_user>
+  <created_date>2026/04/20 00:00:00.000</created_date>
+  <modified_user>-</modified_user>
+  <modified_date>2026/04/20 00:00:00.000</modified_date>
+  <parameters>
+    </parameters>
+  <actions>
+    <action>
+      <name>Start</name>
+      <description/>
+      <type>SPECIAL</type>
+      <attributes/>
+      <DayOfMonth>1</DayOfMonth>
+      <hour>12</hour>
+      <intervalMinutes>60</intervalMinutes>
+      <intervalSeconds>0</intervalSeconds>
+      <minutes>0</minutes>
+      <repeat>N</repeat>
+      <schedulerType>0</schedulerType>
+      <weekDay>1</weekDay>
+      <parallel>N</parallel>
+      <xloc>96</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>pipeline times out</name>
+      <description/>
+      <type>PIPELINE</type>
+      <attributes/>
+      <filename>${PROJECT_HOME}/0022-slow-pipeline.hpl</filename>
+      <params_from_previous>N</params_from_previous>
+      <exec_per_row>N</exec_per_row>
+      <clear_rows>N</clear_rows>
+      <clear_files>N</clear_files>
+      <set_logfile>N</set_logfile>
+      <logfile/>
+      <logext/>
+      <add_date>N</add_date>
+      <add_time>N</add_time>
+      <loglevel>Basic</loglevel>
+      <set_append_logfile>N</set_append_logfile>
+      <wait_until_finished>Y</wait_until_finished>
+      <wait_timeout>500</wait_timeout>
+      <create_parent_folder>N</create_parent_folder>
+      <run_configuration>local</run_configuration>
+      <parameters>
+        <pass_all_parameters>Y</pass_all_parameters>
+      </parameters>
+      <parallel>N</parallel>
+      <xloc>288</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>workflow times out</name>
+      <description/>
+      <type>WORKFLOW</type>
+      <attributes/>
+      <filename>${PROJECT_HOME}/0022-slow-workflow.hwf</filename>
+      <params_from_previous>N</params_from_previous>
+      <exec_per_row>N</exec_per_row>
+      <set_logfile>N</set_logfile>
+      <logfile/>
+      <logext/>
+      <add_date>N</add_date>
+      <add_time>N</add_time>
+      <loglevel>Basic</loglevel>
+      <wait_until_finished>Y</wait_until_finished>
+      <wait_timeout>500</wait_timeout>
+      <create_parent_folder>N</create_parent_folder>
+      <set_append_logfile>N</set_append_logfile>
+      <run_configuration>local</run_configuration>
+      <parameters>
+        <pass_all_parameters>Y</pass_all_parameters>
+      </parameters>
+      <parallel>N</parallel>
+      <xloc>496</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>pipeline completes in time</name>
+      <description/>
+      <type>PIPELINE</type>
+      <attributes/>
+      <filename>${PROJECT_HOME}/0022-fast-pipeline.hpl</filename>
+      <params_from_previous>N</params_from_previous>
+      <exec_per_row>N</exec_per_row>
+      <clear_rows>N</clear_rows>
+      <clear_files>N</clear_files>
+      <set_logfile>N</set_logfile>
+      <logfile/>
+      <logext/>
+      <add_date>N</add_date>
+      <add_time>N</add_time>
+      <loglevel>Basic</loglevel>
+      <set_append_logfile>N</set_append_logfile>
+      <wait_until_finished>Y</wait_until_finished>
+      <wait_timeout>5000</wait_timeout>
+      <create_parent_folder>N</create_parent_folder>
+      <run_configuration>local</run_configuration>
+      <parameters>
+        <pass_all_parameters>Y</pass_all_parameters>
+      </parameters>
+      <parallel>N</parallel>
+      <xloc>704</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>workflow completes in time</name>
+      <description/>
+      <type>WORKFLOW</type>
+      <attributes/>
+      <filename>${PROJECT_HOME}/0022-fast-workflow.hwf</filename>
+      <params_from_previous>N</params_from_previous>
+      <exec_per_row>N</exec_per_row>
+      <set_logfile>N</set_logfile>
+      <logfile/>
+      <logext/>
+      <add_date>N</add_date>
+      <add_time>N</add_time>
+      <loglevel>Basic</loglevel>
+      <wait_until_finished>Y</wait_until_finished>
+      <wait_timeout>5000</wait_timeout>
+      <create_parent_folder>N</create_parent_folder>
+      <set_append_logfile>N</set_append_logfile>
+      <run_configuration>local</run_configuration>
+      <parameters>
+        <pass_all_parameters>Y</pass_all_parameters>
+      </parameters>
+      <parallel>N</parallel>
+      <xloc>912</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Success</name>
+      <description/>
+      <type>SUCCESS</type>
+      <attributes/>
+      <parallel>N</parallel>
+      <xloc>1120</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Abort</name>
+      <description/>
+      <type>ABORT</type>
+      <attributes/>
+      <message>Wait timeout test failed</message>
+      <parallel>N</parallel>
+      <xloc>496</xloc>
+      <yloc>256</yloc>
+      <attributes_hac/>
+    </action>
+  </actions>
+  <hops>
+    <hop>
+      <from>Start</from>
+      <to>pipeline times out</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>Y</unconditional>
+    </hop>
+    <hop>
+      <from>pipeline times out</from>
+      <to>workflow times out</to>
+      <enabled>Y</enabled>
+      <evaluation>N</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>pipeline times out</from>
+      <to>Abort</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>workflow times out</from>
+      <to>pipeline completes in time</to>
+      <enabled>Y</enabled>
+      <evaluation>N</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>workflow times out</from>
+      <to>Abort</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>pipeline completes in time</from>
+      <to>workflow completes in time</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>pipeline completes in time</from>
+      <to>Abort</to>
+      <enabled>Y</enabled>
+      <evaluation>N</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>workflow completes in time</from>
+      <to>Success</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>workflow completes in time</from>
+      <to>Abort</to>
+      <enabled>Y</enabled>
+      <evaluation>N</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+  </hops>
+  <notepads>
+  </notepads>
+  <attributes/>
+</workflow>
diff --git a/integration-tests/transforms/0107-fast-pipeline.hpl 
b/integration-tests/transforms/0107-fast-pipeline.hpl
new file mode 100644
index 0000000000..d9784e1b6b
--- /dev/null
+++ b/integration-tests/transforms/0107-fast-pipeline.hpl
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+  <info>
+    <name>0107-fast-pipeline</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description/>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <parameters>
+    </parameters>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <created_user>-</created_user>
+    <created_date>2026/04/20 00:00:00.000</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/04/20 00:00:00.000</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+  </order>
+  <transform>
+    <name>Generate rows</name>
+    <type>RowGenerator</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <fields>
+    </fields>
+    <interval_in_ms>5000</interval_in_ms>
+    <last_time_field>FiveSecondsAgo</last_time_field>
+    <limit>1</limit>
+    <never_ending>N</never_ending>
+    <row_time_field>now</row_time_field>
+    <attributes/>
+    <GUI>
+      <xloc>160</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/0107-fast-workflow.hwf 
b/integration-tests/transforms/0107-fast-workflow.hwf
new file mode 100644
index 0000000000..498455c302
--- /dev/null
+++ b/integration-tests/transforms/0107-fast-workflow.hwf
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<workflow>
+  <name>0107-fast-workflow</name>
+  <name_sync_with_filename>Y</name_sync_with_filename>
+  <description/>
+  <extended_description/>
+  <workflow_version/>
+  <created_user>-</created_user>
+  <created_date>2026/04/20 00:00:00.000</created_date>
+  <modified_user>-</modified_user>
+  <modified_date>2026/04/20 00:00:00.000</modified_date>
+  <parameters>
+    </parameters>
+  <actions>
+    <action>
+      <name>Start</name>
+      <description/>
+      <type>SPECIAL</type>
+      <attributes/>
+      <DayOfMonth>1</DayOfMonth>
+      <hour>12</hour>
+      <intervalMinutes>60</intervalMinutes>
+      <intervalSeconds>0</intervalSeconds>
+      <minutes>0</minutes>
+      <repeat>N</repeat>
+      <schedulerType>0</schedulerType>
+      <weekDay>1</weekDay>
+      <parallel>N</parallel>
+      <xloc>128</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Success</name>
+      <description/>
+      <type>SUCCESS</type>
+      <attributes/>
+      <parallel>N</parallel>
+      <xloc>288</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+  </actions>
+  <hops>
+    <hop>
+      <from>Start</from>
+      <to>Success</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>Y</unconditional>
+    </hop>
+  </hops>
+  <notepads>
+  </notepads>
+  <attributes/>
+</workflow>
diff --git a/integration-tests/transforms/0107-pipeline-executor-ok.hpl 
b/integration-tests/transforms/0107-pipeline-executor-ok.hpl
new file mode 100644
index 0000000000..f68daebc1c
--- /dev/null
+++ b/integration-tests/transforms/0107-pipeline-executor-ok.hpl
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+  <info>
+    <name>0107-pipeline-executor-ok</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description/>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <parameters>
+    </parameters>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <created_user>-</created_user>
+    <created_date>2026/04/20 00:00:00.000</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/04/20 00:00:00.000</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>Generate rows</from>
+      <to>Execute fast pipeline</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>Generate rows</name>
+    <type>RowGenerator</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <fields>
+    </fields>
+    <interval_in_ms>5000</interval_in_ms>
+    <last_time_field>FiveSecondsAgo</last_time_field>
+    <limit>1</limit>
+    <never_ending>N</never_ending>
+    <row_time_field>now</row_time_field>
+    <attributes/>
+    <GUI>
+      <xloc>128</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Execute fast pipeline</name>
+    <type>PipelineExecutor</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <run_configuration>local</run_configuration>
+    <wait_timeout>5000</wait_timeout>
+    <filename>${PROJECT_HOME}/0107-fast-pipeline.hpl</filename>
+    <group_size>1</group_size>
+    <group_field/>
+    <group_time/>
+    <parameters>
+      <inherit_all_vars>Y</inherit_all_vars>
+    </parameters>
+    <execution_result_target_transform/>
+    <execution_time_field>ExecutionTime</execution_time_field>
+    <execution_result_field>ExecutionResult</execution_result_field>
+    <execution_errors_field>ExecutionNrErrors</execution_errors_field>
+    <result_files_file_name_field>FileName</result_files_file_name_field>
+    <attributes/>
+    <GUI>
+      <xloc>352</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/0107-pipeline-executor-timeout.hpl 
b/integration-tests/transforms/0107-pipeline-executor-timeout.hpl
new file mode 100644
index 0000000000..6a74ec9bd3
--- /dev/null
+++ b/integration-tests/transforms/0107-pipeline-executor-timeout.hpl
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+  <info>
+    <name>0107-pipeline-executor-timeout</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description/>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <parameters>
+    </parameters>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <created_user>-</created_user>
+    <created_date>2026/04/20 00:00:00.000</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/04/20 00:00:00.000</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>Generate rows</from>
+      <to>Execute slow pipeline</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>Generate rows</name>
+    <type>RowGenerator</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <fields>
+    </fields>
+    <interval_in_ms>5000</interval_in_ms>
+    <last_time_field>FiveSecondsAgo</last_time_field>
+    <limit>1</limit>
+    <never_ending>N</never_ending>
+    <row_time_field>now</row_time_field>
+    <attributes/>
+    <GUI>
+      <xloc>128</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Execute slow pipeline</name>
+    <type>PipelineExecutor</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <run_configuration>local</run_configuration>
+    <wait_timeout>500</wait_timeout>
+    <filename>${PROJECT_HOME}/0107-slow-pipeline.hpl</filename>
+    <group_size>1</group_size>
+    <group_field/>
+    <group_time/>
+    <parameters>
+      <inherit_all_vars>Y</inherit_all_vars>
+    </parameters>
+    <execution_result_target_transform/>
+    <execution_time_field>ExecutionTime</execution_time_field>
+    <execution_result_field>ExecutionResult</execution_result_field>
+    <execution_errors_field>ExecutionNrErrors</execution_errors_field>
+    <result_files_file_name_field>FileName</result_files_file_name_field>
+    <attributes/>
+    <GUI>
+      <xloc>352</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/0107-slow-pipeline.hpl 
b/integration-tests/transforms/0107-slow-pipeline.hpl
new file mode 100644
index 0000000000..b237945ecd
--- /dev/null
+++ b/integration-tests/transforms/0107-slow-pipeline.hpl
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+  <info>
+    <name>0107-slow-pipeline</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description/>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <parameters>
+    </parameters>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <created_user>-</created_user>
+    <created_date>2026/04/20 00:00:00.000</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/04/20 00:00:00.000</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>Generate rows</from>
+      <to>Delay row</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>Generate rows</name>
+    <type>RowGenerator</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <fields>
+    </fields>
+    <interval_in_ms>5000</interval_in_ms>
+    <last_time_field>FiveSecondsAgo</last_time_field>
+    <limit>1</limit>
+    <never_ending>N</never_ending>
+    <row_time_field>now</row_time_field>
+    <attributes/>
+    <GUI>
+      <xloc>160</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Delay row</name>
+    <type>Delay</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <scaletime>seconds</scaletime>
+    <scaletime_from_field>N</scaletime_from_field>
+    <timeout>5</timeout>
+    <attributes/>
+    <GUI>
+      <xloc>352</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/0107-slow-workflow.hwf 
b/integration-tests/transforms/0107-slow-workflow.hwf
new file mode 100644
index 0000000000..fd5cd7f79e
--- /dev/null
+++ b/integration-tests/transforms/0107-slow-workflow.hwf
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<workflow>
+  <name>0107-slow-workflow</name>
+  <name_sync_with_filename>Y</name_sync_with_filename>
+  <description/>
+  <extended_description/>
+  <workflow_version/>
+  <created_user>-</created_user>
+  <created_date>2026/04/20 00:00:00.000</created_date>
+  <modified_user>-</modified_user>
+  <modified_date>2026/04/20 00:00:00.000</modified_date>
+  <parameters>
+    </parameters>
+  <actions>
+    <action>
+      <name>Start</name>
+      <description/>
+      <type>SPECIAL</type>
+      <attributes/>
+      <DayOfMonth>1</DayOfMonth>
+      <hour>12</hour>
+      <intervalMinutes>60</intervalMinutes>
+      <intervalSeconds>0</intervalSeconds>
+      <minutes>0</minutes>
+      <repeat>N</repeat>
+      <schedulerType>0</schedulerType>
+      <weekDay>1</weekDay>
+      <parallel>N</parallel>
+      <xloc>128</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Wait 5s</name>
+      <description/>
+      <type>DELAY</type>
+      <attributes/>
+      <maximumTimeout>5</maximumTimeout>
+      <scaletime>0</scaletime>
+      <parallel>N</parallel>
+      <xloc>288</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Success</name>
+      <description/>
+      <type>SUCCESS</type>
+      <attributes/>
+      <parallel>N</parallel>
+      <xloc>448</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+  </actions>
+  <hops>
+    <hop>
+      <from>Start</from>
+      <to>Wait 5s</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>Y</unconditional>
+    </hop>
+    <hop>
+      <from>Wait 5s</from>
+      <to>Success</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+  </hops>
+  <notepads>
+  </notepads>
+  <attributes/>
+</workflow>
diff --git a/integration-tests/transforms/0107-workflow-executor-ok.hpl 
b/integration-tests/transforms/0107-workflow-executor-ok.hpl
new file mode 100644
index 0000000000..50facd53ba
--- /dev/null
+++ b/integration-tests/transforms/0107-workflow-executor-ok.hpl
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+  <info>
+    <name>0107-workflow-executor-ok</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description/>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <parameters>
+    </parameters>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <created_user>-</created_user>
+    <created_date>2026/04/20 00:00:00.000</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/04/20 00:00:00.000</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>Generate rows</from>
+      <to>Execute fast workflow</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>Generate rows</name>
+    <type>RowGenerator</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <fields>
+    </fields>
+    <interval_in_ms>5000</interval_in_ms>
+    <last_time_field>FiveSecondsAgo</last_time_field>
+    <limit>1</limit>
+    <never_ending>N</never_ending>
+    <row_time_field>now</row_time_field>
+    <attributes/>
+    <GUI>
+      <xloc>128</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Execute fast workflow</name>
+    <type>WorkflowExecutor</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <run_configuration>local</run_configuration>
+    <wait_timeout>5000</wait_timeout>
+    <filename>${PROJECT_HOME}/0107-fast-workflow.hwf</filename>
+    <group_size>1</group_size>
+    <inherit_all_vars>Y</inherit_all_vars>
+    <parameters>
+    </parameters>
+    <execution_time_field>ExecutionTime</execution_time_field>
+    <execution_result_field>ExecutionResult</execution_result_field>
+    <execution_errors_field>ExecutionNrErrors</execution_errors_field>
+    <result_files_file_name_field>FileName</result_files_file_name_field>
+    <attributes/>
+    <GUI>
+      <xloc>352</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/0107-workflow-executor-timeout.hpl 
b/integration-tests/transforms/0107-workflow-executor-timeout.hpl
new file mode 100644
index 0000000000..7b2173db2a
--- /dev/null
+++ b/integration-tests/transforms/0107-workflow-executor-timeout.hpl
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+  <info>
+    <name>0107-workflow-executor-timeout</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description/>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <parameters>
+    </parameters>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <created_user>-</created_user>
+    <created_date>2026/04/20 00:00:00.000</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/04/20 00:00:00.000</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>Generate rows</from>
+      <to>Execute slow workflow</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>Generate rows</name>
+    <type>RowGenerator</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <fields>
+    </fields>
+    <interval_in_ms>5000</interval_in_ms>
+    <last_time_field>FiveSecondsAgo</last_time_field>
+    <limit>1</limit>
+    <never_ending>N</never_ending>
+    <row_time_field>now</row_time_field>
+    <attributes/>
+    <GUI>
+      <xloc>128</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Execute slow workflow</name>
+    <type>WorkflowExecutor</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <run_configuration>local</run_configuration>
+    <wait_timeout>500</wait_timeout>
+    <filename>${PROJECT_HOME}/0107-slow-workflow.hwf</filename>
+    <group_size>1</group_size>
+    <inherit_all_vars>Y</inherit_all_vars>
+    <parameters>
+    </parameters>
+    <execution_time_field>ExecutionTime</execution_time_field>
+    <execution_result_field>ExecutionResult</execution_result_field>
+    <execution_errors_field>ExecutionNrErrors</execution_errors_field>
+    <result_files_file_name_field>FileName</result_files_file_name_field>
+    <attributes/>
+    <GUI>
+      <xloc>352</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/transforms/main-0107-executor-wait-timeout.hwf 
b/integration-tests/transforms/main-0107-executor-wait-timeout.hwf
new file mode 100644
index 0000000000..de49268a45
--- /dev/null
+++ b/integration-tests/transforms/main-0107-executor-wait-timeout.hwf
@@ -0,0 +1,289 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<workflow>
+  <name>main-0107-executor-wait-timeout</name>
+  <name_sync_with_filename>Y</name_sync_with_filename>
+  <description/>
+  <extended_description/>
+  <workflow_version/>
+  <created_user>-</created_user>
+  <created_date>2026/04/20 00:00:00.000</created_date>
+  <modified_user>-</modified_user>
+  <modified_date>2026/04/20 00:00:00.000</modified_date>
+  <parameters>
+    </parameters>
+  <actions>
+    <action>
+      <name>Start</name>
+      <description/>
+      <type>SPECIAL</type>
+      <attributes/>
+      <DayOfMonth>1</DayOfMonth>
+      <hour>12</hour>
+      <intervalMinutes>60</intervalMinutes>
+      <intervalSeconds>0</intervalSeconds>
+      <minutes>0</minutes>
+      <repeat>N</repeat>
+      <schedulerType>0</schedulerType>
+      <weekDay>1</weekDay>
+      <parallel>N</parallel>
+      <xloc>80</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>pipeline executor times out</name>
+      <description/>
+      <type>PIPELINE</type>
+      <attributes/>
+      <filename>${PROJECT_HOME}/0107-pipeline-executor-timeout.hpl</filename>
+      <params_from_previous>N</params_from_previous>
+      <exec_per_row>N</exec_per_row>
+      <clear_rows>N</clear_rows>
+      <clear_files>N</clear_files>
+      <set_logfile>N</set_logfile>
+      <loglevel>Basic</loglevel>
+      <set_append_logfile>N</set_append_logfile>
+      <wait_until_finished>Y</wait_until_finished>
+      <create_parent_folder>N</create_parent_folder>
+      <run_configuration>local</run_configuration>
+      <parameters>
+        <pass_all_parameters>Y</pass_all_parameters>
+      </parameters>
+      <parallel>N</parallel>
+      <xloc>256</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>check pipeline executor timeout</name>
+      <description/>
+      <type>EVAL</type>
+      <attributes/>
+      <script>var txt = previous_result.getLogText();
+txt.contains("Maximum wait time");</script>
+      <parallel>N</parallel>
+      <xloc>448</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>workflow executor times out</name>
+      <description/>
+      <type>PIPELINE</type>
+      <attributes/>
+      <filename>${PROJECT_HOME}/0107-workflow-executor-timeout.hpl</filename>
+      <params_from_previous>N</params_from_previous>
+      <exec_per_row>N</exec_per_row>
+      <clear_rows>N</clear_rows>
+      <clear_files>N</clear_files>
+      <set_logfile>N</set_logfile>
+      <loglevel>Basic</loglevel>
+      <set_append_logfile>N</set_append_logfile>
+      <wait_until_finished>Y</wait_until_finished>
+      <create_parent_folder>N</create_parent_folder>
+      <run_configuration>local</run_configuration>
+      <parameters>
+        <pass_all_parameters>Y</pass_all_parameters>
+      </parameters>
+      <parallel>N</parallel>
+      <xloc>656</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>check workflow executor timeout</name>
+      <description/>
+      <type>EVAL</type>
+      <attributes/>
+      <script>var txt = previous_result.getLogText();
+txt.contains("Maximum wait time");</script>
+      <parallel>N</parallel>
+      <xloc>848</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>pipeline executor completes in time</name>
+      <description/>
+      <type>PIPELINE</type>
+      <attributes/>
+      <filename>${PROJECT_HOME}/0107-pipeline-executor-ok.hpl</filename>
+      <params_from_previous>N</params_from_previous>
+      <exec_per_row>N</exec_per_row>
+      <clear_rows>N</clear_rows>
+      <clear_files>N</clear_files>
+      <set_logfile>N</set_logfile>
+      <loglevel>Basic</loglevel>
+      <set_append_logfile>N</set_append_logfile>
+      <wait_until_finished>Y</wait_until_finished>
+      <create_parent_folder>N</create_parent_folder>
+      <run_configuration>local</run_configuration>
+      <parameters>
+        <pass_all_parameters>Y</pass_all_parameters>
+      </parameters>
+      <parallel>N</parallel>
+      <xloc>1040</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>workflow executor completes in time</name>
+      <description/>
+      <type>PIPELINE</type>
+      <attributes/>
+      <filename>${PROJECT_HOME}/0107-workflow-executor-ok.hpl</filename>
+      <params_from_previous>N</params_from_previous>
+      <exec_per_row>N</exec_per_row>
+      <clear_rows>N</clear_rows>
+      <clear_files>N</clear_files>
+      <set_logfile>N</set_logfile>
+      <loglevel>Basic</loglevel>
+      <set_append_logfile>N</set_append_logfile>
+      <wait_until_finished>Y</wait_until_finished>
+      <create_parent_folder>N</create_parent_folder>
+      <run_configuration>local</run_configuration>
+      <parameters>
+        <pass_all_parameters>Y</pass_all_parameters>
+      </parameters>
+      <parallel>N</parallel>
+      <xloc>1264</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Success</name>
+      <description/>
+      <type>SUCCESS</type>
+      <attributes/>
+      <parallel>N</parallel>
+      <xloc>1488</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Abort</name>
+      <description/>
+      <type>ABORT</type>
+      <attributes/>
+      <message>Executor wait timeout test failed</message>
+      <parallel>N</parallel>
+      <xloc>656</xloc>
+      <yloc>256</yloc>
+      <attributes_hac/>
+    </action>
+  </actions>
+  <hops>
+    <hop>
+      <from>Start</from>
+      <to>pipeline executor times out</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>Y</unconditional>
+    </hop>
+    <hop>
+      <from>pipeline executor times out</from>
+      <to>check pipeline executor timeout</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>pipeline executor times out</from>
+      <to>Abort</to>
+      <enabled>Y</enabled>
+      <evaluation>N</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>check pipeline executor timeout</from>
+      <to>workflow executor times out</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>check pipeline executor timeout</from>
+      <to>Abort</to>
+      <enabled>Y</enabled>
+      <evaluation>N</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>workflow executor times out</from>
+      <to>check workflow executor timeout</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>workflow executor times out</from>
+      <to>Abort</to>
+      <enabled>Y</enabled>
+      <evaluation>N</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>check workflow executor timeout</from>
+      <to>pipeline executor completes in time</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>check workflow executor timeout</from>
+      <to>Abort</to>
+      <enabled>Y</enabled>
+      <evaluation>N</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>pipeline executor completes in time</from>
+      <to>workflow executor completes in time</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>pipeline executor completes in time</from>
+      <to>Abort</to>
+      <enabled>Y</enabled>
+      <evaluation>N</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>workflow executor completes in time</from>
+      <to>Success</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>workflow executor completes in time</from>
+      <to>Abort</to>
+      <enabled>Y</enabled>
+      <evaluation>N</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+  </hops>
+  <notepads>
+  </notepads>
+  <attributes/>
+</workflow>
diff --git 
a/plugins/actions/pipeline/src/main/java/org/apache/hop/workflow/actions/pipeline/ActionPipeline.java
 
b/plugins/actions/pipeline/src/main/java/org/apache/hop/workflow/actions/pipeline/ActionPipeline.java
index 55e4a52ba4..8ef8dedb79 100644
--- 
a/plugins/actions/pipeline/src/main/java/org/apache/hop/workflow/actions/pipeline/ActionPipeline.java
+++ 
b/plugins/actions/pipeline/src/main/java/org/apache/hop/workflow/actions/pipeline/ActionPipeline.java
@@ -43,6 +43,7 @@ import org.apache.hop.core.util.FileUtil;
 import org.apache.hop.core.util.Utils;
 import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.core.vfs.HopVfs;
+import org.apache.hop.execution.ExecutionWait;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.metadata.api.HopMetadataProperty;
 import org.apache.hop.metadata.api.HopMetadataPropertyType;
@@ -196,6 +197,13 @@ public class ActionPipeline extends ActionBase implements 
Cloneable, IAction {
   @HopMetadataProperty(key = "wait_until_finished")
   private boolean waitingToFinish = true;
 
+  /**
+   * Maximum time to wait for the pipeline to complete, in milliseconds. Empty 
or 0 means wait
+   * indefinitely. Only used when {@link #waitingToFinish} is true.
+   */
+  @HopMetadataProperty(key = "wait_timeout")
+  private String waitTimeout;
+
   @HopMetadataProperty(key = "parameters")
   private ParameterDefinition parameterDefinition;
 
@@ -556,13 +564,22 @@ public class ActionPipeline extends ActionBase implements 
Cloneable, IAction {
           // Wait until we're done with this pipeline
           //
           if (isWaitingToFinish()) {
-            pipeline.waitUntilFinished();
+            long timeoutMs = ExecutionWait.parseTimeoutMs(this, waitTimeout);
+            boolean finishedInTime = ExecutionWait.waitForPipeline(pipeline, 
timeoutMs);
+            if (!finishedInTime) {
+              logError(
+                  BaseMessages.getString(
+                      PKG, "ActionPipeline.Log.WaitTimeoutReached", 
Long.toString(timeoutMs)));
+            }
 
             if (parentWorkflow.isStopped() || pipeline.getErrors() != 0) {
               pipeline.stopAll();
               result.setNrErrors(1);
             }
             updateResult(result);
+            if (!finishedInTime) {
+              result.setNrErrors(result.getNrErrors() + 1);
+            }
           }
           if (setLogfile) {
             ResultFile resultFile =
@@ -798,6 +815,14 @@ public class ActionPipeline extends ActionBase implements 
Cloneable, IAction {
     this.waitingToFinish = waitingToFinish;
   }
 
+  public String getWaitTimeout() {
+    return waitTimeout;
+  }
+
+  public void setWaitTimeout(String waitTimeout) {
+    this.waitTimeout = waitTimeout;
+  }
+
   public String getRunConfiguration() {
     return runConfiguration;
   }
diff --git 
a/plugins/actions/pipeline/src/main/java/org/apache/hop/workflow/actions/pipeline/ActionPipelineDialog.java
 
b/plugins/actions/pipeline/src/main/java/org/apache/hop/workflow/actions/pipeline/ActionPipelineDialog.java
index 4b871b266d..6722bdd690 100644
--- 
a/plugins/actions/pipeline/src/main/java/org/apache/hop/workflow/actions/pipeline/ActionPipelineDialog.java
+++ 
b/plugins/actions/pipeline/src/main/java/org/apache/hop/workflow/actions/pipeline/ActionPipelineDialog.java
@@ -35,6 +35,7 @@ import org.apache.hop.ui.core.dialog.BaseDialog;
 import org.apache.hop.ui.core.dialog.ErrorDialog;
 import org.apache.hop.ui.core.dialog.MessageBox;
 import org.apache.hop.ui.core.widget.MetaSelectionLine;
+import org.apache.hop.ui.core.widget.TextVar;
 import org.apache.hop.ui.hopgui.HopGui;
 import org.apache.hop.ui.hopgui.file.pipeline.HopPipelineFileType;
 import org.apache.hop.ui.util.SwtSvgImageUtil;
@@ -51,6 +52,7 @@ import org.eclipse.swt.layout.FormAttachment;
 import org.eclipse.swt.layout.FormData;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.TableItem;
 
@@ -60,6 +62,8 @@ public class ActionPipelineDialog extends ActionBaseDialog {
 
   private ActionPipeline action;
   private MetaSelectionLine<PipelineRunConfiguration> wRunConfiguration;
+  private Label wlWaitTimeout;
+  private TextVar wWaitTimeout;
 
   private static final String[] FILE_FILTERLOGNAMES =
       new String[] {
@@ -126,6 +130,24 @@ public class ActionPipelineDialog extends ActionBaseDialog 
{
     fdWait.top = new FormAttachment(wClearFiles, 10);
     fdWait.left = new FormAttachment(0, 0);
     wWaitingToFinish.setLayoutData(fdWait);
+    wWaitingToFinish.addListener(SWT.Selection, e -> enableWaitTimeout());
+
+    wlWaitTimeout = new Label(gExecution, SWT.LEFT);
+    PropsUi.setLook(wlWaitTimeout);
+    wlWaitTimeout.setText(BaseMessages.getString(PKG, 
"ActionPipeline.WaitTimeout.Label"));
+    FormData fdlWaitTimeout = new FormData();
+    fdlWaitTimeout.top = new FormAttachment(wWaitingToFinish, 10);
+    fdlWaitTimeout.left = new FormAttachment(0, 0);
+    wlWaitTimeout.setLayoutData(fdlWaitTimeout);
+
+    wWaitTimeout = new TextVar(variables, gExecution, SWT.SINGLE | SWT.LEFT | 
SWT.BORDER);
+    PropsUi.setLook(wWaitTimeout);
+    wWaitTimeout.setToolTipText(BaseMessages.getString(PKG, 
"ActionPipeline.WaitTimeout.Tooltip"));
+    FormData fdWaitTimeout = new FormData();
+    fdWaitTimeout.top = new FormAttachment(wlWaitTimeout, 0, SWT.CENTER);
+    fdWaitTimeout.left = new FormAttachment(wlWaitTimeout, 10);
+    fdWaitTimeout.right = new FormAttachment(100, 0);
+    wWaitTimeout.setLayoutData(fdWaitTimeout);
 
     // force reload from file specification
     wbGetParams.addListener(SWT.Selection, e -> getParameters(null));
@@ -257,6 +279,8 @@ public class ActionPipelineDialog extends ActionBaseDialog {
     wClearRows.setSelection(action.isClearResultRows());
     wClearFiles.setSelection(action.isClearResultFiles());
     wWaitingToFinish.setSelection(action.isWaitingToFinish());
+    wWaitTimeout.setText(Const.NVL(action.getWaitTimeout(), ""));
+    enableWaitTimeout();
     wAppendLogfile.setSelection(action.isSetAppendLogfile());
     wCreateParentFolder.setSelection(action.isCreateParentFolder());
     if (action.getLogFileLevel() != null) {
@@ -353,6 +377,13 @@ public class ActionPipelineDialog extends ActionBaseDialog 
{
     actionPipeline.setRunConfiguration(wRunConfiguration.getText());
     actionPipeline.setSetAppendLogfile(wAppendLogfile.getSelection());
     actionPipeline.setWaitingToFinish(wWaitingToFinish.getSelection());
+    actionPipeline.setWaitTimeout(wWaitTimeout.getText());
+  }
+
+  private void enableWaitTimeout() {
+    boolean enabled = wWaitingToFinish.getSelection();
+    wlWaitTimeout.setEnabled(enabled);
+    wWaitTimeout.setEnabled(enabled);
   }
 
   @Override
diff --git 
a/plugins/actions/pipeline/src/main/resources/org/apache/hop/workflow/actions/pipeline/messages/messages_en_US.properties
 
b/plugins/actions/pipeline/src/main/resources/org/apache/hop/workflow/actions/pipeline/messages/messages_en_US.properties
index 1b0775f40a..bc40e3f334 100644
--- 
a/plugins/actions/pipeline/src/main/resources/org/apache/hop/workflow/actions/pipeline/messages/messages_en_US.properties
+++ 
b/plugins/actions/pipeline/src/main/resources/org/apache/hop/workflow/actions/pipeline/messages/messages_en_US.properties
@@ -40,5 +40,8 @@ ActionPipeline.ReferencedObject.Description=Pipeline
 ActionPipeline.RunConfig.Message=Using run configuration [{0}]
 ActionPipeline.StartingPipeline=Starting pipeline...(file\={0}, name\={1}, 
repinfo\={2})
 ActionPipeline.WaitToFinish.Label=Wait for the pipeline to complete
+ActionPipeline.WaitTimeout.Label=Stop after (milliseconds)
+ActionPipeline.WaitTimeout.Tooltip=Maximum time to wait for the pipeline to 
complete, in milliseconds. Empty or 0 means wait indefinitely. When the time is 
reached the pipeline is stopped and this action fails. You can use a variable 
such as '${MAX_WAIT_MS}'.
+ActionPipeline.Log.WaitTimeoutReached=Maximum wait time of {0} ms reached, 
stopping the pipeline
 ActionPipelineDialog.Exception.UnableToLoadPipeline.Message=It was not 
possible to load the specified pipeline
 ActionPipelineDialog.Exception.UnableToLoadPipeline.Title=Error loading 
pipeline
diff --git 
a/plugins/actions/pipeline/src/test/java/org/apache/hop/workflow/actions/pipeline/ActionPipelineWaitTimeoutTest.java
 
b/plugins/actions/pipeline/src/test/java/org/apache/hop/workflow/actions/pipeline/ActionPipelineWaitTimeoutTest.java
new file mode 100644
index 0000000000..7d9e8f47a6
--- /dev/null
+++ 
b/plugins/actions/pipeline/src/test/java/org/apache/hop/workflow/actions/pipeline/ActionPipelineWaitTimeoutTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.hop.workflow.actions.pipeline;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+class ActionPipelineWaitTimeoutTest {
+
+  @Test
+  void waitTimeoutIsSerialized() {
+    ActionPipeline action = new ActionPipeline("run-child");
+    action.setWaitingToFinish(true);
+    action.setWaitTimeout("2500");
+    String xml = action.getXml();
+    assertTrue(xml.contains("<wait_timeout>2500</wait_timeout>"));
+    assertEquals("2500", action.getWaitTimeout());
+  }
+}
diff --git 
a/plugins/actions/workflow/src/main/java/org/apache/hop/workflow/actions/workflow/ActionWorkflow.java
 
b/plugins/actions/workflow/src/main/java/org/apache/hop/workflow/actions/workflow/ActionWorkflow.java
index e63e75b0d5..d2dee9a5a2 100644
--- 
a/plugins/actions/workflow/src/main/java/org/apache/hop/workflow/actions/workflow/ActionWorkflow.java
+++ 
b/plugins/actions/workflow/src/main/java/org/apache/hop/workflow/actions/workflow/ActionWorkflow.java
@@ -43,6 +43,7 @@ import org.apache.hop.core.util.CurrentDirectoryResolver;
 import org.apache.hop.core.util.Utils;
 import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.core.vfs.HopVfs;
+import org.apache.hop.execution.ExecutionWait;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.metadata.api.HopMetadataProperty;
 import org.apache.hop.metadata.api.HopMetadataPropertyType;
@@ -175,6 +176,13 @@ public class ActionWorkflow extends ActionBase implements 
Cloneable, IAction {
   @HopMetadataProperty(key = "wait_until_finished")
   private boolean waitingToFinish = true;
 
+  /**
+   * Maximum time to wait for the workflow to complete, in milliseconds. Empty 
or 0 means wait
+   * indefinitely. Only used when {@link #waitingToFinish} is true.
+   */
+  @HopMetadataProperty(key = "wait_timeout")
+  private String waitTimeout;
+
   @HopMetadataProperty(key = "parameters")
   private ParameterDefinition parameterDefinition;
 
@@ -513,20 +521,20 @@ public class ActionWorkflow extends ActionBase implements 
Cloneable, IAction {
         workflowRunnerThread.start();
 
         if (isWaitingToFinish()) {
-          // Keep running until we're done.
-          //
-          while (!runner.isFinished() && !parentWorkflow.isStopped()) {
-            try {
-              Thread.sleep(0, 1);
-            } catch (InterruptedException e) {
-              // Ignore
+          long timeoutMs = ExecutionWait.parseTimeoutMs(this, waitTimeout);
+          boolean finishedInTime =
+              ExecutionWait.waitFor(
+                  runner::isFinished, () -> parentWorkflow.isStopped(), 
timeoutMs);
+
+          // Stop the sub-workflow when the parent was stopped or the wait 
timed out.
+          if (!finishedInTime || parentWorkflow.isStopped()) {
+            if (!finishedInTime) {
+              logError(
+                  BaseMessages.getString(
+                      PKG, "ActionWorkflow.Log.WaitTimeoutReached", 
Long.toString(timeoutMs)));
             }
-          }
-
-          // if the parent-workflow was stopped, stop the sub-workflow too...
-          if (parentWorkflow.isStopped()) {
             workflow.stopExecution();
-            runner.waitUntilFinished(); // Wait until finished!
+            runner.waitUntilFinished();
           }
 
           oneResult = runner.getResult();
@@ -544,6 +552,9 @@ public class ActionWorkflow extends ActionBase implements 
Cloneable, IAction {
           if (oneResult.isResult() == false) {
             result.setNrErrors(result.getNrErrors() + 1);
           }
+          if (!finishedInTime && result.getNrErrors() == 0) {
+            result.setNrErrors(1);
+          }
         }
 
         iteration++;
@@ -846,6 +857,14 @@ public class ActionWorkflow extends ActionBase implements 
Cloneable, IAction {
     this.waitingToFinish = waitingToFinish;
   }
 
+  public String getWaitTimeout() {
+    return waitTimeout;
+  }
+
+  public void setWaitTimeout(String waitTimeout) {
+    this.waitTimeout = waitTimeout;
+  }
+
   public IWorkflowEngine<WorkflowMeta> getWorkflow() {
     return workflow;
   }
diff --git 
a/plugins/actions/workflow/src/main/java/org/apache/hop/workflow/actions/workflow/ActionWorkflowDialog.java
 
b/plugins/actions/workflow/src/main/java/org/apache/hop/workflow/actions/workflow/ActionWorkflowDialog.java
index 2f01ade6d9..0ab2318ee1 100644
--- 
a/plugins/actions/workflow/src/main/java/org/apache/hop/workflow/actions/workflow/ActionWorkflowDialog.java
+++ 
b/plugins/actions/workflow/src/main/java/org/apache/hop/workflow/actions/workflow/ActionWorkflowDialog.java
@@ -34,6 +34,7 @@ import org.apache.hop.ui.core.dialog.BaseDialog;
 import org.apache.hop.ui.core.dialog.ErrorDialog;
 import org.apache.hop.ui.core.dialog.MessageBox;
 import org.apache.hop.ui.core.widget.MetaSelectionLine;
+import org.apache.hop.ui.core.widget.TextVar;
 import org.apache.hop.ui.hopgui.HopGui;
 import org.apache.hop.ui.hopgui.file.workflow.HopWorkflowFileType;
 import org.apache.hop.ui.util.SwtSvgImageUtil;
@@ -50,6 +51,7 @@ import org.eclipse.swt.layout.FormAttachment;
 import org.eclipse.swt.layout.FormData;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.TableItem;
 
@@ -59,6 +61,8 @@ public class ActionWorkflowDialog extends ActionBaseDialog {
 
   private ActionWorkflow action;
   private MetaSelectionLine<WorkflowRunConfiguration> wRunConfiguration;
+  private Label wlWaitTimeout;
+  private TextVar wWaitTimeout;
 
   private static final String[] FILE_FILTERLOGNAMES =
       new String[] {
@@ -106,6 +110,24 @@ public class ActionWorkflowDialog extends ActionBaseDialog 
{
     fdWait.top = new FormAttachment(wEveryRow, 10);
     fdWait.left = new FormAttachment(0, 0);
     wWaitingToFinish.setLayoutData(fdWait);
+    wWaitingToFinish.addListener(SWT.Selection, e -> enableWaitTimeout());
+
+    wlWaitTimeout = new Label(gExecution, SWT.LEFT);
+    PropsUi.setLook(wlWaitTimeout);
+    wlWaitTimeout.setText(BaseMessages.getString(PKG, 
"ActionWorkflow.WaitTimeout.Label"));
+    FormData fdlWaitTimeout = new FormData();
+    fdlWaitTimeout.top = new FormAttachment(wWaitingToFinish, 10);
+    fdlWaitTimeout.left = new FormAttachment(0, 0);
+    wlWaitTimeout.setLayoutData(fdlWaitTimeout);
+
+    wWaitTimeout = new TextVar(variables, gExecution, SWT.SINGLE | SWT.LEFT | 
SWT.BORDER);
+    PropsUi.setLook(wWaitTimeout);
+    wWaitTimeout.setToolTipText(BaseMessages.getString(PKG, 
"ActionWorkflow.WaitTimeout.Tooltip"));
+    FormData fdWaitTimeout = new FormData();
+    fdWaitTimeout.top = new FormAttachment(wlWaitTimeout, 0, SWT.CENTER);
+    fdWaitTimeout.left = new FormAttachment(wlWaitTimeout, 10);
+    fdWaitTimeout.right = new FormAttachment(100, 0);
+    wWaitTimeout.setLayoutData(fdWaitTimeout);
 
     // force reload from file specification
     wbGetParams.addListener(SWT.Selection, e -> getParameters(null));
@@ -244,6 +266,8 @@ public class ActionWorkflowDialog extends ActionBaseDialog {
     wAppendLogfile.setSelection(action.isSetAppendLogfile());
     wCreateParentFolder.setSelection(action.isCreateParentFolder());
     wWaitingToFinish.setSelection(action.isWaitingToFinish());
+    wWaitTimeout.setText(Const.NVL(action.getWaitTimeout(), ""));
+    enableWaitTimeout();
 
     try {
       List<String> runConfigurations =
@@ -331,10 +355,17 @@ public class ActionWorkflowDialog extends 
ActionBaseDialog {
     action.setExecPerRow(wEveryRow.getSelection());
     action.setSetAppendLogfile(wAppendLogfile.getSelection());
     action.setWaitingToFinish(wWaitingToFinish.getSelection());
+    action.setWaitTimeout(wWaitTimeout.getText());
     action.setCreateParentFolder(wCreateParentFolder.getSelection());
     action.setRunConfiguration(wRunConfiguration.getText());
   }
 
+  private void enableWaitTimeout() {
+    boolean enabled = wWaitingToFinish.getSelection();
+    wlWaitTimeout.setEnabled(enabled);
+    wWaitTimeout.setEnabled(enabled);
+  }
+
   @Override
   public void ok() {
     if (Utils.isEmpty(getName())) {
diff --git 
a/plugins/actions/workflow/src/main/resources/org/apache/hop/workflow/actions/workflow/messages/messages_en_US.properties
 
b/plugins/actions/workflow/src/main/resources/org/apache/hop/workflow/actions/workflow/messages/messages_en_US.properties
index 4a81cd9a11..27890114ee 100644
--- 
a/plugins/actions/workflow/src/main/resources/org/apache/hop/workflow/actions/workflow/messages/messages_en_US.properties
+++ 
b/plugins/actions/workflow/src/main/resources/org/apache/hop/workflow/actions/workflow/messages/messages_en_US.properties
@@ -35,6 +35,9 @@ ActionWorkflow.Name=Workflow
 ActionWorkflow.PassAllParameters.Label=Pass parameter values to sub workflow
 ActionWorkflow.ReferencedObject.Description=Workflow
 ActionWorkflow.WaitToFinish.Label=Wait for remote workflow to complete
+ActionWorkflow.WaitTimeout.Label=Stop after (milliseconds)
+ActionWorkflow.WaitTimeout.Tooltip=Maximum time to wait for the workflow to 
complete, in milliseconds. Empty or 0 means wait indefinitely. When the time is 
reached the workflow is stopped and this action fails. You can use a variable 
such as '${MAX_WAIT_MS}'.
+ActionWorkflow.Log.WaitTimeoutReached=Maximum wait time of {0} ms reached, 
stopping the workflow
 ActionWorkflow.WorkflowFile.Label=Workflow\: 
 ActionWorkflowDialog.Exception.UnableToLoadWorkflow.Message=It was not 
possible to load the specified workflow
 ActionWorkflowDialog.Exception.UnableToLoadWorkflow.Title=Error loading 
workflow
diff --git 
a/plugins/actions/workflow/src/test/java/org/apache/hop/workflow/actions/workflow/ActionWorkflowWaitTimeoutTest.java
 
b/plugins/actions/workflow/src/test/java/org/apache/hop/workflow/actions/workflow/ActionWorkflowWaitTimeoutTest.java
new file mode 100644
index 0000000000..2b0f18b4ef
--- /dev/null
+++ 
b/plugins/actions/workflow/src/test/java/org/apache/hop/workflow/actions/workflow/ActionWorkflowWaitTimeoutTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.hop.workflow.actions.workflow;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+class ActionWorkflowWaitTimeoutTest {
+
+  @Test
+  void waitTimeoutIsSerialized() {
+    ActionWorkflow action = new ActionWorkflow("run-child");
+    action.setWaitingToFinish(true);
+    action.setWaitTimeout("2500");
+    String xml = action.getXml();
+    assertTrue(xml.contains("<wait_timeout>2500</wait_timeout>"));
+    assertEquals("2500", action.getWaitTimeout());
+  }
+}
diff --git 
a/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutor.java
 
b/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutor.java
index 92f613ad9e..2130674900 100644
--- 
a/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutor.java
+++ 
b/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutor.java
@@ -36,6 +36,7 @@ import org.apache.hop.core.row.IRowMeta;
 import org.apache.hop.core.row.RowDataUtil;
 import org.apache.hop.core.row.RowMeta;
 import org.apache.hop.core.util.Utils;
+import org.apache.hop.execution.ExecutionWait;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.pipeline.Pipeline;
 import org.apache.hop.pipeline.PipelineMeta;
@@ -262,9 +263,20 @@ public class PipelineExecutor extends 
BaseTransform<PipelineExecutorMeta, Pipeli
       executorPipeline.startThreads();
 
       // Wait a while until we're done with the pipeline
-      executorPipeline.waitUntilFinished();
+      long timeoutMs = ExecutionWait.parseTimeoutMs(this, 
meta.getWaitTimeout());
+      boolean finishedInTime = ExecutionWait.waitForPipeline(executorPipeline, 
timeoutMs);
 
       result = executorPipeline.getResult();
+      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);
diff --git 
a/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorDialog.java
 
b/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorDialog.java
index 20a5b6684a..6fd0827033 100644
--- 
a/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorDialog.java
+++ 
b/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorDialog.java
@@ -91,6 +91,9 @@ public class PipelineExecutorDialog extends 
BaseTransformDialog {
 
   protected MetaSelectionLine<PipelineRunConfiguration> wRunConfiguration;
 
+  private Label wlWaitTimeout;
+  private TextVar wWaitTimeout;
+
   private Button wbPipelineNameInField;
 
   private Label wlPipelineNameField;
@@ -255,6 +258,25 @@ public class PipelineExecutorDialog extends 
BaseTransformDialog {
     fdRunConfiguration.right = new FormAttachment(100, 0);
     wRunConfiguration.setLayoutData(fdRunConfiguration);
 
+    wlWaitTimeout = new Label(shell, SWT.RIGHT);
+    PropsUi.setLook(wlWaitTimeout);
+    wlWaitTimeout.setText(BaseMessages.getString(PKG, 
"PipelineExecutorDialog.WaitTimeout.Label"));
+    FormData fdlWaitTimeout = new FormData();
+    fdlWaitTimeout.left = new FormAttachment(0, 0);
+    fdlWaitTimeout.top = new FormAttachment(wRunConfiguration, margin);
+    fdlWaitTimeout.right = new FormAttachment(middle, -margin);
+    wlWaitTimeout.setLayoutData(fdlWaitTimeout);
+
+    wWaitTimeout = new TextVar(variables, shell, SWT.SINGLE | SWT.LEFT | 
SWT.BORDER);
+    PropsUi.setLook(wWaitTimeout);
+    wWaitTimeout.setToolTipText(
+        BaseMessages.getString(PKG, 
"PipelineExecutorDialog.WaitTimeout.Tooltip"));
+    FormData fdWaitTimeout = new FormData();
+    fdWaitTimeout.left = new FormAttachment(middle, 0);
+    fdWaitTimeout.top = new FormAttachment(wlWaitTimeout, 0, SWT.CENTER);
+    fdWaitTimeout.right = new FormAttachment(100, 0);
+    wWaitTimeout.setLayoutData(fdWaitTimeout);
+
     //
     // Add a tab folder for the parameters and various input and output
     // streams
@@ -265,7 +287,7 @@ public class PipelineExecutorDialog extends 
BaseTransformDialog {
 
     FormData fdTabFolder = new FormData();
     fdTabFolder.left = new FormAttachment(0, 0);
-    fdTabFolder.top = new FormAttachment(wRunConfiguration, 20);
+    fdTabFolder.top = new FormAttachment(wWaitTimeout, 20);
     fdTabFolder.right = new FormAttachment(100, 0);
     fdTabFolder.bottom = new FormAttachment(100, -50);
     wTabFolder.setLayoutData(fdTabFolder);
@@ -404,6 +426,8 @@ public class PipelineExecutorDialog extends 
BaseTransformDialog {
       LogChannel.UI.logError("Error getting pipeline run configurations", e);
     }
 
+    wWaitTimeout.setText(Const.NVL(pipelineExecutorMeta.getWaitTimeout(), ""));
+
     //  throw in a separate thread.
     //
     try {
@@ -1184,6 +1208,7 @@ public class PipelineExecutorDialog extends 
BaseTransformDialog {
     
pipelineExecutorMeta.setFilenameInField(wbPipelineNameInField.getSelection());
     pipelineExecutorMeta.setFilenameField(wPipelineNameField.getText());
     pipelineExecutorMeta.setRunConfigurationName(wRunConfiguration.getText());
+    pipelineExecutorMeta.setWaitTimeout(wWaitTimeout.getText());
 
     // Load the information on the tabs, optionally do some
     // verifications...
diff --git 
a/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorMeta.java
 
b/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorMeta.java
index c23efc891a..3a426790fd 100644
--- 
a/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorMeta.java
+++ 
b/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorMeta.java
@@ -80,6 +80,13 @@ public class PipelineExecutorMeta
       hopMetadataPropertyType = HopMetadataPropertyType.PIPELINE_RUN_CONFIG)
   private String runConfigurationName;
 
+  /**
+   * Maximum time to wait for the child pipeline to complete, in milliseconds. 
Empty or 0 means wait
+   * indefinitely.
+   */
+  @HopMetadataProperty(key = "wait_timeout")
+  private String waitTimeout;
+
   /** Flag that indicate that pipeline name is specified in a stream's field */
   @HopMetadataProperty(key = "filenameInField")
   private boolean filenameInField;
diff --git 
a/plugins/transforms/pipelineexecutor/src/main/resources/org/apache/hop/pipeline/transforms/pipelineexecutor/messages/messages_en_US.properties
 
b/plugins/transforms/pipelineexecutor/src/main/resources/org/apache/hop/pipeline/transforms/pipelineexecutor/messages/messages_en_US.properties
index 781cc76a8c..a7241d949f 100644
--- 
a/plugins/transforms/pipelineexecutor/src/main/resources/org/apache/hop/pipeline/transforms/pipelineexecutor/messages/messages_en_US.properties
+++ 
b/plugins/transforms/pipelineexecutor/src/main/resources/org/apache/hop/pipeline/transforms/pipelineexecutor/messages/messages_en_US.properties
@@ -18,6 +18,7 @@
 PipelineExecutor.Description=This transform executes a Hop pipeline, sets 
parameters and passes rows.
 PipelineExecutor.Exception.GroupFieldNotFound=Group field ''{0}'' could not be 
found in the input stream
 PipelineExecutor.Name=Pipeline executor
+PipelineExecutor.Log.WaitTimeoutReached=Maximum wait time of {0} ms reached, 
stopping the pipeline
 PipelineExecutor.UnexpectedError=There was an unexpected error\:
 PipelineExecutorDialog.Browse.Label=Browse...
 PipelineExecutorDialog.ColumnInfo.Field=Field name
@@ -72,6 +73,8 @@ PipelineExecutorDialog.RowGroup.Title=Row grouping
 PipelineExecutorDialog.RowGroup.Tooltip=Determine how many rows of data are 
passed to the pipeline before execution
 PipelineExecutorDialog.RunConfiguration.Label=Run configuration
 PipelineExecutorDialog.RunConfiguration.Tooltip=The pipeline run configuration 
used to execute the specified pipeline. Use the buttons to create, edit or open 
the selected configuration.
+PipelineExecutorDialog.WaitTimeout.Label=Stop after (milliseconds)
+PipelineExecutorDialog.WaitTimeout.Tooltip=Maximum time to wait for the 
pipeline to complete, in milliseconds. Empty or 0 means wait indefinitely. When 
the time is reached the pipeline is stopped and the execution result is 
reported as failed. You can use a variable such as '${MAX_WAIT_MS}'.
 PipelineExecutorDialog.Shell.Title=Pipeline executor
 PipelineExecutorDialog.TransformName.Label=Transform name
 PipelineExecutorMeta.CheckResult.NoInputReceived=No input is received from 
previous transforms
diff --git 
a/plugins/transforms/pipelineexecutor/src/test/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorMetaTest.java
 
b/plugins/transforms/pipelineexecutor/src/test/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorMetaTest.java
index cddde3a02a..2f2423506a 100644
--- 
a/plugins/transforms/pipelineexecutor/src/test/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorMetaTest.java
+++ 
b/plugins/transforms/pipelineexecutor/src/test/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutorMetaTest.java
@@ -38,6 +38,7 @@ class PipelineExecutorMetaTest {
             "/pipeline-executor-transform.xml", PipelineExecutorMeta.class);
 
     assertEquals("${PROJECT_HOME}/loops/child-loops-log-counter.hpl", 
meta.getFilename());
+    assertEquals("1500", meta.getWaitTimeout());
     assertEquals("execution results", 
meta.getExecutionResultTargetTransform());
     assertEquals("ExecutionTime", meta.getExecutionTimeField());
     assertEquals("ExecutionResult", meta.getExecutionResultField());
@@ -89,6 +90,7 @@ class PipelineExecutorMetaTest {
     assertEquals(meta.getResultFilesTargetTransform(), 
clone.getResultFilesTargetTransform());
     assertEquals(meta.getResultFilesFileNameField(), 
clone.getResultFilesFileNameField());
     assertEquals(meta.getExecutorsOutputTransform(), 
clone.getExecutorsOutputTransform());
+    assertEquals(meta.getWaitTimeout(), clone.getWaitTimeout());
     assertEquals(meta.getParameters().size(), clone.getParameters().size());
     assertEquals(meta.getResultRows().size(), clone.getResultRows().size());
   }
diff --git 
a/plugins/transforms/pipelineexecutor/src/test/resources/pipeline-executor-transform.xml
 
b/plugins/transforms/pipelineexecutor/src/test/resources/pipeline-executor-transform.xml
index a039927896..1c5423bb56 100644
--- 
a/plugins/transforms/pipelineexecutor/src/test/resources/pipeline-executor-transform.xml
+++ 
b/plugins/transforms/pipelineexecutor/src/test/resources/pipeline-executor-transform.xml
@@ -28,6 +28,7 @@
         <schema_name/>
     </partitioning>
     <run_configuration>local</run_configuration>
+    <wait_timeout>1500</wait_timeout>
     <filename>${PROJECT_HOME}/loops/child-loops-log-counter.hpl</filename>
     <filenameInField>N</filenameInField>
     <filenameField/>
diff --git 
a/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutor.java
 
b/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutor.java
index 956115ba7f..05dd2e4d6b 100644
--- 
a/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutor.java
+++ 
b/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutor.java
@@ -20,6 +20,7 @@ package org.apache.hop.pipeline.transforms.workflowexecutor;
 import com.google.common.annotations.VisibleForTesting;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.hop.core.Const;
 import org.apache.hop.core.Result;
@@ -34,6 +35,7 @@ import org.apache.hop.core.row.IValueMeta;
 import org.apache.hop.core.row.RowDataUtil;
 import org.apache.hop.core.row.value.ValueMetaFactory;
 import org.apache.hop.core.util.Utils;
+import org.apache.hop.execution.ExecutionWait;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.pipeline.Pipeline;
 import org.apache.hop.pipeline.PipelineMeta;
@@ -254,7 +256,38 @@ public class WorkflowExecutor extends 
BaseTransform<WorkflowExecutorMeta, Workfl
     //
     getPipeline().addActiveSubWorkflow(getTransformName(), 
data.executorWorkflow);
 
-    Result result = data.executorWorkflow.startExecution();
+    AtomicReference<Result> resultRef = new AtomicReference<>();
+    Thread runner =
+        new Thread(
+            () -> resultRef.set(data.executorWorkflow.startExecution()),
+            "WorkflowExecutor-" + getTransformName());
+    runner.start();
+
+    long timeoutMs = ExecutionWait.parseTimeoutMs(this, meta.getWaitTimeout());
+    boolean finishedInTime =
+        ExecutionWait.waitForThread(
+            runner, () -> isStopped() || getPipeline().isStopped(), timeoutMs);
+
+    if (!finishedInTime || isStopped() || getPipeline().isStopped()) {
+      if (!finishedInTime) {
+        logError(
+            BaseMessages.getString(
+                PKG, "WorkflowExecutor.Log.WaitTimeoutReached", 
Long.toString(timeoutMs)));
+      }
+      data.executorWorkflow.stopExecution();
+      ExecutionWait.joinQuietly(runner);
+    }
+
+    Result result = resultRef.get();
+    if (result == null) {
+      result = new Result();
+      result.setResult(false);
+      result.setNrErrors(1);
+    }
+    if (!finishedInTime) {
+      result.setResult(false);
+      result.setNrErrors(Math.max(1, result.getNrErrors()));
+    }
 
     // First the natural output...
     // Execution-result rows keep the first input row fields (e.g. filename) 
and append metrics.
diff --git 
a/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorDialog.java
 
b/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorDialog.java
index e54e727f72..7a04df2f80 100644
--- 
a/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorDialog.java
+++ 
b/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorDialog.java
@@ -97,6 +97,9 @@ public class WorkflowExecutorDialog extends 
BaseTransformDialog {
 
   protected MetaSelectionLine<WorkflowRunConfiguration> wRunConfiguration;
 
+  private Label wlWaitTimeout;
+  private TextVar wWaitTimeout;
+
   private CTabFolder wTabFolder;
 
   private WorkflowMeta executorWorkflowMeta = null;
@@ -255,6 +258,25 @@ public class WorkflowExecutorDialog extends 
BaseTransformDialog {
     fdRunConfiguration.right = new FormAttachment(100, 0);
     wRunConfiguration.setLayoutData(fdRunConfiguration);
 
+    wlWaitTimeout = new Label(shell, SWT.RIGHT);
+    PropsUi.setLook(wlWaitTimeout);
+    wlWaitTimeout.setText(BaseMessages.getString(PKG, 
"WorkflowExecutorDialog.WaitTimeout.Label"));
+    FormData fdlWaitTimeout = new FormData();
+    fdlWaitTimeout.left = new FormAttachment(0, 0);
+    fdlWaitTimeout.top = new FormAttachment(wRunConfiguration, margin);
+    fdlWaitTimeout.right = new FormAttachment(middle, -margin);
+    wlWaitTimeout.setLayoutData(fdlWaitTimeout);
+
+    wWaitTimeout = new TextVar(variables, shell, SWT.SINGLE | SWT.LEFT | 
SWT.BORDER);
+    PropsUi.setLook(wWaitTimeout);
+    wWaitTimeout.setToolTipText(
+        BaseMessages.getString(PKG, 
"WorkflowExecutorDialog.WaitTimeout.Tooltip"));
+    FormData fdWaitTimeout = new FormData();
+    fdWaitTimeout.left = new FormAttachment(middle, 0);
+    fdWaitTimeout.top = new FormAttachment(wlWaitTimeout, 0, SWT.CENTER);
+    fdWaitTimeout.right = new FormAttachment(100, 0);
+    wWaitTimeout.setLayoutData(fdWaitTimeout);
+
     //
     // Add a tab folder for the parameters and various input and output
     // streams
@@ -265,7 +287,7 @@ public class WorkflowExecutorDialog extends 
BaseTransformDialog {
 
     FormData fdTabFolder = new FormData();
     fdTabFolder.left = new FormAttachment(0, 0);
-    fdTabFolder.top = new FormAttachment(wRunConfiguration, 20);
+    fdTabFolder.top = new FormAttachment(wWaitTimeout, 20);
     fdTabFolder.right = new FormAttachment(100, 0);
     fdTabFolder.bottom = new FormAttachment(100, -50);
     wTabFolder.setLayoutData(fdTabFolder);
@@ -420,6 +442,8 @@ public class WorkflowExecutorDialog extends 
BaseTransformDialog {
       LogChannel.UI.logError("Error getting workflow run configurations", e);
     }
 
+    wWaitTimeout.setText(Const.NVL(workflowExecutorMeta.getWaitTimeout(), ""));
+
     try {
       String[] prevTransforms = pipelineMeta.getTransformNames();
       Arrays.sort(prevTransforms);
@@ -1165,6 +1189,7 @@ public class WorkflowExecutorDialog extends 
BaseTransformDialog {
     
workflowExecutorMeta.setFilenameInField(wbWorkflowNameInField.getSelection());
     workflowExecutorMeta.setFilenameField(wWorkflowNameField.getText());
     workflowExecutorMeta.setRunConfigurationName(wRunConfiguration.getText());
+    workflowExecutorMeta.setWaitTimeout(wWaitTimeout.getText());
 
     // Load the information on the tabs, optionally do some
     // verifications...
diff --git 
a/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorMeta.java
 
b/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorMeta.java
index d1ba9c7e2a..abf2590d6b 100644
--- 
a/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorMeta.java
+++ 
b/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorMeta.java
@@ -88,6 +88,13 @@ public class WorkflowExecutorMeta
       hopMetadataPropertyType = HopMetadataPropertyType.WORKFLOW_RUN_CONFIG)
   private String runConfigurationName;
 
+  /**
+   * Maximum time to wait for the child workflow to complete, in milliseconds. 
Empty or 0 means wait
+   * indefinitely.
+   */
+  @HopMetadataProperty(key = "wait_timeout")
+  private String waitTimeout;
+
   @HopMetadataProperty(
       key = "filename",
       hopMetadataPropertyType = HopMetadataPropertyType.WORKFLOW_FILE)
diff --git 
a/plugins/transforms/workflowexecutor/src/main/resources/org/apache/hop/pipeline/transforms/workflowexecutor/messages/messages_en_US.properties
 
b/plugins/transforms/workflowexecutor/src/main/resources/org/apache/hop/pipeline/transforms/workflowexecutor/messages/messages_en_US.properties
index dafea20319..7ca3c5bb09 100644
--- 
a/plugins/transforms/workflowexecutor/src/main/resources/org/apache/hop/pipeline/transforms/workflowexecutor/messages/messages_en_US.properties
+++ 
b/plugins/transforms/workflowexecutor/src/main/resources/org/apache/hop/pipeline/transforms/workflowexecutor/messages/messages_en_US.properties
@@ -21,6 +21,7 @@ WorkflowExecutor.Exception.UnableToFindField=Unable to find 
field ''{0}'' in the
 WorkflowExecutor.Exception.UnableToLoadWorkflow=Unable to load the specified 
workflow\: {0}
 WorkflowExecutor.IncorrectDataTypePassed=The ''{0}'' data types passed from 
the workflows result rows does not correspond to the specified ''{1}'' data 
types.  Make sure you are passing rows with the expected layout.
 WorkflowExecutor.Name=Workflow executor
+WorkflowExecutor.Log.WaitTimeoutReached=Maximum wait time of {0} ms reached, 
stopping the workflow
 WorkflowExecutor.UnexpectedError=There was an unexpected error\:
 WorkflowExecutorDialog.Browse.Label=Browse...
 WorkflowExecutorDialog.ColumnInfo.Field=Field name
@@ -71,6 +72,8 @@ WorkflowExecutorDialog.RowGroup.Title=Row grouping
 WorkflowExecutorDialog.RowGroup.Tooltip=Determine how many rows of data are 
passed to the workflow before execution
 WorkflowExecutorDialog.RunConfiguration.Label=Run configuration
 WorkflowExecutorDialog.RunConfiguration.Tooltip=The workflow run configuration 
used to execute the specified workflow. Use the buttons to create, edit or open 
the selected configuration.
+WorkflowExecutorDialog.WaitTimeout.Label=Stop after (milliseconds)
+WorkflowExecutorDialog.WaitTimeout.Tooltip=Maximum time to wait for the 
workflow to complete, in milliseconds. Empty or 0 means wait indefinitely. When 
the time is reached the workflow is stopped and the execution result is 
reported as failed. You can use a variable such as '${MAX_WAIT_MS}'.
 WorkflowExecutorDialog.Shell.Title=Workflow executor
 WorkflowExecutorDialog.TransformName.Label=Transform name
 WorkflowExecutorDialog.Workflow.Label=Workflow
diff --git 
a/plugins/transforms/workflowexecutor/src/test/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorMetaTest.java
 
b/plugins/transforms/workflowexecutor/src/test/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorMetaTest.java
index f15100fe1a..71e99f885e 100644
--- 
a/plugins/transforms/workflowexecutor/src/test/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorMetaTest.java
+++ 
b/plugins/transforms/workflowexecutor/src/test/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutorMetaTest.java
@@ -33,6 +33,7 @@ class WorkflowExecutorMetaTest {
             "/workflow-executor-transform.xml", WorkflowExecutorMeta.class);
 
     assertEquals("${PROJECT_HOME}/loops/child-workflow-executor.hwf", 
meta.getFilename());
+    assertEquals("1500", meta.getWaitTimeout());
     assertFalse(meta.isFilenameInField());
     assertEquals("", meta.getFilenameField() == null ? "" : 
meta.getFilenameField());
     assertEquals("ExecutionTime", meta.getExecutionTimeField());
@@ -86,6 +87,7 @@ class WorkflowExecutorMetaTest {
     assertEquals(clone.getResultFilesFileNameField(), 
meta.getResultFilesFileNameField());
     assertEquals(clone.getResultRowsField().size(), 
meta.getResultRowsField().size());
     assertEquals(clone.getParameters().size(), meta.getParameters().size());
+    assertEquals(clone.getWaitTimeout(), meta.getWaitTimeout());
   }
 
   @Test
diff --git 
a/plugins/transforms/workflowexecutor/src/test/resources/workflow-executor-transform.xml
 
b/plugins/transforms/workflowexecutor/src/test/resources/workflow-executor-transform.xml
index d2058dec6b..9f370e1ed5 100644
--- 
a/plugins/transforms/workflowexecutor/src/test/resources/workflow-executor-transform.xml
+++ 
b/plugins/transforms/workflowexecutor/src/test/resources/workflow-executor-transform.xml
@@ -28,6 +28,7 @@
         <schema_name/>
     </partitioning>
     <run_configuration>local</run_configuration>
+    <wait_timeout>1500</wait_timeout>
     <filename>${PROJECT_HOME}/loops/child-workflow-executor.hwf</filename>
     <filenameInField>N</filenameInField>
     <filenameField/>

Reply via email to