shashank created CAMEL-25709:
--------------------------------

             Summary: Aggregate EIP - a group is never completed by its 
completionTimeout, and nothing is logged, when the aggregation repository fails 
once while the timeout checker completes it
                 Key: CAMEL-25709
                 URL: https://issues.apache.org/jira/browse/CAMEL-25709
             Project: Camel
          Issue Type: Bug
          Components: camel-core
            Reporter: shashank
            Assignee: shashank


The completion timeout of the Aggregate EIP is driven by 
{{AggregationTimeoutMap}}, a {{DefaultTimeoutMap}} that the timeout checker 
purges every {{completionTimeoutCheckerInterval}} (1000 ms by default). 
{{DefaultTimeoutMap.purge()}} first removes every expired entry from the map 
and then calls the listeners; the aggregator's listener, 
{{AggregationTimeoutMap.onEviction}}, reads the group from the aggregation 
repository ({{aggregationRepository.get}}), removes it ({{onCompletion}} -> 
{{aggregationRepository.remove}}) and sends it on.

If the listener throws, {{DefaultTimeoutMap.emitEvent}} drops the exception 
without a log line (main 07c54cb62eae, {{DefaultTimeoutMap.java:243-247}}: 
{{catch (Exception t) { // Ignore }}}). The entry is already gone from the 
timeout map, so nothing will ever try to complete that group again:
* the group stays in the aggregation repository and is never sent, unless 
another message with the same correlation key arrives (which puts a new timeout 
entry) or the route is restarted (which rebuilds the timeout map from the 
repository);
* nothing is logged: neither the WARN of the purge task nor any other line.

Only {{OptimisticLockingException}} is handled in {{onEviction}}. Any other 
exception of the repository ends the group's timeout, for example a 
{{JdbcAggregationRepository}} (camel-sql) whose database is not reachable for a 
moment, or a Redis / Cassandra / Infinispan / Hazelcast repository with a 
connection error at that time. CAMEL-24141 fixed the same failure mode for the 
recover task and the completionInterval task, and stated that the 
completionTimeout checker survives repository hiccups: the checker task does, 
but the group whose timeout was being handled does not.

Trigger: {{completionTimeout}} (or {{completionTimeoutExpression}}) with a 
persistent aggregation repository, and one failing {{get}} or {{remove}} call 
of that repository at the moment the group times out. When correlation keys are 
not reused (an order id, a batch id), the group is lost for good while the 
route keeps running.

h3. Reproduction

New {{AggregateTimeoutRepositoryExceptionTest}} (camel-core): a 
{{MemoryAggregationRepository}} wrapper fails exactly once, in {{get}} or in 
{{remove}}, on the call made by the timeout checker 
({{completionTimeout(200)}}, {{completionTimeoutCheckerInterval(50)}}). On main 
both tests fail after 10 seconds:
{noformat}
mock://result Received message count. Expected: <1> but was: <0>
{noformat}
and the test log has no line about the simulated failure.

h3. Proposed fix

{{AggregationTimeoutMap.onEviction}} catches an exception of the completion, 
logs a WARN (correlation key, cause; the stack trace at DEBUG) and puts the key 
back into the timeout map to be tried again after 
{{completionTimeoutCheckerInterval}}. It uses {{putIfAbsent}}, so a timeout 
entry that a new message for the key put in the meantime is kept. When the 
group had already been removed before the error (for example 
{{AggregationStrategy.timeout}} threw), the next try finds no group and does 
nothing, as for a group completed by another node.

A group the repository can never read is now logged once per check interval 
until it is removed, like the completionInterval and recover tasks log each 
failing run. No option or default changes, so no upgrade guide entry.

camel-core tests with the fix: 8185 run, 0 failures, 0 errors, 45 skipped. Not 
run: camel-sql / camel-cassandraql / camel-redis repositories against a real 
store.

How it was found: a sweep of tasks scheduled on executors and timers whose 
first exception ends them (after the camel-ibm-secrets-manager refresh task); 
the aggregator's recover and interval tasks were guarded by CAMEL-24141, so the 
timeout path was read next; {{emitEvent}} swallows listener exceptions after 
the entry is removed. The test confirmed it.

Affected: main (07c54cb62eae; tested). camel-4.23.x ({{onEviction}} line 1579), 
camel-4.22.x (1352), camel-4.18.x (1308) and camel-4.14.x (1294) have the same 
listener and the same {{emitEvent}} (read, not run).

Duplicate check (2026-10-16): JIRA text "AggregationTimeoutMap", "onEviction 
aggregate", "completionTimeout repository", "aggregator completionTimeout never 
completes", "completion timeout repository exception", "DefaultTimeoutMap 
listener exception ignored" (CAMEL-24141, CAMEL-24945, CAMEL-25130, 
CAMEL-24943, CAMEL-24946, CAMEL-25095: none about an exception in the timeout 
listener); GitHub pull requests "AggregationTimeoutMap", "aggregate 
onEviction", "aggregate completion timeout repository", "DefaultTimeoutMap 
listener" (#26790, #27033, #26788, #27039 and reply-manager fixes; none about 
this).

_Filed with Claude Code on behalf of allthingssecurity._




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

Reply via email to