Skip to content

CAMEL-23352: Add syncOptimisticRetry option to Aggregate EIP#22769

Open
code-massel wants to merge 1 commit intoapache:mainfrom
code-massel:CAMEL-23352
Open

CAMEL-23352: Add syncOptimisticRetry option to Aggregate EIP#22769
code-massel wants to merge 1 commit intoapache:mainfrom
code-massel:CAMEL-23352

Conversation

@code-massel
Copy link
Copy Markdown

@code-massel code-massel commented Apr 24, 2026

When optimisticLocking is enabled, retries are normally scheduled asynchronously on a background ScheduledExecutorService, which switches threads and breaks transactional use cases where the aggregation repository and inbound message store must commit/rollback within a single transaction boundary.

The new syncOptimisticRetry flag (default false) causes the retry delay to execute via Thread.sleep on the calling thread instead, preserving the transaction context. This restores the Camel 2.x behavior for users who need it, without changing the default async behavior.

Description

When optimisticLocking is enabled on the Aggregate EIP, retry failures are scheduled asynchronously on a background ScheduledExecutorService. This thread switch breaks transactional use cases where the aggregation repository and inbound message store must commit/rollback within a single transaction boundary (transaction context is bound to the originating thread).

This PR adds a new syncOptimisticRetry option (default false) to the aggregate EIP. When enabled, the retry delay runs via Thread.sleep on the calling thread instead of scheduling on a background executor, preserving the transaction context. Default is false so existing routes are unaffected.

Target

  • I checked that the commit is targeting the correct branch (Camel 4 uses the main branch)

Tracking

Apache Camel coding standards and style

  • I checked that each commit in the pull request has a meaningful subject line and body.

  • I have run mvn clean install -DskipTests locally from root folder and I have committed all auto-generated changes.

@github-actions github-actions Bot added the core label Apr 25, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@davsclaus
Copy link
Copy Markdown
Contributor

when you add new options to EIPs you generally need to rebuild the entire code base

	modified:   catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/aggregate.json
	modified:   catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
	modified:   catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-xml-io.xsd
	modified:   core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/aggregate.json
	modified:   core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
	modified:   core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
	modified:   core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java
	modified:   dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
	modified:   dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json

@davsclaus
Copy link
Copy Markdown
Contributor

for example run from root

mvn clean install -P fastinstall

When optimisticLocking is enabled, retries are normally scheduled
asynchronously on a background ScheduledExecutorService, which switches
threads and breaks transactional use cases where the aggregation
repository and inbound message store must commit/rollback within a single
transaction boundary.

The new syncOptimisticRetry flag (default false) causes the retry delay
to execute via Thread.sleep on the calling thread instead, preserving
the transaction context. This restores the Camel 2.x behavior for users
who need it, without changing the default async behavior.
Copy link
Copy Markdown
Contributor

@gnodet gnodet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: CAMEL-23352 — Add syncOptimisticRetry option to Aggregate EIP

Thank you for this contribution! The use case is well-motivated — async optimistic locking retries breaking transaction context is a real problem, and adding an opt-in synchronous retry is a clean approach. The core implementation logic is correct.

A few items need attention before this can merge.

Blocker: Missing generated files

The PR modifies 4 hand-written files but does not include any regenerated downstream artifacts. CI will fail at the "Fail if there are uncommitted changes" step. Please run:

mvn install -B -pl core/camel-core-model -DskipTests

Then check git status and commit all generated file changes. This will update at minimum:

  • core/camel-core-model/src/generated/ — JSON model metadata, configurers
  • dsl/camel-yaml-dsl/ — YAML DSL schema and deserializers

The auto-generated options table in the aggregate EIP docs (aggregate-eip.adoc) will also pick up the new option from the Javadoc after regeneration, so a separate prose change is likely not needed.

Implementation

The core retry logic is sound:

  • The syncOptimisticRetry branch correctly calls doDelay(attempt) then continues the retry loop on the calling thread.
  • InterruptedException handling correctly restores the interrupt flag and propagates via exchange.setException().
  • Default (false) preserves existing async behavior — no regression risk.
  • The reifier and copy constructor correctly wire the new option.

See inline comments for minor suggestions.

Claude Code on behalf of Guillaume Nodet

private String optimisticLocking;
@XmlAttribute
@Metadata(label = "advanced", javaType = "java.lang.Boolean", defaultValue = "false")
private String syncOptimisticRetry;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit / naming: consider optimisticLockingSyncRetry (or optimisticLockRetrySync) to group it visually with the existing optimisticLocking field in docs and configuration. syncOptimisticRetry reads a bit ambiguously ("synchronize" vs "synchronous"). Not a blocker — just a readability suggestion.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the suggested rename of the option

Comment on lines +1019 to +1023
/**
* When optimistic locking is enabled, retries happen synchronously in the same thread instead of being scheduled on
* a background thread. This preserves transaction context for repositories that require single-thread transactional
* guarantees.
*/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice Javadoc. Consider adding a note that this option only takes effect when optimisticLocking is also enabled:

Suggested change
/**
* When optimistic locking is enabled, retries happen synchronously in the same thread instead of being scheduled on
* a background thread. This preserves transaction context for repositories that require single-thread transactional
* guarantees.
*/
/**
* When optimistic locking is enabled, retries happen synchronously in the same thread instead of being scheduled on
* a background thread. This preserves transaction context for repositories that require single-thread transactional
* guarantees. Only takes effect when {@link #optimisticLocking()} is also enabled.
*/

.optimisticLocking()
.syncOptimisticRetry()
.optimisticLockRetryPolicy(
new OptimisticLockRetryPolicy().maximumRetries(10).retryDelay(0))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good test coverage overall. One observation: retryDelay(0) means doDelay() returns immediately without sleeping, so the Thread.sleep + InterruptedException code path isn't exercised.

Consider adding a small test with a nonzero delay (e.g., retryDelay(10)) to verify the synchronous sleep actually happens on the calling thread under realistic conditions. A test that interrupts the thread during retry would further strengthen coverage of the InterruptedException branch. Not a blocker, but would increase confidence.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants