shashank created CAMEL-25017:
--------------------------------

             Summary: 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


{{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