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)