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

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


The following commit(s) were added to refs/heads/main by this push:
     new 01a855ab3dcb CAMEL-24006: Fix flaky JpaPollingConsumerLockEntityTest 
(#24612)
01a855ab3dcb is described below

commit 01a855ab3dcbb85412cd2c740c01d80a4937cf19
Author: Guillaume Nodet <[email protected]>
AuthorDate: Sun Jul 12 08:34:10 2026 +0200

    CAMEL-24006: Fix flaky JpaPollingConsumerLockEntityTest (#24612)
    
    CAMEL-24006: Fix flaky JpaPollingConsumerLockEntityTest
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../jpa/JpaPollingConsumerLockEntityTest.java      | 39 ++++++++++++++++++++--
 1 file changed, 36 insertions(+), 3 deletions(-)

diff --git 
a/components/camel-jpa/src/test/java/org/apache/camel/processor/jpa/JpaPollingConsumerLockEntityTest.java
 
b/components/camel-jpa/src/test/java/org/apache/camel/processor/jpa/JpaPollingConsumerLockEntityTest.java
index 5e6126cb88dd..e99823bf3b43 100644
--- 
a/components/camel-jpa/src/test/java/org/apache/camel/processor/jpa/JpaPollingConsumerLockEntityTest.java
+++ 
b/components/camel-jpa/src/test/java/org/apache/camel/processor/jpa/JpaPollingConsumerLockEntityTest.java
@@ -18,6 +18,7 @@ package org.apache.camel.processor.jpa;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.CyclicBarrier;
 import java.util.concurrent.TimeUnit;
 
 import jakarta.persistence.OptimisticLockException;
@@ -33,6 +34,11 @@ import org.junit.jupiter.api.Test;
 public class JpaPollingConsumerLockEntityTest extends AbstractJpaTest {
     protected static final String SELECT_ALL_STRING = "select x from " + 
Customer.class.getName() + " x";
 
+    // Barrier that forces both "not-locked" threads to complete the read 
(pollEnrich)
+    // before either proceeds to the write (to("jpa://...")), ensuring they 
both hold
+    // stale version references and the second commit triggers an 
OptimisticLockException.
+    private final CyclicBarrier notLockedBarrier = new CyclicBarrier(2);
+
     @BeforeEach
     public void setupBeans() {
         Customer customer = new Customer();
@@ -71,7 +77,12 @@ public class JpaPollingConsumerLockEntityTest extends 
AbstractJpaTest {
         MockEndpoint mock = getMockEndpoint("mock:not-locked");
         MockEndpoint errMock = getMockEndpoint("mock:error");
 
-        mock.expectedBodiesReceived("orders: 1");
+        // Without pessimistic locking, two concurrent updates to the same 
versioned entity
+        // should cause an OptimisticLockException for the loser. The 
CyclicBarrier in the
+        // enrichment strategy (see createRouteBuilder) forces both threads to 
read the entity
+        // before either can proceed to the write, making the conflict 
deterministic.
+        mock.expectedMessageCount(1);
+        mock.message(0).body().isEqualTo("orders: 1");
 
         errMock.expectedMessageCount(1);
         errMock.message(0).body().isInstanceOf(OptimisticLockException.class);
@@ -82,7 +93,7 @@ public class JpaPollingConsumerLockEntityTest extends 
AbstractJpaTest {
         template.asyncRequestBodyAndHeaders("direct:not-locked", "message", 
headers);
         template.asyncRequestBodyAndHeaders("direct:not-locked", "message", 
headers);
 
-        MockEndpoint.assertIsSatisfied(context);
+        MockEndpoint.assertIsSatisfied(context, 20, TimeUnit.SECONDS);
     }
 
     @Override
@@ -100,6 +111,28 @@ public class JpaPollingConsumerLockEntityTest extends 
AbstractJpaTest {
                     }
                 };
 
+                // Enrichment strategy for the not-locked route that 
synchronizes both
+                // threads after the read so they both hold stale entity 
versions.
+                // The barrier wait happens AFTER the entity has been read and 
mutated
+                // but BEFORE it is written back to the DB via the subsequent 
to("jpa://...").
+                AggregationStrategy notLockedEnrichStrategy = new 
AggregationStrategy() {
+                    @Override
+                    public Exchange aggregate(Exchange originalExchange, 
Exchange jpaExchange) {
+                        Customer customer = 
jpaExchange.getIn().getBody(Customer.class);
+                        customer.setOrderCount(customer.getOrderCount() + 1);
+
+                        try {
+                            // Wait for the other thread to also complete its 
read, ensuring
+                            // both threads proceed to the write with the same 
stale version.
+                            notLockedBarrier.await(10, TimeUnit.SECONDS);
+                        } catch (Exception e) {
+                            throw new RuntimeException("Barrier wait 
interrupted", e);
+                        }
+
+                        return jpaExchange;
+                    }
+                };
+
                 onException(Exception.class)
                         .setBody().simple("${exception}")
                         .to("mock:error")
@@ -124,7 +157,7 @@ public class JpaPollingConsumerLockEntityTest extends 
AbstractJpaTest {
                         .pollEnrich()
                         .simple("jpa://" + Customer.class.getName()
                                 + "?query=select c from Customer c where 
c.name like '${header.name}'")
-                        .aggregationStrategy(enrichStrategy)
+                        .aggregationStrategy(notLockedEnrichStrategy)
                         .to("jpa://" + Customer.class.getName())
                         .setBody().simple("orders: ${body.orderCount}")
                         .to("mock:not-locked");

Reply via email to