shashank created CAMEL-25864:
--------------------------------

             Summary: camel-core - Aggregate EIP: with an auto-discovered 
KeyValueRepository, the recover task of one aggregator sends the exchanges that 
another aggregator completed again, on its own route
                 Key: CAMEL-25864
                 URL: https://issues.apache.org/jira/browse/CAMEL-25864
             Project: Camel
          Issue Type: Bug
          Components: camel-core
            Reporter: shashank
            Assignee: shashank


An Aggregate EIP without an {{aggregationRepository}} wraps the single 
{{KeyValueRepository}} of the registry in a {{KeyValueAggregationRepository}} 
(CAMEL-24463; the "Single backend, multiple patterns" section of the 
KeyValueRepository page says no explicit wiring is needed). Every aggregator of 
the application does this with the same bean. Since CAMEL-25819 (main, 4.24.0) 
the wrapper is set with {{setAggregationRepository}}, so each of these 
aggregators now schedules a recover task.

{{KeyValueAggregationRepository}} keeps a completed exchange, until it is 
confirmed, under {{completed:<exchangeId>}}, and {{scan}} returns every key 
with that prefix. So the recover task of aggregator B scans the completed 
exchanges of aggregator A as well. {{AggregateProcessor.RecoverTask}} only 
skips the exchange ids in its own {{inProgressCompleteExchanges}} / 
{{unconfirmedCompleteExchanges}}, so B recovers the exchange that A completed 
and is still processing, marks it redelivered and sends it on B's route 
({{onSubmitCompletion}}), then confirms it. The exchange is delivered twice, 
once on a route that has nothing to do with it. The same happens to an exchange 
that failed on A's route: it is recovered by A and by B. The recover tasks run 
1 s after start and then every 5 s (the default {{recoveryInterval}}, which 
cannot be changed for an auto-discovered repository), so under load every scan 
picks up the exchanges the other aggregators have in flight.

{code:java}
// one KeyValueRepository bean in the registry, e.g. a MemoryKeyValueRepository 
or a RedisKeyValueRepository
from("direct:orders").routeId("orders")
    .aggregate(header("id"), new BodyInAggregatingStrategy()).completionSize(2)
    .process(e -> slowCall())          // the aggregated exchange is completed, 
not yet confirmed
    .to("mock:orders");

from("direct:invoices").routeId("invoices")
    .aggregate(header("id"), new BodyInAggregatingStrategy()).completionSize(2)
    .to("mock:invoices");              // receives the "A+B" of the orders route
{code}

*Fix*: {{KeyValueAggregationRepository}} gets a constructor with a recovery 
name: the completed exchanges are then kept, scanned, recovered and confirmed 
under {{completed[<name>]:<exchangeId>}}, which an adapter without a name 
(prefix {{completed:}}) does not scan. {{AggregateProcessor}} names the adapter 
it creates for an auto-discovered repository after its route id and id 
({{<routeId>/<id>}}). The open groups keep their keys 
({{aggregate:<correlationKey>}}), and so does an adapter created without a 
name. No new bean property, so no generated metadata changes. Completed entries 
written under {{completed:}} by an auto-discovered adapter before (4.23.x, 
where they were never recovered, see CAMEL-25819) are not picked up by the 
named adapters.

*Tests*: new {{AggregateAutoDiscoveredKeyValueRepositorySharedTest}} 
(camel-core): two routes with an aggregator and one 
{{MemoryKeyValueRepository}} bean; the orders route completes a group and holds 
the aggregated exchange downstream across the recover scans. On main it fails 
with {{mock://invoices Received message count. Expected: <0> but was: <1>}}; 
with the fix it passes, the orders route gets "A+B" once and the repository is 
empty at the end. 
{{KeyValueAggregationRepositoryTest.testRecoveryNameKeepsCompletedExchangesApart}}
 covers the adapter. camel-core aggregator and support tests 
({{org.apache.camel.processor.aggregator.**}}, {{org.apache.camel.support.**}}, 
...): 707 run, 0 failures, 3 skipped; camel-support: 136 run, 0 failures. 
{{AggregateAutoDiscoveredKeyValueRepositoryRecoverTest}} (CAMEL-25819) still 
passes.

*How it was found*: review of the changes merged to main in the last week 
(CAMEL-25819), looking at what the change made reachable: the recover task now 
runs for a repository that every aggregator of the application shares. The 
JUnit test above is the evidence.

*Affected*: main only (read, not run). On 4.23.x the auto-discovered adapter 
has no recover task (CAMEL-25819 is fixed in 4.24.0 only), so the duplicates do 
not happen there. An application that explicitly wraps the same 
{{KeyValueRepository}} in a {{KeyValueAggregationRepository}} for several 
aggregators has the same overlap on every version with the adapter; the new 
constructor lets it name each adapter.

_Filed with Claude Code on behalf of allthingssecurity._




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

Reply via email to