This is an automated email from the ASF dual-hosted git repository.
yesamer pushed a commit to branch main
in repository
https://gitbox.apache.org/repos/asf/incubator-kie-kogito-runtimes.git
The following commit(s) were added to refs/heads/main by this push:
new 3ba8dca0c3 [incubator-kie-issues#2339] NPE in `JDBCProcessInstances.`
when retriggering `CallActivity` nodes with `waitForCompletion=true` (#4315)
3ba8dca0c3 is described below
commit 3ba8dca0c3c9678dc4cca404292b5d4d74874e7e
Author: Pere Fernández <[email protected]>
AuthorDate: Fri Jun 12 10:56:31 2026 +0200
[incubator-kie-issues#2339] NPE in `JDBCProcessInstances.` when
retriggering `CallActivity` nodes with `waitForCompletion=true` (#4315)
---
.../process/impl/AbstractProcessInstance.java | 12 ++-
.../ContextAwareProcessInstanceLockStrategy.java | 5 ++
.../lock/ProcessInstanceAtomicLockStrategy.java | 9 ++-
.../impl/lock/ProcessInstanceLockStrategy.java | 8 ++
.../ProcessInstanceAtomicLockStrategyTest.java | 87 ++++++++++++++++++++++
.../src/test/java/org/jbpm/bpmn2/ActivityTest.java | 47 ++++++++++++
6 files changed, 164 insertions(+), 4 deletions(-)
diff --git
a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java
b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java
index d00495594c..842fdb08ff 100644
---
a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java
+++
b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java
@@ -612,6 +612,9 @@ public abstract class AbstractProcessInstance<T extends
Model> implements Proces
}
private <R> R
executeInWorkflowProcessInstance(Function<WorkflowProcessInstanceImpl, R>
execution) {
+ // Check if this is a reentrant call before entering the lock
+ boolean isReentrant =
processInstanceLockStrategy.isLockedByCurrentThread(id);
+
return processInstanceLockStrategy.executeOperation(id, () -> {
WorkflowProcessInstanceImpl workflowProcessInstance =
internalLoadProcessInstanceState();
if (isProcessInstanceConnected()) {
@@ -621,7 +624,7 @@ public abstract class AbstractProcessInstance<T extends
Model> implements Proces
try {
outcome = execution.apply(workflowProcessInstance);
} catch (Throwable th) {
- // clean up after non expected error
+ // clean up after non-expected error
if (isProcessInstanceConnected()) {
getProcessRuntime().getProcessInstanceManager().removeProcessInstance(workflowProcessInstance);
}
@@ -636,7 +639,12 @@ public abstract class AbstractProcessInstance<T extends
Model> implements Proces
syncPersistence(workflowProcessInstance);
getProcessRuntime().getProcessInstanceManager().removeProcessInstance(workflowProcessInstance);
}
- internalUnloadProcessInstanceState();
+
+ // Only unload state if this is not a reentrant call, this
prevents nested calls from unloading the parent's state
+ if (!isReentrant) {
+ internalUnloadProcessInstanceState();
+ }
+
return outcome;
});
}
diff --git
a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/lock/ContextAwareProcessInstanceLockStrategy.java
b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/lock/ContextAwareProcessInstanceLockStrategy.java
index 9d656f1d5e..eabf6a15e1 100644
---
a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/lock/ContextAwareProcessInstanceLockStrategy.java
+++
b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/lock/ContextAwareProcessInstanceLockStrategy.java
@@ -66,4 +66,9 @@ public class ContextAwareProcessInstanceLockStrategy
implements ProcessInstanceL
}
});
}
+
+ @Override
+ public boolean isLockedByCurrentThread(String processInstanceId) {
+ return delegate.isLockedByCurrentThread(processInstanceId);
+ }
}
diff --git
a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/lock/ProcessInstanceAtomicLockStrategy.java
b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/lock/ProcessInstanceAtomicLockStrategy.java
index ad78e2d581..0cd3a27531 100644
---
a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/lock/ProcessInstanceAtomicLockStrategy.java
+++
b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/lock/ProcessInstanceAtomicLockStrategy.java
@@ -84,8 +84,7 @@ public class ProcessInstanceAtomicLockStrategy implements
ProcessInstanceLockStr
return newHolder;
});
- // at this points this is a safe ask as if we invoked prior to this
point the hold it will always return
- // properly
+ // At this point this is a safe ask as if we invoked prior to this
point the hold it will always return properly
boolean alreadyAcquired =
processInstanceLockHolder.isHeldByCurrentThread();
try {
if (!alreadyAcquired) {
@@ -116,6 +115,12 @@ public class ProcessInstanceAtomicLockStrategy implements
ProcessInstanceLockStr
}
+ @Override
+ public boolean isLockedByCurrentThread(String processInstanceId) {
+ ProcessInstanceLockHolder holder = locks.get(processInstanceId);
+ return holder != null && holder.isHeldByCurrentThread();
+ }
+
public static synchronized ProcessInstanceLockStrategy instance() {
if (INSTANCE == null) {
INSTANCE = new ProcessInstanceAtomicLockStrategy();
diff --git
a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/lock/ProcessInstanceLockStrategy.java
b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/lock/ProcessInstanceLockStrategy.java
index 02f8350866..5d0a27d5aa 100644
---
a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/lock/ProcessInstanceLockStrategy.java
+++
b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/lock/ProcessInstanceLockStrategy.java
@@ -22,4 +22,12 @@ public interface ProcessInstanceLockStrategy {
<T> T executeOperation(String processInstanceId, WorkflowAtomicExecutor<T>
empty);
+ /**
+ * Checks if the current thread already holds the lock for the given
process instance.
+ *
+ * @param processInstanceId the process instance id
+ * @return true if the current thread already holds the lock, false
otherwise
+ */
+ boolean isLockedByCurrentThread(String processInstanceId);
+
}
diff --git
a/jbpm/jbpm-flow/src/test/java/org/kie/kogito/process/impl/lock/ProcessInstanceAtomicLockStrategyTest.java
b/jbpm/jbpm-flow/src/test/java/org/kie/kogito/process/impl/lock/ProcessInstanceAtomicLockStrategyTest.java
new file mode 100644
index 0000000000..909968ad3a
--- /dev/null
+++
b/jbpm/jbpm-flow/src/test/java/org/kie/kogito/process/impl/lock/ProcessInstanceAtomicLockStrategyTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.kie.kogito.process.impl.lock;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ProcessInstanceAtomicLockStrategyTest {
+
+ private ProcessInstanceAtomicLockStrategy strategy;
+ private static final String PROCESS_INSTANCE_ID =
"testProcessInstanceId-1";
+
+ @BeforeEach
+ void setUp() {
+ strategy = (ProcessInstanceAtomicLockStrategy)
ProcessInstanceAtomicLockStrategy.instance();
+ }
+
+ @Test
+ void testProcessInstanceIsNotLockedByCurrentThread() {
+ boolean isLocked =
strategy.isLockedByCurrentThread(PROCESS_INSTANCE_ID);
+
+ assertThat(isLocked).isFalse();
+ }
+
+ @Test
+ void testProcessInstanceIsLockedByCurrentThread() {
+ strategy.executeOperation(PROCESS_INSTANCE_ID, () -> {
+ boolean isLocked =
strategy.isLockedByCurrentThread(PROCESS_INSTANCE_ID);
+ assertThat(isLocked).isTrue();
+ return null;
+ });
+ }
+
+ @Test
+ void testProcessInstanceIsLockedByCurrentThreadReentrantCalls() {
+
+ // Verifying that process instance is locked in nested operations
+ strategy.executeOperation(PROCESS_INSTANCE_ID, () -> {
+
+
assertThat(strategy.isLockedByCurrentThread(PROCESS_INSTANCE_ID)).isTrue();
+
+ return strategy.executeOperation(PROCESS_INSTANCE_ID, () -> {
+
+
assertThat(strategy.isLockedByCurrentThread(PROCESS_INSTANCE_ID)).isTrue();
+
+ return strategy.executeOperation(PROCESS_INSTANCE_ID, () -> {
+
assertThat(strategy.isLockedByCurrentThread(PROCESS_INSTANCE_ID)).isTrue();
+ return "success";
+ });
+ });
+ });
+
+ // After all operations complete, lock should be released
+
assertThat(strategy.isLockedByCurrentThread(PROCESS_INSTANCE_ID)).isFalse();
+ }
+
+ @Test
+ void testDifferentProcessInstancesIsLockedByCurrentThread() {
+ String processInstanceId2 = "testProcessInstanceId-2";
+
+ strategy.executeOperation(PROCESS_INSTANCE_ID, () -> {
+ // verify that only testProcessInstanceId-1 is locked
+
assertThat(strategy.isLockedByCurrentThread(PROCESS_INSTANCE_ID)).isTrue();
+
+
assertThat(strategy.isLockedByCurrentThread(processInstanceId2)).isFalse();
+ return null;
+ });
+ }
+}
diff --git a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/ActivityTest.java
b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/ActivityTest.java
index 44d6a424f4..69bb990bf0 100755
--- a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/ActivityTest.java
+++ b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/ActivityTest.java
@@ -1803,6 +1803,53 @@ public class ActivityTest extends JbpmBpmn2TestCase {
assertThat(instance).extracting(ProcessInstance::status).isEqualTo(org.kie.kogito.process.ProcessInstance.STATE_ABORTED);
}
+ @Test
+ public void testRetriggerCallActivityNodeInstance() {
+ final String flow1NodeUniqueId =
"_B45422B2-F0D4-40D2-A2BF-72BE1F23EC81";
+
+ Application app = ProcessTestHelper.newApplication();
+ ProcessTestHelper.registerHandler(app, "Human Task", new
DefaultKogitoWorkItemHandler());
+ org.kie.kogito.process.Process<FlowMainModel> main =
FlowMainProcess.newProcess(app);
+ org.kie.kogito.process.Process<FlowChild1Model> child1 =
FlowChild1Process.newProcess(app);
+ FlowChild2Process.newProcess(app);
+ FlowChild3Process.newProcess(app);
+
+ org.kie.kogito.process.ProcessInstance<FlowMainModel> instance =
main.createInstance(main.createModel());
+ instance.start();
+
+ assertThat(child1.instances().stream().count()).isEqualTo(1);
+
+ Collection<KogitoNodeInstance> childNodeInstances =
instance.findNodes(kogitoNodeInstance ->
kogitoNodeInstance.getNodeDefinitionId().equals(flow1NodeUniqueId));
+
+ assertThat(childNodeInstances).hasSize(1);
+
+ KogitoNodeInstance flow1NodeInstance =
childNodeInstances.iterator().next();
+
+ assertThat(flow1NodeInstance)
+ .isNotNull()
+ .hasFieldOrProperty("id")
+ .hasFieldOrPropertyWithValue("isRetrigger", false);
+
+ instance.retriggerNodeInstance(flow1NodeInstance.getId());
+
+ Collection<KogitoNodeInstance> childNodeInstancesAfterRetrigger =
instance.findNodes(kogitoNodeInstance ->
kogitoNodeInstance.getNodeDefinitionId().equals(flow1NodeUniqueId));
+
+ assertThat(childNodeInstancesAfterRetrigger).hasSize(1);
+
+ KogitoNodeInstance retriggeredFlow1NodeInstance =
childNodeInstancesAfterRetrigger
+ .iterator()
+ .next();
+
+ assertThat(retriggeredFlow1NodeInstance)
+ .isNotNull()
+ .hasFieldOrPropertyWithValue("isRetrigger", true)
+ .hasFieldOrProperty("id")
+ .extracting("id")
+ .isNotEqualTo(flow1NodeInstance.getId());
+
+ instance.abort();
+ }
+
@Test
public void testAbortChildProcessUponCompletion() {
Application app = ProcessTestHelper.newApplication();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]