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 95a8332620 revert commit when manually stopping workflow in
transactional setting, fixes #4681 (#7017)
95a8332620 is described below
commit 95a8332620d3ae20735158f42e54f033b6dcbd86
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Fri Apr 17 15:14:44 2026 +0200
revert commit when manually stopping workflow in transactional setting,
fixes #4681 (#7017)
---
.../java/org/apache/hop/workflow/Workflow.java | 7 ++
.../engines/local/LocalWorkflowEngine.java | 5 +-
.../java/org/apache/hop/workflow/WorkflowTest.java | 104 ++++++++++++++++++++-
3 files changed, 114 insertions(+), 2 deletions(-)
diff --git a/engine/src/main/java/org/apache/hop/workflow/Workflow.java
b/engine/src/main/java/org/apache/hop/workflow/Workflow.java
index 6db1dbff84..689d044e19 100644
--- a/engine/src/main/java/org/apache/hop/workflow/Workflow.java
+++ b/engine/src/main/java/org/apache/hop/workflow/Workflow.java
@@ -997,6 +997,13 @@ public abstract class Workflow extends Variables
res.setResult(false);
}
+ // Toolbar / API stop sets the engine's stopped flag; the last completed
action can still
+ // return a successful Result with stopped=false. Propagate stop into
Result so consumers
+ // (e.g. transactional workflow commit/rollback) behave correctly.
+ if (isStopped() && res != null) {
+ res.setStopped(true);
+ }
+
return res;
}
diff --git
a/engine/src/main/java/org/apache/hop/workflow/engines/local/LocalWorkflowEngine.java
b/engine/src/main/java/org/apache/hop/workflow/engines/local/LocalWorkflowEngine.java
index 96091eca78..61f32968a3 100644
---
a/engine/src/main/java/org/apache/hop/workflow/engines/local/LocalWorkflowEngine.java
+++
b/engine/src/main/java/org/apache/hop/workflow/engines/local/LocalWorkflowEngine.java
@@ -139,7 +139,10 @@ public class LocalWorkflowEngine extends Workflow
implements IWorkflowEngine<Wor
// All fine? Commit!
//
try {
- if (result.isResult() && !result.isStopped() &&
result.getNrErrors() == 0) {
+ if (result.isResult()
+ && !result.isStopped()
+ && !workflow.isStopped()
+ && result.getNrErrors() == 0) {
try {
database.commit(true);
workflow
diff --git a/engine/src/test/java/org/apache/hop/workflow/WorkflowTest.java
b/engine/src/test/java/org/apache/hop/workflow/WorkflowTest.java
index 2361800f6d..1fae0e3faa 100644
--- a/engine/src/test/java/org/apache/hop/workflow/WorkflowTest.java
+++ b/engine/src/test/java/org/apache/hop/workflow/WorkflowTest.java
@@ -18,14 +18,26 @@
package org.apache.hop.workflow;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.Result;
+import org.apache.hop.core.annotations.Action;
import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.exception.HopPluginException;
import org.apache.hop.core.exception.HopRuntimeException;
import org.apache.hop.core.logging.HopLogStore;
import org.apache.hop.core.logging.LogLevel;
+import org.apache.hop.core.plugins.ActionPluginType;
+import org.apache.hop.core.plugins.PluginRegistry;
+import org.apache.hop.workflow.action.ActionBase;
+import org.apache.hop.workflow.action.ActionMeta;
+import org.apache.hop.workflow.action.IAction;
+import org.apache.hop.workflow.actions.dummy.ActionDummy;
+import org.apache.hop.workflow.actions.start.ActionStart;
import org.apache.hop.workflow.engine.IWorkflowEngine;
import org.apache.hop.workflow.engines.local.LocalWorkflowEngine;
import org.junit.jupiter.api.BeforeAll;
@@ -90,9 +102,51 @@ class WorkflowTest {
}
}
+ /** Used by {@link ActionBlockingForWorkflowStopTest} to coordinate with the
test thread. */
+ private static volatile CountDownLatch blockingEntered;
+
+ private static volatile CountDownLatch blockingRelease;
+
+ @Action(id = "ActionBlockingForWorkflowStopTest", name = "Blocking action
for workflow stop test")
+ public static class ActionBlockingForWorkflowStopTest extends ActionBase
implements IAction {
+
+ public ActionBlockingForWorkflowStopTest() {
+ super("", "", "ActionBlockingForWorkflowStopTest");
+ }
+
+ @Override
+ public Result execute(Result prevResult, int nr) throws HopException {
+ CountDownLatch entered = blockingEntered;
+ CountDownLatch release = blockingRelease;
+ if (entered != null) {
+ entered.countDown();
+ }
+ if (release != null) {
+ try {
+ if (!release.await(60, TimeUnit.SECONDS)) {
+ throw new HopException("Timed out waiting in blocking test
action");
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new HopException(e);
+ }
+ }
+ Result r = prevResult == null ? new Result() : prevResult.clone();
+ r.setResult(true);
+ r.setNrErrors(0);
+ r.setStopped(false);
+ return r;
+ }
+ }
+
@BeforeAll
- static void beforeClass() throws HopException {
+ static void beforeClass() throws HopException, HopPluginException {
HopEnvironment.init();
+ PluginRegistry.getInstance()
+ .registerPluginClass(
+ ActionBlockingForWorkflowStopTest.class.getName(),
+ ActionPluginType.class,
+ Action.class);
}
@BeforeEach
@@ -137,4 +191,52 @@ class WorkflowTest {
thread1.join();
thread2.join();
}
+
+ /**
+ * When stop is requested while an action is running, that action can still
finish with a
+ * successful Result that has stopped=false. The workflow result must still
report stopped so
+ * transactional handling can roll back.
+ */
+ @Test
+ void workflowResultReflectsStopWhenStoppedDuringActionExecution() throws
Exception {
+ blockingEntered = new CountDownLatch(1);
+ blockingRelease = new CountDownLatch(1);
+ try {
+ WorkflowMeta meta = new WorkflowMeta();
+ meta.setName("workflow-stop-result-test");
+
+ ActionStart start = new ActionStart("START");
+ ActionMeta startMeta = new ActionMeta(start);
+ meta.addAction(startMeta);
+
+ ActionBlockingForWorkflowStopTest blocking = new
ActionBlockingForWorkflowStopTest();
+ blocking.setName("blocking");
+ ActionMeta blockingMeta = new ActionMeta(blocking);
+ meta.addAction(blockingMeta);
+
+ ActionDummy neverRun = new ActionDummy();
+ neverRun.setName("never-run");
+ ActionMeta neverRunMeta = new ActionMeta(neverRun);
+ meta.addAction(neverRunMeta);
+
+ meta.addWorkflowHop(new WorkflowHopMeta(startMeta, blockingMeta));
+ WorkflowHopMeta hopToNext = new WorkflowHopMeta(blockingMeta,
neverRunMeta);
+ hopToNext.setUnconditional();
+ meta.addWorkflowHop(hopToNext);
+
+ LocalWorkflowEngine engine = new LocalWorkflowEngine(meta);
+ engine.setLogLevel(LogLevel.MINIMAL);
+
+ Thread runner = new Thread(engine::startExecution);
+ runner.start();
+ assertTrue(blockingEntered.await(30, TimeUnit.SECONDS));
+ engine.stopExecution();
+ blockingRelease.countDown();
+ runner.join(60000);
+ assertTrue(engine.getResult().isStopped(), "Result should reflect user
stop for rollback");
+ } finally {
+ blockingEntered = null;
+ blockingRelease = null;
+ }
+ }
}