You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@camel.apache.org by "Federico Mariani (Jira)" <ji...@apache.org> on 2026/07/16 09:28:00 UTC
[jira] [Updated] (CAMEL-24138) camel-core - Splitter errorThreshold/maxFailedRecords: failure tracker leaks into nested splits and silently swallows their failures
[ https://issues.apache.org/jira/browse/CAMEL-24138?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Federico Mariani updated CAMEL-24138:
-------------------------------------
Attachment: SplitterNestedFailureTrackerLeakIssueTest.java
> camel-core - Splitter errorThreshold/maxFailedRecords: failure tracker leaks into nested splits and silently swallows their failures
> ------------------------------------------------------------------------------------------------------------------------------------
>
> Key: CAMEL-24138
> URL: https://issues.apache.org/jira/browse/CAMEL-24138
> Project: Camel
> Issue Type: Bug
> Components: camel-core, eip
> Affects Versions: 4.22.0
> Reporter: Federico Mariani
> Priority: Critical
> Attachments: SplitterNestedFailureTrackerLeakIssueTest.java
>
>
> The new {{errorThreshold}}/{{maxFailedRecords}} options (CAMEL-23264, unreleased 4.22.0-SNAPSHOT) store the splitter's {{SplitFailureTracker}} as a plain exchange property ({{CamelSplitFailureTracker}}, {{Splitter.java:210}}). Exchange properties are copied into every sub-exchange by {{createCorrelatedCopy}}, so the tracker *leaks into any nested splitter* running inside the outer split. The inner splitter's {{shouldContinueOnFailure}} looks the tracker up on _its_ input exchange:
> {code:java}
> SplitFailureTracker tracker = original.getProperty(SPLIT_FAILURE_TRACKER, SplitFailureTracker.class);
> if (tracker == null) {
> return super.shouldContinueOnFailure(subExchange, original, index); // normal behavior
> }
> tracker.recordFailure(index, subExchange.getException());
> // the inner splitter has maxFailedRecords == 0 and errorThreshold == 0, so both checks are skipped
> ...
> subExchange.setException(null); // <-- inner failure silently cleared
> return true;
> {code}
> Because the _inner_ splitter instance has no thresholds configured, both threshold checks are skipped and it unconditionally clears the exception of its own failed sub-items. Consequences:
> # Inner split failures no longer propagate — the inner split (and therefore the outer item) *succeeds silently* even though items failed. The outer {{maxFailedRecords}}/{{errorThreshold}} never trips.
> # Inner failures are recorded into the *outer* tracker with *inner* indexes, corrupting {{SplitResult}} ({{failureCount}} can exceed {{totalItems}}, making {{getSuccessCount()}} negative).
> *Reproducer (attached, fails on main):* {{SplitterNestedFailureTrackerLeakIssueTest}} — outer {{split(body()).maxFailedRecords(1)}} around a plain inner {{split(body())}} whose item throws; the exchange completes without an exception instead of aborting:
> {noformat}
> org.opentest4j.AssertionFailedError: Inner split failure was silently swallowed by the leaked outer failure tracker ==> expected: not <null>
> {noformat}
> Suggested fix direction: don't discover the tracker via an inherited exchange property — key it per-processor instance (like the {{AGGREGATION_STRATEGY}} map keyed by processor), or only honor the tracker when {{this}} splitter actually has thresholds configured ({{errorThreshold > 0 || maxFailedRecords > 0}}).
> Since CAMEL-23264 is unreleased, this can still be fixed before 4.22.0 ships.
> _Filed by Claude Code on behalf of Federico Mariani (fmariani)_
--
This message was sent by Atlassian Jira
(v8.20.10#820010)