Skip to content

CAMEL-24046: camel-sql - JdbcCachedMessageIdRepository must not cache a key whose insert failed, make cache thread-safe#24657

Merged
Croway merged 1 commit into
apache:mainfrom
Croway:CAMEL-24046-jdbc-cached-idempotent-repository
Jul 13, 2026
Merged

CAMEL-24046: camel-sql - JdbcCachedMessageIdRepository must not cache a key whose insert failed, make cache thread-safe#24657
Croway merged 1 commit into
apache:mainfrom
Croway:CAMEL-24046-jdbc-cached-idempotent-repository

Conversation

@Croway

@Croway Croway commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

JIRA: https://issues.apache.org/jira/browse/CAMEL-24046

Two related defects in JdbcCachedMessageIdRepository:

  1. add(key) put the key into the in-memory cache before calling super.add(key). If the database insert failed (outage, PK race with another node), the exception propagated and the idempotent consumer never processed the message — but the key stayed cached, so every redelivery was rejected as a duplicate. The message was never processed and never stored until JVM restart.
  2. The cache was a plain HashMap mutated from concurrent consumer threads with no synchronization and a non-volatile field (a runtime reload() might never become visible to other threads); concurrent puts during resize can lose entries, causing duplicate processing. The hit/miss counters were unsynchronized ints.

Changes:

  • Populate the cache only after the database insert succeeded
  • Use ConcurrentHashMap with a volatile field for safe publication on reload(), and AtomicInteger hit/miss counters (public getters unchanged)
  • New test simulating a DB failure during add(): contains() must return false and a redelivered add() must succeed once the DB is back (fails on current main, passes with the fix); existing cache hit/miss-count test passes unchanged, confirming counting semantics are preserved

Claude Code on behalf of Croway

🤖 Generated with Claude Code

… a key whose insert failed, make cache thread-safe
@Croway Croway requested review from davsclaus and oscerd July 13, 2026 09:44
@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.

@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.

Both fixes are correct and well-tested. The cache-before-insert bug fix prevents permanent message loss on DB failure — moving cache.merge() to after super.add() ensures keys are only cached after a successful DB insert. The ConcurrentHashMap/AtomicInteger migration resolves thread-safety hazards in the shared cache.

Scanner coverage: No static analysis tools (PMD, Checkstyle, semgrep, SpotBugs) available. Rely on CI.

Notes:

  • The cache.merge(key, 1, Integer::sum) after super.add(key) executes unconditionally regardless of the return value. When super.add() returns false (duplicate), the cache count is inflated — functionally harmless for boolean idempotent semantics and matches pre-existing behavior.
  • volatile on the cache field correctly ensures safe publication when reload() replaces the map from a different thread.
  • The test directly validates the regression scenario (drop table → simulate failure → recreate → verify key is not cached).

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

Claude Code review on behalf of @gnodet

@oscerd oscerd 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.

Reviewed with focus on the cache/insert ordering and the concurrency changes — the fix is correct and I have no change requests. Summary of the verification:

Both halves of the fix address confirmed defects on main. The poisoned-cache bug is real: add() currently caches the key (cache.put, line 70) before super.add(key) (line 76), so an insert that throws leaves the key cached and every redelivery is rejected as a duplicate forever. The reordering (only cache.merge after super.add returns) fixes all paths, including the super.add() == false case (row already in DB), where caching still matches DB truth. The thread-safety half is equally justified: main uses a plain HashMap with non-volatile field and plain int counters, shared across concurrently invoked idempotent-consumer threads.

The regression test is genuine: assertFalse(repository.contains("second")) after a failed insert fails on current main (cache hit → true) and passes with the fix. Fully synchronous, no Thread.sleep.

History check: the cache-before-insert ordering dates to the original CAMEL-16770 contribution — no prior intentional decision is being reverted.

Two non-blocking observations, both pre-existing rather than introduced here:

  1. Inside an outer transaction (PROPAGATION_REQUIRED), super.add() returning doesn't mean the insert is committed — if the enclosing transaction rolls back later, the DB row vanishes while the key stays cached. In practice the idempotent consumer's default removeOnFailure=true evicts the key on failure, and the new code is strictly better than the old (which cached before even attempting the insert), so no action needed — at most the comment could say "after the insert call returned" rather than "succeeded".
  2. The hit-path getOrDefault/merge pair isn't atomic, so a concurrent remove() between them can resurrect a cache entry. Same window existed before the PR; cache values are only presence flags and the DB remains source of truth on a miss, so it's benign.

No file overlap with the sibling camel-sql PRs #24655/#24656 (disjoint packages), so merge order between the three doesn't matter. The two build jobs were still pending at review time — worth confirming green before merge.

Reviewed with Claude Code (Fable 5) on behalf of oscerd. This review was generated by an AI agent and may contain inaccuracies; please verify all suggestions before applying.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • components/camel-sql

🔬 Scalpel shadow comparison — Scalpel: 11 tested, 29 compile-only — current: 11 all tested

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

⚠️ Modules only in Scalpel (29)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-csimple-maven-plugin
  • 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 11 modules (1 direct + 10 downstream), skip tests for 29 (generated code, meta-modules)

Modules Scalpel would test (11)
  • camel-jbang-mcp
  • camel-jbang-plugin-mcp
  • camel-jbang-plugin-route-parser
  • camel-jbang-plugin-tui
  • camel-jbang-plugin-validate
  • camel-jta
  • camel-kafka
  • camel-launcher-container
  • camel-sql
  • camel-yaml-dsl-validator
  • camel-yaml-dsl-validator-maven-plugin
Modules with tests skipped (29)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-csimple-maven-plugin
  • 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

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

All tested modules (40 modules)
  • Camel :: All Components Sync point
  • Camel :: Assembly
  • Camel :: Catalog :: CSimple Maven Plugin (deprecated)
  • Camel :: Catalog :: Camel Catalog
  • Camel :: Catalog :: Camel Report Maven Plugin
  • Camel :: Catalog :: Camel Route Parser
  • Camel :: Catalog :: Console
  • Camel :: Catalog :: Dummy Component
  • Camel :: Catalog :: Lucene (deprecated)
  • Camel :: Catalog :: Maven
  • Camel :: Catalog :: Suggest
  • Camel :: Component DSL
  • Camel :: Coverage
  • Camel :: Docs
  • Camel :: Endpoint DSL
  • Camel :: Endpoint DSL :: Support
  • 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 :: JTA
  • Camel :: Kafka
  • Camel :: Kamelet Main
  • Camel :: Launcher
  • Camel :: Launcher :: Container
  • Camel :: SQL
  • 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

@Croway Croway merged commit 6449c0e into apache:main Jul 13, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants