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

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

Fixed by https://github.com/apache/camel/pull/26793 (merged as 42f9284a04c2). 
Thanks allthingssecurity!

_Claude Code on behalf of davsclaus_

> camel-seda - request/reply timeout race: a late reply is copied into the 
> caller's exchange after the producer already returned 
> ExchangeTimedOutException
> --------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-24947
>                 URL: https://issues.apache.org/jira/browse/CAMEL-24947
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-seda
>            Reporter: shashank
>            Priority: Minor
>             Fix For: 4.23.0
>
>
> With {{waitForTaskToComplete=Always}}/{{IfReplyExpected}} and {{timeout > 
> 0}}, a reply that arrives at about the time of the timeout can be copied into 
> the caller's exchange after {{SedaProducer}} has already returned to the 
> caller with {{ExchangeTimedOutException}}. The caller can also see the reply 
> body together with the timeout exception.
> {{SedaProducer.process()}}:
> * The consumer side ({{onDone}}, SedaProducer.java:73-93) runs {{if 
> (latch.getCount() == 0) return;}}, then 
> {{ExchangeHelper.copyResults(exchange, response)}}, then 
> {{latch.countDown()}}.
> * The producer side (:125-134) runs {{if (!latch.await(timeout)) { 
> exchange.setException(timeout); queue.remove(copy); latch.countDown(); }}}, 
> then {{callback.done(true)}}.
> The latch check in {{onDone}} and the copy are not atomic with the timeout 
> branch. If the consumer passes the check and the timeout fires before it 
> counts down, both threads write the caller's exchange. The consumer can keep 
> writing after {{callback.done}}. {{copyResults}} replaces the message, 
> properties, variables and flags (routeStop, rollbackOnly, redelivery 
> counters, errorHandlerHandled), and finally calls {{setException(null)}}, 
> while the caller's error handler / doCatch is already working on the same 
> exchange.
> Reproduced against 4.23.0-SNAPSHOT. The pause below is a {{SafeCopyProperty}} 
> set by the consumer route; {{copyResults}} calls it after the message copy 
> and before {{setException}}:
> {noformat}
> template.send(seda:a?timeout=300), consumer reply copy paused inside onDone
>   returned to caller after 315 ms: body=reply, 
> exception=ExchangeTimedOutException
>   same exchange 300 ms later:      body=reply, exception=null
> route doTry{to(seda:b?timeout=300)} doCatch(ExchangeTimedOutException)
>   inside doCatch: body=reply, exception=null, caught=ExchangeTimedOutException
> {noformat}
> Without any pause, 400 requests with {{timeout=20}} and consumers taking 
> 18-22 ms gave 24 exchanges returned with the reply body plus 
> {{ExchangeTimedOutException}}, and 2 exchanges that were modified after the 
> producer had returned.
> The comment in {{onDone}} ("check for timeout, which then already would have 
> invoked the latch") shows the intent: a late response should be ignored.
> Proposed fix: let exactly one side own the result, with an {{AtomicBoolean 
> completed}} shared by the producer and the {{onDone}} synchronization:
> * {{onDone}} does {{completed.compareAndSet(false, true)}} before copying, 
> and ignores the response if that fails.
> * On timeout, the producer does {{completed.compareAndSet(false, true)}}; if 
> it wins it sets {{ExchangeTimedOutException}} and removes the copy from the 
> queue as today. If it loses (the consumer is copying the reply), it waits for 
> the latch without timeout (the copy is short) and returns the reply instead 
> of the timeout.
> * The {{latch.countDown()}} in the timeout branch is no longer needed to make 
> {{onDone}} ignore a late reply.
> A PR with the fix follows, with regression test {{SedaTimeoutLateReplyTest}} 
> (pauses the consumer inside the reply copy via a {{SafeCopyProperty}}). If 
> the fix for the related interrupt issue (SedaProducer swallows 
> InterruptedException) is merged too, the interrupted reply wait must claim 
> the exchange the same way.
> (Found with a TLA+ model of SedaProducer/SedaConsumer; the fixed variant 
> passes.)



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

Reply via email to