CAMEL-24006: Fix flaky JpaPollingConsumerLockEntityTest#24612
Conversation
The testPollingConsumerWithoutLock test was flaky because it relied on two concurrent JPA polling consumers naturally racing to update the same versioned entity, which is non-deterministic. Under CI load, one request could complete before the other started its read, avoiding the optimistic lock conflict entirely. Fix: use a CyclicBarrier in the enrichment strategy for the not-locked route so both threads must complete the entity read before either can proceed to the write. This guarantees both hold stale version references, making the OptimisticLockException deterministic. Also added an explicit 20-second timeout to the not-locked test's assertIsSatisfied call to prevent indefinite hangs under CI contention. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
🌟 Thank you for your contribution to the Apache Camel project! 🌟 🐫 Apache Camel Committers, please review the following items:
|
|
🧪 CI tested the following changed modules:
🔬 Scalpel shadow comparison — Scalpel: 1 tested, 0 compile-only — current: 9 all testedMaveniverse Scalpel detected 1 affected modules (current approach: 9). Modules only in current approach (8)
Skip-tests mode would test 1 modules (1 direct + 0 downstream), skip tests for 0 (generated code, meta-modules) Modules Scalpel would test (1)
All tested modules (9 modules)
|
gnodet
left a comment
There was a problem hiding this comment.
Self-review — solid fix that makes a probabilistic race condition deterministic.
Root cause: Correct — without synchronization, one asyncRequestBodyAndHeaders could complete its entire read-mutate-write cycle before the other starts its read, meaning the second thread reads the already-committed version and no OptimisticLockException occurs.
Fix: The CyclicBarrier(2) approach is textbook for forcing concurrent interleaving in tests:
- Both threads enter
pollEnrichand read the entity (same stale version) - Both threads hit the barrier inside
notLockedEnrichStrategy.aggregate()— neither proceeds until both have read - Both proceed to
to("jpa://...")— the second commit triggersOptimisticLockExceptiondeterministically
Good design decisions:
- Separate enrichment strategy for the not-locked route rather than modifying the shared one — the locked route doesn't need the barrier since pessimistic locking handles the conflict at the DB level
- 10s barrier timeout — long enough for CI, prevents test from hanging if one thread fails before reaching the barrier
- 20s
assertIsSatisfiedtimeout — accommodates slow CI - Assertion change to
expectedMessageCount(1)+message(0).body().isEqualTo(...)is functionally equivalent and fine
Claude Code self-review on behalf of @gnodet
Summary
Claude Code on behalf of gnodet
Fixes the flaky
JpaPollingConsumerLockEntityTestwhich was failing on CI ~5.9% of the time (testPollingConsumerWithoutLock: 9 failures + 7 flaky out of 270 runs over the last 10 days).Root cause: The test relied on two concurrent JPA polling consumers naturally racing to update the same versioned entity without pessimistic locking. Under CI load, one request could complete before the other started its read, avoiding the optimistic lock conflict entirely. When no
OptimisticLockExceptionoccurred, the assertion onmock:errorfailed.Fix: Use a
CyclicBarrierin the enrichment strategy for the not-locked route. Both threads must complete the entity read (and in-memory mutation) before either can proceed to the JPA write. This guarantees both hold stale version references, making theOptimisticLockExceptiondeterministic rather than probabilistic.CyclicBarrier(2)field to the test classMockEndpoint.assertIsSatisfied()callTest plan
mvn test -pl components/camel-jpa -Dtest=JpaPollingConsumerLockEntityTestpasses consistently (5/5 runs locally)🤖 Generated with Claude Code