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)