shashank created CAMEL-26321:
--------------------------------

             Summary: camel-core - Split EIP: when maxFailedRecords or 
errorThreshold stops the split, the exchange carries the message, headers and 
properties of the failed split item instead of the original input (unlike 
stopOnException)
                 Key: CAMEL-26321
                 URL: https://issues.apache.org/jira/browse/CAMEL-26321
             Project: Camel
          Issue Type: Bug
          Components: camel-core
            Reporter: shashank
            Assignee: shashank


When the Splitter stops because {{maxFailedRecords}} or {{errorThreshold}} is 
reached (CAMEL-23264, since 4.22.0), the exchange gets the message, headers and 
exchange properties of the split item that reached the threshold, as the steps 
inside the split changed them, instead of the original input (main 
426c3f8a4cb8). With {{stopOnException}}, the all-or-nothing variant, the 
exchange keeps the original input and only gets the exception (CAMEL-6802):
{code:java}
from("direct:max").split(body()).maxFailedRecords(1)
    .setHeader("item", simple("${body}")).setBody(constant("changed"))
    .throwException(new IllegalStateException("boom")).end();

send body "a,b,c" with header orig=yes:
maxFailedRecords(1)            -> body=changed headers={orig=yes, item=a} 
exception=CamelExchangeException
maxFailedRecords(2)            -> body=changed headers={orig=yes, item=b} 
exception=CamelExchangeException
stopOnException() instead      -> body=a,b,c   headers={orig=yes}         
exception=CamelExchangeException
{code}
The exchange properties of the item are merged into the exchange: one set in 
the item is there afterwards, one the item removed is still there. Its exchange 
variables are copied too. {{errorThreshold}}, {{parallelProcessing}}, 
{{streaming}}, {{shareUnitOfWork}} and a transacted exchange behave the same. 
With nested splits the outer exchange gets the failed outer item (or, when the 
inner split has a threshold too, the failed inner item). The split EIP page 
says "The Splitter will by default return the original input message", and that 
when the threshold is reached "the splitter stops and sets an exception on the 
exchange". In the CAMEL-23264 PR (#22300) zero tolerance is 
"{{stopOnException}} or {{maxFailedRecords(1)}}", but the two return different 
messages.

So a {{doTry}}/{{doCatch}} around the split, an {{onException}} in a calling 
route or an outer dead letter channel that puts the failed input aside after 
the stop keeps one modified split item instead of the input. The items after it 
were not processed and are not in what was kept, so they are lost. (With a dead 
letter channel or a handled {{onException}} on the route of the split itself, 
the exceptions of the items are handled inside the split and the threshold is 
never reached.)

*Fix* (proposed): both stops go through the same branch of the multicast task 
({{shouldContinueOnFailure}} returns false), but {{MulticastProcessor.doDone}} 
only takes the stopOnException path (set the exception, do not copy the 
sub-exchange back) when the {{stopOnException}} option is set; otherwise it 
runs {{ExchangeHelper.copyResults(original, subExchange)}}. The Splitter now 
marks its failure tracker when a threshold stops the split, and {{doDone}} also 
takes that path when a new protected {{isStoppedOnFailure(original)}} says so 
(false in {{MulticastProcessor}}). The exchange then gets the same exception 
and properties as with {{stopOnException}}, and an aggregation strategy sees 
the stop the same way ({{onCompletion}} with the failed sub-exchange, the items 
before it aggregated, the aggregated result not returned). The 
{{CamelSplitResult}} property is set as before. Split EIP docs and an upgrade 
guide entry, as a route now receives the original input after such a stop.

*Tests*: {{SplitterThresholdStopKeepsOriginalMessageTest}} (14 tests: 
maxFailedRecords 1 and 2, errorThreshold, parallel, streaming, shareUnitOfWork, 
transacted, two nested-split cases, a doTry/doCatch around the split, an 
aggregation strategy and the exception and property set compared with 
stopOnException, and two stopOnException controls). The 12 non-control tests 
fail on main and all pass with the fix. The Splitter, Multicast, RecipientList 
and threshold tests of camel-core pass (613 tests).

*How it was found*: a Rocq specification of Camel's EIPs written from the 
documentation, compared with real Camel on generated routes (12 of 3 000 split 
cases differed, all with {{maxFailedRecords}}), then reproduced on HotSpot.

*Affected*: 4.22.0 and later (4.22.0, 4.22.1, 4.22.2, 4.23.0 and main; 
camel-4.22.x has the same code). Not 4.18.x or 4.14.x, which do not have these 
options.

_Filed with Claude Code on behalf of allthingssecurity._



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

Reply via email to