[
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)