[ 
https://issues.apache.org/jira/browse/CAMEL-24959?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Claus Ibsen resolved CAMEL-24959.
---------------------------------
    Fix Version/s: 4.23.0
       Resolution: Fixed

The fix is merged on main, so it is in Camel 4.23.0:
* 4700e7f3c7aa CAMEL-24959: camel-core - Do not complete a parallel streaming 
split before its parts when the iterator ends with a null
* 3e53ef0f81d3 CAMEL-24959: camel-core - Make the new test class package-private
* c96a268d7bdf CAMEL-24959: camel-core - Make doDoneNoMorePairs private

Resolving, as the ticket was not updated when the PR was merged.

_Claude Code on behalf of Claus Ibsen_

> Split with streaming and parallelProcessing completes before its parts and 
> loses the aggregated result when the iterator returns null after 
> hasNext()=true
> ----------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-24959
>                 URL: https://issues.apache.org/jira/browse/CAMEL-24959
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-core
>            Reporter: shashank
>            Priority: Minor
>             Fix For: 4.23.0
>
>
> An iterator may answer {{hasNext()}} with true and then return null from 
> {{next()}}. The Splitter skips such null parts (CAMEL-9745, 
> {{SplitIteratorNullTest}}). With {{streaming()}} + {{parallelProcessing()}}, 
> a trailing null part makes the split complete at once, while the parts it 
> already sent are still running. The aggregated result is lost and the route 
> continues with the original message.
> In parallel mode {{MulticastReactiveTask.run()}} re-schedules itself whenever 
> {{hasNext}} was true. The next run skips the null in 
> {{getNextProcessorExchangePair()}}, finds no pair and calls 
> {{doDone(result.get(), true)}} immediately. The replies of the in-flight 
> parts are then ignored by the {{done}} check in {{aggregate()}}. Sequential 
> mode is not affected, because there the next run starts only after the 
> previous part completed.
> Reproduced against 4.23.0-SNAPSHOT with the iterator from 
> {{SplitIteratorNullTest}} (A, B, C, then {{hasNext()=true}} / 
> {{next()=null}}), a concatenating strategy and a 300 ms sub-route:
> {noformat}
> split(...).streaming()                        -> body=ABC after 940 ms
> split(...).parallelProcessing()               -> body=ABC after 308 ms
> split(...).streaming().parallelProcessing()   -> body=<original> after 3 ms; 
> A, B, C processed afterwards and never aggregated
> {noformat}
> CAMEL-21114 reported the same symptom for transacted routes with the zip 
> splitter (fixed there by changing the transacted scheduling). The reactive 
> parallel path still has it.
> Proposed fix: when the task finds no more pairs, treat it like "last pair 
> sent" (new {{MulticastTask.doDoneNoMorePairs()}}, called from 
> {{MulticastReactiveTask.run()}} instead of {{doDone}}). Under the task lock 
> it sets {{allSent}}, aggregates the completed exchanges that are queued, and 
> calls {{doDone}} only if everything sent has been aggregated. Otherwise 
> {{aggregate()}} completes when it aggregates the last part, as it already 
> does when the last pair is known to be the last one.
> * The lock is needed. A lock-free "set {{allSent}}, then compare 
> {{nbAggregated}} with {{nbExchangeSent}}" could see the counter the timeout 
> is incrementing for the missing indices and complete as a normal completion 
> while {{timeout()}} is still calling the strategy (a TLA+ model of the task 
> finds this interleaving).
> * Aggregating under the lock is needed too. {{aggregate()}} only does 
> {{tryLock()}}, so a part that completes while the task holds the lock gives 
> up and relies on the lock holder to poll its result.
> * The poll loop of {{aggregate()}} moved unchanged into a private 
> {{aggregateCompleted()}}, which both methods use.
> * Sequential mode takes the same path. There the previous part is always 
> aggregated before the next run, so it behaves as before: complete at once.
> * The transacted task is unchanged (CAMEL-21114 fixed it there).
> A PR with the fix follows, with regression test 
> {{SplitParallelStreamingIteratorNullTest}}. It uses the iterator from 
> {{SplitIteratorNullTest}}, and the parts wait until the iterator's final 
> {{hasNext()}} returned false, so they are still in flight when the splitter 
> finds no more parts. Test methods: {{testSplitStreamingParallel}} (the bug) 
> and {{testSplitStreaming}} (sequential control). Without the fix, 
> {{testSplitStreamingParallel}} fails with {{The split should return the 
> aggregated parts, but returned: 
> org.apache.camel.processor.SplitParallelStreamingIteratorNullTest$MyIterator@70cc4471
>  ==> expected: <ABC> ...}} (the original body).
> Found with a TLA+ model of the multicast task, then reproduced against 
> 4.23.0-SNAPSHOT.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to