shashank created CAMEL-26575:
--------------------------------

             Summary: camel-core - Multicast, Split and RecipientList: an 
AggregationStrategy whose onCompletion throws hangs the exchange
                 Key: CAMEL-26575
                 URL: https://issues.apache.org/jira/browse/CAMEL-26575
             Project: Camel
          Issue Type: Bug
          Components: camel-core
            Reporter: shashank
            Assignee: shashank


Seen while fixing CAMEL-26464.

{{MulticastTask.doDone}} (and {{doTimeoutDone}}) first sets the done flag 
({{done.compareAndSet(false, true)}}) and then calls 
{{MulticastProcessor.doDone}}, which calls 
{{AggregationStrategy.onCompletion(subExchange, original)}} before it schedules 
the callback of the exchange ({{reactiveExecutor.schedule(callback)}}, its last 
statement). When {{onCompletion}} throws, the callback is never scheduled. The 
exception goes to the catch of {{aggregateCompleted}} (or of {{timeout}}), 
whose {{doFailed}} / {{doTimeoutFailed}} finds the task done already and only 
logs it at DEBUG ({{Exception occurred after the exchange was already 
completed...}}). With {{stopOnException}}, where the multicast is done from the 
callback of the failed sub exchange, it goes up to the reactive executor, which 
logs and drops it. Either way the exchange hangs: a synchronous caller 
({{template.send}}, a consumer thread, a {{synchronous()}} multicast) blocks 
for good, the exchange stays inflight and a graceful shutdown waits for it.

A SPIN model of the completion (the aggregator, the callback of a failed sub 
exchange with {{stopOnException}}, and the timeout, all competing for the done 
flag) confirms it: it is not a race, it hangs every time: once {{onCompletion}} 
throws, no interleaving schedules the callback; with the change below it is 
scheduled exactly once in every interleaving.

{code:java}
AggregationStrategy strategy = new AggregationStrategy() {
    public Exchange aggregate(Exchange oldExchange, Exchange newExchange) { 
return newExchange; }
    public void onCompletion(Exchange exchange, Exchange inputExchange) { throw 
new IllegalStateException("onCompletion"); }
};
from("direct:start").multicast(strategy).to("mock:a", 
"mock:b").end().to("mock:result");

template.send("direct:start", e -> e.getMessage().setBody("Hello"));   // never 
returns
{code}

On main the send hangs for the multicast (sequential, {{parallelProcessing}}, 
{{synchronous}}, {{stopOnException}}, {{timeout}}), the split (also 
{{streaming}}, also in a {{doTry}}: the {{doCatch}} never runs) and the 
recipient list. The same strategy throwing from {{aggregate}} fails the 
exchange with the exception, as expected. {{onCompletion}} is user code (a 
custom strategy, the completion mixin of {{FlexibleAggregationStrategy}}). Its 
javadoc says it must not throw, and none of Camel's own strategies do, so this 
needs a strategy that breaks that contract; it should still fail the exchange, 
as when {{aggregate}} throws, instead of hanging it with only a DEBUG log.

*Proposal*
* Catch an exception from {{onCompletion}} in {{MulticastProcessor.doDone}}, do 
the rest of the done work and schedule the callback as now, with the exchange 
failed with that exception (set after the results are copied, as 
{{ExchangeHelper.copyResults}} would overwrite it) and not marked as exhausted, 
so the error handler of the route handles it (redelivery, {{onException}}, dead 
letter channel) as when the strategy throws from {{aggregate}}. An exchange 
that has failed already (such as with {{stopOnException}}) keeps its exception, 
which tells where it failed, with the new one added as suppressed.
* {{doDone}} runs once per exchange (it is guarded by the done flag), so the 
callback is still scheduled exactly once; nothing changes on the success path.
* camel-4.18.x has the same bug.

_Filed with Claude Code on behalf of allthingssecurity._



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

Reply via email to