[ 
https://issues.apache.org/jira/browse/CAMEL-26315?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Guillaume Nodet reassigned CAMEL-26315:
---------------------------------------

    Assignee: Guillaume Nodet

> camel-core - TransientResumeStrategy.createSimpleCache() removes an entry 
> when the resume action returns false, the opposite of the ResumeAction 
> contract, and fails with ConcurrentModificationException
> ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-26315
>                 URL: https://issues.apache.org/jira/browse/CAMEL-26315
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-aws2-kinesis, camel-core, camel-kafka
>            Reporter: Claus Ibsen
>            Assignee: Guillaume Nodet
>            Priority: Major
>             Fix For: 4.24.0
>
>
> {{ResumeAction.evalEntry}} documents its return value as "true if the entry 
> addressed should be invalidated or false otherwise", and 
> {{ResumeCache.forEach}} applies the action to every entry. The two 
> {{ResumeCache}} implementations do opposite things with that return value:
> * camel-caffeine {{CaffeineCache.forEach}} follows the contract: it 
> invalidates the entry when the action returns {{true}}.
> * {{TransientResumeStrategy.createSimpleCache()}} (camel-core-processor) 
> removes the entry when the action returns {{false}}, and it removes it from a 
> plain {{HashMap}} while it is iterating over {{entrySet()}}:
> {code:java}
> public void forEach(BiFunction<? super Object, ? super Object, Boolean> 
> action) {
>     for (Map.Entry<Object, Object> e : cache.entrySet()) {
>         if (!action.apply(e.getKey(), e.getValue())) {
>             cache.remove(e.getKey());
>         }
>     }
> }
> {code}
> The resume actions each work with only one of the two caches:
> * camel-aws2-kinesis {{KinesisResumeAction.evalEntry}} always returns 
> {{false}} ("keep the entry"). With the simple cache, the first shard that 
> resumes removes the entries of every other shard. With two or more entries, a 
> {{ConcurrentModificationException}} is more likely, because the entry is 
> removed during the iteration. {{KinesisConsumerResumeIT}}, the only resume 
> example for the component, uses 
> {{TransientResumeStrategy.createSimpleCache()}}. CAMEL-25942 (PR #28461) made 
> each shard resume from its own entry, but it relies on the cache keeping the 
> other entries.
> * camel-kafka {{KafkaResumeAdapter.resume}} returns {{true}} after 
> {{consumer.seek(...)}}. With the simple cache the entry is kept. With 
> {{CaffeineCache}}, which follows the contract, every entry is invalidated 
> after the first resume.
> The simple cache is also used by the resume ITs of camel-cassandraql, 
> camel-couchbase and camel-kafka, and by the file consumer resume tests in 
> camel-core.
> h4. Proposed change
> # Make {{createSimpleCache().forEach}} follow the documented contract (remove 
> on {{true}}). Remove through the iterator ({{Iterator.remove()}}), or use 
> {{entrySet().removeIf(...)}}, so the iteration cannot fail with a 
> {{ConcurrentModificationException}}. A {{ConcurrentHashMap}} may also be 
> worth it, since consumers of parallel shards or partitions use one cache.
> # Check every {{evalEntry}} / {{forEach}} callback against the contract. 
> {{KafkaResumeAdapter.resume}} should most likely return {{false}} (keep the 
> offset), and {{KinesisResumeAction}} already returns {{false}}.
> # Add a unit test for {{createSimpleCache().forEach}} with two or more 
> entries, for both return values.
> # Changing the simple cache's behaviour affects custom {{ResumeAction}}s 
> written against it, so it needs an upgrade-guide note.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to