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