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

pefernan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie.git


The following commit(s) were added to refs/heads/main by this push:
     new 4e3727fab40 [incubator-kie-7106] 
fix(`kogito-quarkus-workflow-extension-common`): scope the UnitOfWorkManager 
bean to the application, not to the JVM (#7107)
4e3727fab40 is described below

commit 4e3727fab4033c10338619c3110dc2e668fb4f24
Author: Thiago Lugli <[email protected]>
AuthorDate: Wed Sep 16 10:46:45 2026 -0300

    [incubator-kie-7106] fix(`kogito-quarkus-workflow-extension-common`): scope 
the UnitOfWorkManager bean to the application, not to the JVM (#7107)
    
    * [NO-ISSUE] chore: fix the jobs-service IT `shared/` ignore rule stale 
since the repo merge
    
    The four integration-tests-jobs-service-quarkus modules copy the shared 
BPMN/SWF
    definitions into their own src/main/resources/shared during 
generate-resources, so
    any working copy that has been built shows 24 generated files as untracked.
    
    An ignore rule for exactly this already existed, but it still pointed at
    apps-integration-tests/integration-tests-jobs-service/** -- the path this 
content
    had before the kogito-apps repository was merged into this one. The rule has
    therefore matched nothing since the merge.
    
    Point it at the current layout and record why the path changed, so the next 
person
    to see these files does not add a second, duplicate rule. No tracked file 
lives
    under that path, so nothing committed is shadowed.
    
    * [incubator-kie-7106] fix(quarkus): scope the UnitOfWorkManager bean to 
the application, not to the JVM
    
    In Quarkus dev mode, the first or second live reload of an application using
    kogito-addons-quarkus-data-index-jpa fails to start with
    `IllegalStateException: EntityManagerFactory is closed`, raised from
    ProcessDefinitionRegistration -> DataIndexEventPublisher -> IndexingService 
->
    QuarkusProcessDefinitionEntityStorage.get(). Once it starts, it never 
recovers: every
    subsequent reload fails the same way.
    
    Cause: the CDI producer returned 
StaticUnitOfWorkManger.staticUnitOfWorkManager(). That
    static lives in a class loaded by the Quarkus *base* runtime ClassLoader, 
which a live
    reload does not recreate, so the UnitOfWorkManager -- and the 
BaseEventManager inside it --
    outlives the application it belongs to. On every boot AbstractProcessConfig 
runs
    
        eventPublishers.forEach(p -> 
unitOfWorkManager().eventManager().addPublisher(p));
        unitOfWorkListeners.forEach(l -> unitOfWorkManager().register(l));
    
    and both addPublisher and register are add-only. So each reload appends 
another
    generation's publishers and listeners to the same manager, and publish() 
then fans out to
    publishers belonging to applications that were shut down long ago. The 
data-index JPA
    publisher is the one that synchronously opens a Hibernate session, so it is 
the one that
    turns a stale reference into a visible exception; the leak itself is not 
specific to it.
    
    Measured across five consecutive dev-mode generations: Application is 
correctly recreated
    every time (@2b15af5c, @41d76578, @266a08bf, @6637bbb9, @72a2ae1f) while 
the manager and
    its event manager never are (DefaultUnitOfWorkManager@135ba, 
BaseEventManager@78f53ca9
    throughout), and the publisher set grows 1, 2, 3, 4, 5.
    
    This is not a revert of dcaca447 ([Fix #3869] Using the same work and event 
manager).
    Before that commit the producer was a bare @Produces, i.e. @Dependent, so 
every injection
    point received its own manager and only ProcessConfig's carried the 
publishers -- that is
    what #3869 was. The defect there was the missing scope; dcaca447 obtained 
sharing through a
    JVM static, and it is that static's lifetime that is fixed here. @Singleton 
provides the
    same sharing through the container, so dcaca447's invariant is preserved. 
@Singleton rather
    than @ApplicationScoped so injection points hold the instance directly, 
leaving the
    unit-of-work path free of a client proxy. 
LightProcessRuntimeServiceProvider is
    deliberately left as dcaca447 set it.
    
    Adds the regression test #3869 never shipped: `timerResume` parks on an 
intermediate timer
    so the instance is persisted and resumed by the job scheduler rather than 
the request
    thread, and the test asserts the data index sees the completion. Verified 
failing before
    this change (holds ACTIVE until the awaitility ceiling) and passing after, 
both with
    dcaca447 reverse-applied and with the producer unscoped.
---
 .gitignore                                         |  2 +-
 .../integration-tests-process/pom.xml              |  5 ++
 .../src/main/resources/timerresume.bpmn            | 45 ++++++++++++
 .../it/JPAQuarkusAddonDataIndexTimerResumeIT.java  | 80 ++++++++++++++++++++++
 .../quarkus/workflow/KogitoBeanProducer.java       |  8 ++-
 5 files changed, 137 insertions(+), 3 deletions(-)

diff --git a/.gitignore b/.gitignore
index b892e8dd1e9..934cc1aa584 100644
--- a/.gitignore
+++ b/.gitignore
@@ -180,4 +180,4 @@ package-lock.json
 !.rat-excludes
 
 # Jobs Service ITs
-apps-integration-tests/integration-tests-jobs-service/**/src/main/resources/shared/
\ No newline at end of file
+kogito-apps-quarkus/integration-tests-jobs-service-quarkus/**/src/main/resources/shared/
\ No newline at end of file
diff --git 
a/kogito-apps-quarkus/data-index-quarkus/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-jpa/integration-tests-process/pom.xml
 
b/kogito-apps-quarkus/data-index-quarkus/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-jpa/integration-tests-process/pom.xml
index da51da23cbf..ca4047d98ed 100644
--- 
a/kogito-apps-quarkus/data-index-quarkus/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-jpa/integration-tests-process/pom.xml
+++ 
b/kogito-apps-quarkus/data-index-quarkus/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-jpa/integration-tests-process/pom.xml
@@ -79,6 +79,11 @@
       <artifactId>rest-assured</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.awaitility</groupId>
+      <artifactId>awaitility</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.kie</groupId>
       
<artifactId>kogito-addons-quarkus-data-index-postgresql-deployment</artifactId>
diff --git 
a/kogito-apps-quarkus/data-index-quarkus/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-jpa/integration-tests-process/src/main/resources/timerresume.bpmn
 
b/kogito-apps-quarkus/data-index-quarkus/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-jpa/integration-tests-process/src/main/resources/timerresume.bpmn
new file mode 100644
index 00000000000..6729671dc33
--- /dev/null
+++ 
b/kogito-apps-quarkus/data-index-quarkus/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-jpa/integration-tests-process/src/main/resources/timerresume.bpmn
@@ -0,0 +1,45 @@
+<?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.
+-->
+<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL";
+                   xmlns:drools="http://www.jboss.org/drools";
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   id="_timerResumeDefinitions"
+                   targetNamespace="http://www.omg.org/bpmn20";>
+  <!-- Parks on a timer so the instance is persisted and resumed by the 
scheduler. Guards #3869. -->
+  <bpmn2:process id="timerResume" 
drools:packageName="org.kie.kogito.addons.quarkus.data.index.it"
+                 drools:version="1.0" drools:adHoc="false" name="timerResume"
+                 isExecutable="true" processType="Public">
+    <bpmn2:startEvent id="_timerResumeStart">
+      <bpmn2:outgoing>_timerResumeFlow1</bpmn2:outgoing>
+    </bpmn2:startEvent>
+    <bpmn2:intermediateCatchEvent id="_timerResumeWait" name="Wait">
+      <bpmn2:incoming>_timerResumeFlow1</bpmn2:incoming>
+      <bpmn2:outgoing>_timerResumeFlow2</bpmn2:outgoing>
+      <bpmn2:timerEventDefinition id="_timerResumeTimerDef">
+        <bpmn2:timeDuration 
xsi:type="bpmn2:tFormalExpression">PT1S</bpmn2:timeDuration>
+      </bpmn2:timerEventDefinition>
+    </bpmn2:intermediateCatchEvent>
+    <bpmn2:endEvent id="_timerResumeEnd">
+      <bpmn2:incoming>_timerResumeFlow2</bpmn2:incoming>
+    </bpmn2:endEvent>
+    <bpmn2:sequenceFlow id="_timerResumeFlow1" sourceRef="_timerResumeStart" 
targetRef="_timerResumeWait"/>
+    <bpmn2:sequenceFlow id="_timerResumeFlow2" sourceRef="_timerResumeWait" 
targetRef="_timerResumeEnd"/>
+  </bpmn2:process>
+</bpmn2:definitions>
diff --git 
a/kogito-apps-quarkus/data-index-quarkus/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-jpa/integration-tests-process/src/test/java/org/kie/kogito/addons/quarkus/data/index/it/JPAQuarkusAddonDataIndexTimerResumeIT.java
 
b/kogito-apps-quarkus/data-index-quarkus/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-jpa/integration-tests-process/src/test/java/org/kie/kogito/addons/quarkus/data/index/it/JPAQuarkusAddonDataIndexTimerResumeIT.java
new file mode 100644
index 00000000000..57c1c49d6e0
--- /dev/null
+++ 
b/kogito-apps-quarkus/data-index-quarkus/kogito-addons-quarkus-data-index/kogito-addons-quarkus-data-index-jpa/integration-tests-process/src/test/java/org/kie/kogito/addons/quarkus/data/index/it/JPAQuarkusAddonDataIndexTimerResumeIT.java
@@ -0,0 +1,80 @@
+/*
+ * 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.addons.quarkus.data.index.it;
+
+import java.time.Duration;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.restassured.RestAssured;
+import io.restassured.http.ContentType;
+import io.restassured.response.ValidatableResponse;
+
+import static io.restassured.RestAssured.given;
+import static org.awaitility.Awaitility.await;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+
+/**
+ * Regression test for <a 
href="https://github.com/apache/incubator-kie-kogito-runtimes/issues/3869";>#3869</a>:
+ * events emitted after a persisted instance is resumed must reach the data 
index.
+ */
+@QuarkusIntegrationTest
+class JPAQuarkusAddonDataIndexTimerResumeIT {
+
+    private static final String PROCESS_ID = "timerResume";
+
+    static {
+        RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
+    }
+
+    @Test
+    void testEventsAfterTimerResumeReachDataIndex() {
+        String processInstanceId = given()
+                .contentType(ContentType.JSON)
+                .accept(ContentType.JSON)
+                .post("/" + PROCESS_ID)
+                .then()
+                .statusCode(201)
+                .body("id", is(notNullValue()))
+                .extract().path("id");
+
+        // Parked on the timer: indexed, but not finished yet.
+        queryState(processInstanceId).body("data.ProcessInstances.size()", 
is(1))
+                .body("data.ProcessInstances[0].state", is("ACTIVE"));
+
+        // The timer fires, the instance is resumed off the request thread and 
completes. If the
+        // resuming path publishes into a UnitOfWorkManager that carries no 
publishers, the data
+        // index never learns about it and this never becomes COMPLETED.
+        await().atMost(Duration.ofSeconds(30))
+                .pollInterval(Duration.ofSeconds(1))
+                .untilAsserted(() -> queryState(processInstanceId)
+                        .body("data.ProcessInstances.size()", is(1))
+                        .body("data.ProcessInstances[0].state", 
is("COMPLETED")));
+    }
+
+    private static ValidatableResponse queryState(String processInstanceId) {
+        return given().contentType(ContentType.JSON)
+                .body("{ \"query\" : \"{ProcessInstances(where: { id: {equal: 
\\\"" + processInstanceId
+                        + "\\\"}}){ id, state, processId } }\" }")
+                .when().post("/graphql")
+                .then().statusCode(200);
+    }
+}
diff --git 
a/kogito-quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common/src/main/java/org/kie/kogito/quarkus/workflow/KogitoBeanProducer.java
 
b/kogito-quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common/src/main/java/org/kie/kogito/quarkus/workflow/KogitoBeanProducer.java
index 98f978b24bf..4807d5bf777 100644
--- 
a/kogito-quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common/src/main/java/org/kie/kogito/quarkus/workflow/KogitoBeanProducer.java
+++ 
b/kogito-quarkus/extensions/kogito-quarkus-workflow-extension-common/kogito-quarkus-workflow-common/src/main/java/org/kie/kogito/quarkus/workflow/KogitoBeanProducer.java
@@ -31,7 +31,8 @@ import 
org.kie.kogito.process.version.ProjectVersionProcessVersionResolver;
 import org.kie.kogito.services.jobs.impl.InMemoryJobContext;
 import org.kie.kogito.services.jobs.impl.InMemoryJobService;
 import org.kie.kogito.services.jobs.impl.InMemoryProcessJobExecutorFactory;
-import org.kie.kogito.services.uow.StaticUnitOfWorkManger;
+import org.kie.kogito.services.uow.CollectingUnitOfWorkFactory;
+import org.kie.kogito.services.uow.DefaultUnitOfWorkManager;
 import org.kie.kogito.uow.UnitOfWorkManager;
 import org.kie.kogito.usertask.UserTasks;
 
@@ -41,6 +42,7 @@ import io.quarkus.arc.properties.IfBuildProperty;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.enterprise.inject.Instance;
 import jakarta.enterprise.inject.Produces;
+import jakarta.inject.Singleton;
 
 @ApplicationScoped
 public class KogitoBeanProducer {
@@ -57,10 +59,12 @@ public class KogitoBeanProducer {
         return new DefaultCorrelationService();
     }
 
+    // Must stay a per-application singleton: a JVM-wide static outlives a 
dev-mode reload. See incubator-kie#7106.
     @DefaultBean
     @Produces
+    @Singleton
     UnitOfWorkManager unitOfWorkManager() {
-        return StaticUnitOfWorkManger.staticUnitOfWorkManager();
+        return new DefaultUnitOfWorkManager(new CollectingUnitOfWorkFactory());
     }
 
     @DefaultBean


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to