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

Claus Ibsen resolved CAMEL-25017.
---------------------------------
    Resolution: Fixed

Fixed by https://github.com/apache/camel/pull/26882 (merged to main for 4.23.0).

_Claude Code on behalf of davsclaus_

> Claim Check EIP: Split/Multicast/Recipient List/Wire Tap/SEDA copies share 
> the parent's claim check repository, so parallel parts get each other's 
> messages back
> ----------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-25017
>                 URL: https://issues.apache.org/jira/browse/CAMEL-25017
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-core
>            Reporter: shashank
>            Priority: Minor
>             Fix For: 4.23.0
>
>
> {{ClaimCheckProcessor}} keeps its repository in the exchange property 
> {{CamelClaimCheckRepository}} and creates it lazily 
> ({{ClaimCheckProcessor.java:212-219}}). {{DefaultClaimCheckRepository}} is a 
> plain {{HashMap}} plus an {{ArrayDeque}}. The exchange copy constructor 
> copies the internal properties by reference ({{AbstractExchange.java:126}}, a 
> new EnumMap of the parent's internal properties), so once the parent exchange 
> has used the Claim Check, every copy made by Split, Multicast, Recipient 
> List, Wire Tap, Enrich, ... uses the same repository instance as the parent 
> and as each other.
> Consequences:
> * with parallel processing, parts that {{Set}}/{{Get}} the same key overwrite 
> each other's data, and {{Push}}/{{Pop}} pop another part's message: a part 
> continues with another part's body and headers, silently;
> * the unsynchronized {{HashMap}}/{{ArrayDeque}} are modified concurrently 
> (parallel parts, or a Wire Tap copy running next to the original);
> * also sequentially: a part that fails between {{Push}} and {{Pop}} leaves 
> its message on the parent's stack, and the parent's later {{Pop}} returns the 
> part's message instead of its own.
> A typical route that is affected: {{claimCheck(Set, "original")}} -> 
> {{split(...).parallelProcessing()}} -> in each part {{claimCheck(Set, 
> "item")}} / call a service / {{claimCheck(Get, "item")}} -> after the split 
> {{claimCheck(Get, "original")}}.
> *Reproduction*: split "A,B" with {{parallelProcessing()}}, each part saves 
> its message ({{Set "item"}} or {{Push}}), a service step sets the body to 
> "reply-from-service", and restores it ({{Get "item"}} or {{Pop}}); latches 
> force the order part 0 saves, part 1 saves, part 0 restores, part 1 restores. 
> 20 runs each:
> {noformat}
> setget  parent did not use the Claim Check:            0/20 wrong (always 
> [0:A, 1:B])
> setget  parent did claimCheck(Set,"orig") before split: 20/20 wrong (e.g. 
> [0:B, 1:B], expected [0:A, 1:B])
> pushpop parent did not use the Claim Check:            0/20 wrong
> pushpop parent did claimCheck(Set,"orig") before split: 20/20 wrong (e.g. 
> [0:B, 1:A])
> {noformat}
> The only difference between the two routes is one {{claimCheck(Set, "orig")}} 
> in the parent before the split.
> A TLA+ model with a shared repository violates "each part gets its own 
> message back" in 5 states for Set/Get and for Push/Pop; with a repository per 
> exchange it holds.
> This contradicts the {{ClaimCheckProcessor}} javadoc, which says the 
> repository is "not shared among Exchanges, but a private instance is created 
> per Exchange". Sharing only happens when the parent used the Claim Check 
> before the EIP, because the repository is created on first use.
> *Proposed fix:* give each copy its own copy of the repository 
> ({{DefaultClaimCheckRepository}} implements {{SafeCopyProperty}}, applied in 
> {{AbstractExchange.copy()}}, {{ExchangeHelper.copyExchangeWithProperties}} 
> (Disruptor) and the deprecated {{PooledProcessorExchangeFactory}}), so a part 
> can still read what the parent saved but its own writes stay private. The 
> exchange that {{Set}}/{{Push}} stores should not carry the repository; detach 
> it while making that copy, so the repository is not copied on every 
> {{Set}}/{{Push}}. (Giving copies an empty repository instead breaks 
> {{MulticastMixOriginalMessageBodyAndEnrichedHeadersClaimCheckTest}}, where a 
> multicast part reads the parent's claim check.)
> Behaviour change: what a part stores is no longer visible to the parent or to 
> the other parts. After a Multicast or Recipient List, the parent continues 
> with the repository of the result exchange, as for its other properties.
> _Filed with Claude Code on behalf of allthingssecurity._



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

Reply via email to