Skip to content

CAMEL-24080: camel-aws2-kinesis - make consumer shard state thread-safe for multi-shard streams - #24717

Merged
oscerd merged 1 commit into
apache:mainfrom
oscerd:fix/CAMEL-24080
Jul 15, 2026
Merged

CAMEL-24080: camel-aws2-kinesis - make consumer shard state thread-safe for multi-shard streams#24717
oscerd merged 1 commit into
apache:mainfrom
oscerd:fix/CAMEL-24080

Conversation

@oscerd

@oscerd oscerd commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes CAMEL-24080.

When no shardId is configured, Kinesis2Consumer.poll() iterates the shard list with a parallelStream() and each worker runs fetchAndPrepareRecordsForCamel(...) concurrently. The shared per-consumer state written from those workers was not thread-safe:

  • currentShardIterators was a plain HashMap (and warnLogged a plain HashSet), mutated concurrently via updateShardIterator(...) / getShardIterator(...). Concurrent put/add can corrupt the map or lose iterator updates, leading to lost or duplicated records when consuming a stream with more than one shard.
  • processedExchangeCount was updated with getAndSet(processBatch(...)) (and reset with set(0) on the closed-shard path), so parallel shards overwrote each other and poll() returned only the last-finishing shard's count instead of the total. That count feeds ScheduledBatchPollingConsumer idle/backoff accounting (e.g. backoffIdleThreshold).

Fix

  • Use ConcurrentHashMap for currentShardIterators and ConcurrentHashMap.newKeySet() for warnLogged.
  • Accumulate the poll count with addAndGet(...) and drop the set(0) reset on the closed-shard branch.
  • ConcurrentHashMap forbids null values, and the closed-shard detection (isShardClosed, updateShardIterator) relied on storing a null iterator to mark a shard closed. A sentinel (empty string — already treated as "no iterator" by the existing ObjectHelper.isEmpty(...) checks) is stored instead, so closed-shard behavior is unchanged.

Tests

  • New KinesisConsumerMultiShardTest — two open shards, two records each; asserts poll() returns 4 (the sum), which fails against the old getAndSet logic (returned 2).
  • Existing KinesisConsumerClosedShardWith{Silent,Fail}Test continue to pass, covering the null→sentinel closed-shard path.

Backport

The same code is present on camel-4.18.x and camel-4.14.x; this will be backported to both after merge (fixVersions 4.22.0 / 4.18.4 / 4.14.9).


Claude Code on behalf of oscerd

…fe for multi-shard streams

When no shardId is configured, Kinesis2Consumer.poll() iterates the shard
list with a parallelStream() and each worker updates shared per-consumer
state. That state was not thread-safe:

- currentShardIterators was a plain HashMap and warnLogged a plain HashSet,
  both mutated concurrently -> possible map corruption and lost/duplicated
  shard iterators when consuming multiple shards.
- processedExchangeCount was updated with getAndSet(...) (and reset with
  set(0) on the closed-shard path), so parallel shards overwrote each other
  and poll() returned only the last shard's count instead of the total,
  breaking ScheduledBatchPollingConsumer idle/backoff accounting.

Use ConcurrentHashMap / ConcurrentHashMap.newKeySet() for the shared state
and accumulate the poll count with addAndGet(). ConcurrentHashMap forbids
null values, and the closed-shard detection relied on storing a null
iterator, so a sentinel (empty string, already treated as "no iterator" by
the existing ObjectHelper.isEmpty checks) is stored instead.

Adds KinesisConsumerMultiShardTest asserting poll() sums the records across
all shards; existing closed-shard tests continue to pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
@oscerd oscerd added the bug Something isn't working label Jul 15, 2026
@oscerd
oscerd requested review from davsclaus and gnodet July 15, 2026 11:11

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

Clean, well-targeted fix for a real concurrency bug introduced when synchronized blocks were removed (CAMEL-20199). The analysis is thorough and the changes are minimal:

  • HashMap/HashSetConcurrentHashMap/ConcurrentHashMap.newKeySet() — correct for the parallelStream() mutation in poll()
  • getAndSetaddAndGet — properly accumulates across shards instead of last-writer-wins
  • Removing set(0) on the closed-shard path — prevents resetting counts accumulated by other parallel workers
  • Empty-string sentinel for null — clean solution for ConcurrentHashMap's null prohibition, logically equivalent to the old null-based detection

The new KinesisConsumerMultiShardTest exercises the exact failure mode (2 shards × 2 records, asserts total is 4). Existing closed-shard tests continue to validate that path.

LGTM.

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

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

Well-analyzed concurrency fix that correctly replaces HashMap/HashSet with ConcurrentHashMap equivalents and fixes the processedExchangeCount race from getAndSet to addAndGet.

Key observations:

  • The two bugs are real: (1) HashMap structural corruption under concurrent put from parallelStream workers, and (2) getAndSet overwriting the accumulated count — with 2 shards × 2 records, the old code would report only the last shard's count instead of the sum.

  • The sentinel pattern (CLOSED_SHARD_ITERATOR = "") for null values in ConcurrentHashMap is clean. ObjectHelper.isEmpty("") returns true, so existing null checks work correctly with the sentinel.

  • Removing processedExchangeCount.set(0) on the closed-shard early-return path is correct — with addAndGet, a closed shard contributes zero by returning early. The old set(0) would have been destructive under parallelStream by zeroing counts from other shards.

  • The pre-existing check-then-act in getShardIterator is not atomic, but is safe in practice because each shard operates on its own distinct key — no two parallelStream workers contend on the same shardId.

  • The test validates the addAndGet accumulation fix (2 shards × 2 records = 4 total). True concurrent contention isn't exercised since Mockito stubs are synchronous, but this is acceptable for a unit test.

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

@github-actions

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • components/camel-aws/camel-aws2-kinesis

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

Maveniverse Scalpel detected 38 affected modules (current approach: 9).

⚠️ 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 9 modules (1 direct + 8 downstream), skip tests for 29 (generated code, meta-modules)

Modules Scalpel would test (9)
  • camel-aws2-kinesis
  • camel-jbang-mcp
  • camel-jbang-plugin-mcp
  • camel-jbang-plugin-route-parser
  • camel-jbang-plugin-tui
  • camel-jbang-plugin-validate
  • camel-launcher-container
  • 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 (38 modules)
  • Camel :: AWS2 Kinesis
  • 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 :: 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

@oscerd oscerd self-assigned this Jul 15, 2026
@oscerd oscerd added this to the 4.22.0 milestone Jul 15, 2026
@oscerd
oscerd merged commit 75695df into apache:main Jul 15, 2026
8 checks passed
oscerd added a commit that referenced this pull request Jul 16, 2026
…er shard state thread-safe for multi-shard streams (#24744)

Backport of #24717. Thread-safe shard state (ConcurrentHashMap) and accumulated poll count in the Kinesis consumer.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@oscerd
oscerd deleted the fix/CAMEL-24080 branch July 16, 2026 09:02
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 components-aws

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants