[
https://issues.apache.org/jira/browse/CAMEL-25942?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Claus Ibsen resolved CAMEL-25942.
---------------------------------
Resolution: Fixed
> camel-aws2-kinesis - resuming with the resumable EIP gives every shard the
> position of another shard, and fails with a ClassCastException once an offset
> was stored
> -------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: CAMEL-25942
> URL: https://issues.apache.org/jira/browse/CAMEL-25942
> Project: Camel
> Issue Type: Bug
> Components: camel-aws2-kinesis
> Reporter: shashank
> Assignee: shashank
> Priority: Major
> Fix For: 4.24.0
>
>
> The polling consumer asks the route's {{ResumeActionAwareAdapter}} to resume
> a shard each time the shard needs a new shard iterator
> ({{Kinesis2Consumer.resume}}, main 9f839f4bcc9d). The adapter calls
> {{evalEntry}} of the {{KinesisResumeAction}} for *every* entry of the resume
> cache, and the default action sets the shard id and sequence number of each
> entry in turn on the {{GetShardIteratorRequest}} of the shard that asked
> ({{KinesisResumeAction.evalEntry}}: {{builder.shardId((String) shardId)}},
> {{AFTER_SEQUENCE_NUMBER}}, {{(String) sequenceNumber}}). So:
> * with more than one shard in the cache, every shard gets the position of the
> last entry. With the cache {{{shard-0=1, shard-1=2}}} and two shards, both
> slots read {{shard-1}} after sequence 2: its records are routed twice (once
> with {{CamelAwsKinesisShardId=shard-0}}) and {{shard-0}} is never read from
> its own position;
> * {{KinesisResumeStrategy.updateLastOffset}} stores a {{KinesisOffset}} in
> the cache when the resumable EIP completes an exchange, but {{evalEntry}}
> casts the value to {{String}}. The next time a shard needs a new iterator
> (the route or context restarts with the same cache, the iterator expired, a
> child shard after resharding), {{getShardIterator}} throws
> {{ClassCastException: KinesisOffset cannot be cast to String}} on every poll
> and the shard is never read again;
> * the adapter is one instance for the route, and {{poll()}} gets the
> iterators of the shards in parallel ({{parallelStream}}). {{setResumeAction}}
> + {{resume()}} of two shards can interleave, and one shard is then resumed
> with the action (and request builder) of the other.
> CAMEL-24156 listed the multi-shard resume in its camel-aws2-kinesis section;
> PR #24863, which resolved that umbrella, only created one action per shard.
> The deleted {{KinesisDefaultResumeAdapter}} (before CAMEL-20840) looked up
> {{cache.get(shardId, KinesisOffset.class)}}; the action that replaced it
> ignores its own {{shardId}}. The only test, {{KinesisConsumerResumeIT}},
> overrides {{evalEntry}} and uses one shard.
> *Fix*: the default action uses only the entry whose key is its own shard id,
> takes the sequence number from a {{String}} or an {{Offset}}
> ({{KinesisOffset}}), and keeps the entry ({{false}}, per the {{ResumeAction}}
> contract). The consumer sets the action and resumes under one lock shared by
> all consumers, because routes that use the same resume strategy bean share
> its adapter and {{poll()}} gets the iterators of the shards in parallel. One
> paragraph in the component docs (Custom Resume Action) says that
> {{evalEntry}} sees every entry and that a subclass should compare the key
> with {{getShardId()}}.
> *Tests*: three new tests in {{KinesisConsumerPositionTest}} (in-memory
> Kinesis client of that test; a resume cache that follows the
> {{ResumeCache.forEach}} contract like camel-caffeine's {{CaffeineCache}}).
> All fail on main:
> * {{resumeStrategyStartsEachShardAfterItsOwnSequenceNumber}}: {{expected:
> <[b0, c0, c1]> but was: <[c1, c1]>}};
> * {{resumeStrategyStartsANewConsumerAfterTheOffsetsOfTheResumableEip}}
> (offsets stored as the resumable EIP does, new consumer, then an expired
> iterator): {{ClassCastException: KinesisOffset cannot be cast to String}} at
> {{KinesisResumeAction.evalEntry}};
> * {{consumersOfOneResumeStrategyResumeOneAfterTheOther}} (two consumers with
> one strategy): {{the second consumer set its resume action while the first
> one resumed with its own}}.
> camel-aws2-kinesis unit tests with the fix: 49 run, 0 failures.
> Not changed (follow-up): {{TransientResumeStrategy.createSimpleCache()}}
> (used by the IT) removes an entry when the action returns {{false}}, the
> opposite of the {{ResumeAction}} contract that {{CaffeineCache}} follows, and
> removes it from the {{HashMap}} while iterating it, so with two or more
> entries the resume throws {{ConcurrentModificationException}}.
> *How it was found*: while checking the open items of CAMEL-24156 on main, by
> reading the resume path of the polling consumer. The JUnit tests above are
> the evidence.
> *Affected*: main, camel-4.23.x, camel-4.22.x, camel-4.18.x, camel-4.14.x,
> camel-4.10.x and camel-4.8.x (the same {{KinesisResumeAction.evalEntry}};
> read, not run).
> _Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)