Skip to content

CAMEL-24622: camel-infinispan - keep completed exchanges for recovery - #26115

Open
oscerd wants to merge 1 commit into
apache:mainfrom
oscerd:fix/CAMEL-24622
Open

CAMEL-24622: camel-infinispan - keep completed exchanges for recovery#26115
oscerd wants to merge 1 commit into
apache:mainfrom
oscerd:fix/CAMEL-24622

Conversation

@oscerd

@oscerd oscerd commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Found by a source audit of components/camel-infinispan.

The problem

InfinispanAggregationRepository implements RecoverableAggregationRepository, but it keeps a single cache
keyed by the aggregation correlation key and has no recovery store, so all four recovery methods work on the
wrong key space:

method contract what it did
remove(ctx, key, exchange) move the holder into a recovery store keyed by exchange id plain cache.remove(key) — the completed exchange is gone
confirm(ctx, exchangeId) delete it from the recovery store cache.remove(exchangeId) against the correlation-keyed cache — never matches
scan(ctx) return exchange ids to recover returns the correlation keys of aggregations still in progress
recover(ctx, exchangeId) load a completed, unconfirmed exchange returns an exchange that is still aggregating

With useRecovery defaulting to true, the consequences are visible at runtime:

  • AggregateProcessor treats the scan() result as exchange ids and checks it against
    inProgressCompleteExchanges, so the duplicate-delivery guard never matches;
  • the still-open aggregation is marked CamelRedelivered and re-submitted on every recovery interval
    (default 5s);
  • after maximumRedeliveries (default 3) it goes to the dead letter channel;
  • and an exchange that genuinely failed after completion can never be recovered, which is the whole point of
    the interface.

Neither the remote nor the embedded subclass overrides these methods, so both are affected.

The change

A completed exchange is kept in the same cache under a camel-recovery:<exchange id> key until it is
confirmed. scan() reports those ids, recover() reads them, confirm() deletes one, and getKeys()
filters them out so it still reports only the aggregations in progress. scan() returns an empty set when
useRecovery=false.

The recovery entry is keyed by exchange.getExchangeId() of the exchange handed to remove(), which is what
JdbcAggregationRepository does (final String confirmKey = exchange.getExchangeId()), so the confirm path
lines up with the canonical implementation.

On the single-cache design. RedisAggregationRepository and JdbcAggregationRepository use a second
map/table for the recovery store, and that was the first thing I tried. For Hot Rod it means the user has to
provision a second cache server-side — a deployment change I do not think belongs in a bug fix — so this
keeps one cache and namespaces the recovery keys instead. If you would rather have the second cache with an
option to name it, say so and I will rework it.

Tests

The three existing tests encoded the broken key space — testConfirmExist confirmed with a correlation key
while the exchanges carried Exchange_N ids, testScan named its result exchangeIdSet and asserted
correlation keys, testRecover recovered by correlation key. They are rewritten around the real contract
(add → remove → scan sees the exchange id → recover returns it → confirm clears it) in both the embedded
test and the remote IT, plus a new test that getKeys() ignores recovery entries.

mvn clean install on components/camel-infinispan is green — 111 remote tests, including the rewritten
InfinispanRemoteAggregationRepositoryOperationsIT against a Hot Rod testcontainer, and 12 embedded
aggregation tests. Full reactor mvn clean install -DskipTests -Dquickly green. Upgrade-guide entry added
for 4.23.


Claude Code on behalf of oscerd

🤖 Generated with Claude Code

The aggregation repository implements RecoverableAggregationRepository but had
no recovery store, so the four recovery methods worked on the wrong key space:
remove deleted the completed exchange outright, confirm removed an exchange id
from a cache keyed by correlation key, scan returned the correlation keys of the
aggregations still in progress, and recover therefore returned an exchange that
was still aggregating.

With useRecovery enabled by default, the recovery task re-delivered aggregations
that were still accumulating, marked them CamelRedelivered and sent them to the
dead letter channel once maximumRedeliveries was reached, while an exchange that
genuinely failed after completion could never be recovered.

A completed exchange is now kept in the same cache under a
camel-recovery:<exchange id> key until it is confirmed. That is what scan
reports and recover reads, confirm deletes it, and getKeys filters those entries
out so it still reports only the aggregations in progress. The key follows the
exchange handed to remove, as JdbcAggregationRepository does. Recovery entries
are only written when useRecovery is enabled.

The three existing tests asserted the broken key space and are rewritten around
the real contract, in the embedded test and in the remote integration test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpshGTyfSmdq7hC8tuEwrZ
@oscerd oscerd added the bug Something isn't working label Sep 4, 2026
@oscerd
oscerd requested review from davsclaus and gnodet September 4, 2026 08:49
@gnodet gnodet added port/camel-4.22.x Bug needs porting to camel-4.22.x port/camel-4.18.x Bug needs porting to camel-4.18.x labels Sep 4, 2026
@gnodet

gnodet commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

🔄 Backport Bot

This bugfix targets main and may need porting to:

  • camel-4.22.x
  • camel-4.18.x

Labels port/<branch> have been added. Remove a label to opt out of porting to that branch.

Port PRs will be created automatically when this PR is merged. Comment /port to create them now.

ℹ️ If you push additional commits after /port, use /port again to update the port PRs.

@github-actions

github-actions Bot commented Sep 4, 2026

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.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • components/camel-infinispan/camel-infinispan-common
  • components/camel-infinispan/camel-infinispan-embedded
  • components/camel-infinispan/camel-infinispan
  • docs

🔬 Scalpel shadow comparison — Scalpel: 14 tested, 26 compile-only — current: 13 all tested

Maveniverse Scalpel detected 40 affected modules (current approach: 13).

⚠️ Modules only in Scalpel (27)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • docs
  • dummy-component

Skip-tests mode would test 14 modules (4 direct + 10 downstream), skip tests for 26 (generated code, meta-modules)

Modules Scalpel would test (14)
  • camel-infinispan
  • camel-infinispan-common
  • camel-infinispan-embedded
  • camel-jbang-mcp
  • camel-jbang-plugin-mcp
  • camel-jbang-plugin-route-parser
  • camel-jbang-plugin-tui
  • camel-jbang-plugin-validate
  • camel-langchain4j-embeddings
  • camel-langchain4j-embeddingstore
  • camel-launcher-container
  • camel-yaml-dsl-validator
  • camel-yaml-dsl-validator-maven-plugin
  • docs
Modules with tests skipped (26)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • dummy-component

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

⚠️ Some tests are disabled on GitHub Actions (@DisabledIfSystemProperty(named = "ci.env.name")) and require manual verification:

  • components/camel-infinispan/camel-infinispan: 2 test(s) disabled on GitHub Actions
All tested modules (40 modules)
  • Camel :: AI :: LangChain4j :: Embedding
  • Camel :: AI :: LangChain4j :: EmbeddingStore
  • Camel :: All Components Sync point
  • Camel :: Assembly
  • Camel :: Catalog :: Camel Catalog
  • Camel :: Catalog :: Camel Report Maven Plugin
  • Camel :: Catalog :: Camel Route Parser
  • Camel :: Catalog :: Console
  • Camel :: Catalog :: Dummy Component
  • Camel :: Catalog :: Maven
  • Camel :: Catalog :: Suggest
  • Camel :: Component DSL
  • Camel :: Coverage
  • Camel :: Docs
  • Camel :: Endpoint DSL
  • Camel :: Endpoint DSL :: Support
  • Camel :: Infinispan :: Common
  • Camel :: Infinispan :: Embedded
  • Camel :: Infinispan :: Remote
  • Camel :: Integration Tests
  • Camel :: JBang :: Core
  • Camel :: JBang :: Integration tests
  • Camel :: JBang :: MCP
  • Camel :: JBang :: Main
  • Camel :: JBang :: Plugin :: Edit
  • Camel :: JBang :: Plugin :: Generate
  • Camel :: JBang :: Plugin :: Kubernetes
  • Camel :: JBang :: Plugin :: MCP
  • Camel :: JBang :: Plugin :: Route Parser
  • Camel :: JBang :: Plugin :: TUI
  • Camel :: JBang :: Plugin :: Testing
  • Camel :: JBang :: Plugin :: Validate
  • Camel :: Kamelet Main
  • Camel :: Launcher
  • Camel :: Launcher :: Container
  • Camel :: YAML DSL
  • Camel :: YAML DSL :: Deserializers
  • Camel :: YAML DSL :: Maven Plugins
  • Camel :: YAML DSL :: Validator
  • Camel :: YAML DSL :: Validator Maven Plugin

⚙️ View full build and test results

@gnodet gnodet left a comment

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.

Solid fix — the recovery key space is correctly implemented, tests are thorough, and the upgrade guide is excellent.

The remove → put compound action across cache operations has a theoretical crash window where the exchange could be lost, but this is inherent to the single-cache design (the two-table JDBC approach has the same window between DELETE and INSERT), and the PR description explicitly acknowledges the trade-off and offers to rework with a second cache if preferred.

ast-grep flagged scan() for mutable-collection-return — false positive, the code already wraps with Collections::unmodifiableSet via Collectors.collectingAndThen.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

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

Labels

bug Something isn't working components docs port/camel-4.18.x Bug needs porting to camel-4.18.x port/camel-4.22.x Bug needs porting to camel-4.22.x

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants