shashank created CAMEL-24954:
--------------------------------

             Summary: Stopping a route with an Idempotent Consumer stops and 
clears a repository that other running routes still use
                 Key: CAMEL-24954
                 URL: https://issues.apache.org/jira/browse/CAMEL-24954
             Project: Camel
          Issue Type: Bug
          Components: camel-core
            Reporter: shashank


{{IdempotentConsumer.doStop()}} stops the idempotent repository 
({{ServiceHelper.stopService(processor, idempotentRepository)}}). The in-memory 
repositories clear their data when stopped:
* {{MemoryIdempotentRepository.doStop()}} runs {{cache.clear()}}.
* {{KeyValueIdempotentRepository.doStop()}} stops the underlying 
{{KeyValueRepository}}, and {{MemoryKeyValueRepository.doStop()}} runs 
{{store.clear()}}. That clears every entry in the store, not only the ones with 
the {{idempotent:}} prefix.

A repository is often shared: the same bean is referenced from several routes. 
Since 4.23 it is also shared by default. When no repository is configured, 
{{IdempotentConsumerReifier}} wraps the single {{KeyValueRepository}} bean from 
the registry, and {{AggregateProcessor}} and the Cache EIP do the same. So 
stopping one route silently turns off duplicate detection for all the other 
routes, which are still running. With a shared {{KeyValueRepository}}, it also 
throws away the aggregator's in-progress groups.

Reproducer:
{code:java}
from("direct:a").routeId("a").idempotentConsumer(header("id")).idempotentRepository(repo).process(e
 -> count.incrementAndGet());
from("direct:b").routeId("b").idempotentConsumer(header("id")).idempotentRepository(repo).log("b");
{code}
Send id=1 to {{direct:a}} twice (count=1), then call 
{{context.getRouteController().stopRoute("b")}}, then send id=1 to {{direct:a}} 
again. Route a is still started, but {{repo.contains("1")}} is now false and 
the message is processed again (count=2). Results:
* {{MemoryIdempotentRepository}}: count=2.
* {{new KeyValueIdempotentRepository()}}: count=2.
* No repository configured and a {{MemoryKeyValueRepository}} bean in the 
registry (auto-discovered by both routes): count=2.
* {{FileIdempotentRepository}}: count=1, because {{add}} falls back to the file 
store.

With a {{KeyValueIdempotentRepository}} and a {{KeyValueAggregationRepository}} 
over the same {{MemoryKeyValueRepository}}, the store contains {{[aggregate:g1, 
idempotent:1]}} before {{stopRoute}} on the idempotent route and {{[]}} after 
it. The next message for group g1 then starts a new group, so the first message 
is lost from the aggregate.

The Cache EIP (new in 4.23) has the same pattern: {{CacheProcessor.doStop()}} 
stops its {{KeyValueRepository}}. With one {{MemoryKeyValueRepository}} bean 
auto-discovered by an idempotent consumer and a Cache EIP, {{stopRoute}} on the 
cache route clears the store ({{[idempotent:1, K]}} before, {{[]}} after), and 
the idempotent route processes id 1 again.

A single route that is stopped and started also forgets all ids it has seen, 
because its in-memory repository was cleared by the stop.

Other code already handles this:
* {{GenericFileEndpoint.doStart}} adds its idempotent repository as a context 
service ("idempotent repository may be used by others, so add it as a service 
so its stopped when CamelContext stops"), and its {{doStop}} does not stop the 
repository.
* {{AggregateProcessor}} stops its aggregation repository only in 
{{doShutdown}}.

Proposed fix: {{IdempotentConsumer.doStop()}} and {{CacheProcessor.doStop()}} 
no longer stop the repository. {{doStart}} and {{doShutdown}} are unchanged, so 
the repository is still stopped and shut down when the route is removed or the 
CamelContext stops (CAMEL-9569), like {{AggregateProcessor}} does. Behaviour 
change for the 4.23 upgrade guide: a route that is stopped and started again 
keeps the ids in its in-memory repository, and a repository backed by a remote 
store stays started while the route is stopped 
({{IdempotentRepository.clear()}} or the {{clear}} JMX operation forgets the 
ids). A PR with the fix follows, with regression tests.

Not covered by the fix (possible follow-ups):
* Removing a route (rather than stopping it) still shuts down the repository, 
so removing one of several routes that share an in-memory repository still 
clears it for the others. This is the existing CAMEL-9569 trade-off; fixing it 
needs reference counting or an ownership flag.
* {{KeyValueIdempotentRepository.doStop()}} (and 
{{KeyValueAggregationRepository}}) stops the {{KeyValueRepository}} that was 
passed in, although the adapter does not own it.

Found with a TLA+ model of the Idempotent Consumer EIP, then reproduced against 
4.23.0-SNAPSHOT.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to