shashank created CAMEL-24958:
--------------------------------

             Summary: Recipient List does not release the producers (and 
prototype endpoints) of recipients that were not sent to (stopOnException, 
timeout)
                 Key: CAMEL-24958
                 URL: https://issues.apache.org/jira/browse/CAMEL-24958
             Project: Camel
          Issue Type: Bug
          Components: camel-core
            Reporter: shashank


{{RecipientListProcessor.createProcessorExchangePairs}} acquires a producer for 
every recipient up front ({{producerCache.acquireProducer}}). The producer is 
released, and a prototype endpoint ({{cacheSize=-1}}) is stopped, only in 
{{RecipientProcessorExchangePair.done()}}, which 
{{MulticastProcessor.afterSend}} calls for recipients that were actually sent 
to. When the list stops early, the remaining recipients are never sent to and 
their producers are never released:
* {{stopOnException}} and a recipient fails;
* the timeout fires before a recipient is sent;
* the task is rejected.

{{MulticastProcessor.doDone}} only releases the pair exchanges. So:
* non-singleton producers (ftp/sftp/ftps, smb, ssh, mina sync, ...): every 
failed exchange starts a new producer, which is never returned to the pool;
* {{cacheSize=-1}}: the prototype endpoint and its producer are never stopped, 
not even when the CamelContext stops.

Reproduced against 4.23.0-SNAPSHOT with a test component whose endpoint has 
{{isSingletonProducer()=false}} and counts starts/stops (50 exchanges per line, 
{{recipientList(header("to")).stopOnException()}}):
{noformat}
[pooled:a, direct:boom]                  (all sent)   producers started=1   
stopped=0
[direct:boom, pooled:b]                  (b not sent) producers started=51  
stopped=0     <- 50 extra producers
cacheSize(-1) [pooled:dN]                (all sent)   endpoints started=+50 
stopped=+50   (control)
cacheSize(-1) [direct:boom, pooled:cN]   (c not sent) endpoints started=+50 
stopped=+0
after CamelContext.stop()                             producers started=151 
stopped=101, endpoints started=102 stopped=52
{noformat}

Note: {{timeout}} requires {{parallelProcessing}} on a Recipient List ("Timeout 
is used but ParallelProcessing has not been enabled"), so the timeout case is a 
parallel one.

Proposed fix (all in {{RecipientListProcessor}}):
* {{RecipientProcessorExchangePair}} gets a state: NEW, BEGUN, DONE or 
RELEASED, changed only by compare-and-set.
** {{begin()}} claims NEW -> BEGUN. If the claim fails, the pair was already 
released, and {{getProcessor()}} then returns a no-op processor, so the 
recipient is not sent.
** {{done()}} releases only on BEGUN -> DONE, which makes it idempotent.
** A new {{releaseIfNotBegun()}} releases on NEW -> RELEASED.
** The release itself is unchanged: {{producerCache.releaseProducer(...)}}, and 
stopping the prototype endpoint.
* {{RecipientListProcessor}} overrides {{doDone(...)}}. It calls 
{{releaseIfNotBegun()}} on every pair (the pairs of a Recipient List are always 
a {{List}}), then {{super.doDone(...)}}. So the producers are released before 
the callback continues the route.
* {{MulticastProcessor}}, {{Splitter}} and the {{ProcessorExchangePair}} 
interface are unchanged.

Behaviour change: with {{parallelProcessing}}, a recipient whose task had not 
started yet when the Recipient List completed (stopOnException, timeout) is now 
skipped. Before, it was still sent afterwards. Recipients that had already 
started keep running, as before. There is an upgrade-guide entry.

A PR with the fix follows, with regression test 
{{RecipientListReleaseUnsentProducerTest}}. It uses a component with a 
non-singleton producer that counts producer starts and endpoint starts/stops. 
Test methods:
* {{testStopOnException}} (sequential)
* {{testStopOnExceptionParallel}} (single-thread pool)
* {{testPrototypeEndpointStopped}} ({{cacheSize(-1)}})
* {{testTimeout}} (parallel, the single pool thread is blocked by the first 
recipient)

Without the fix they fail with {{expected: <1> but was: <5>}}, {{expected: <1> 
but was: <5>}}, {{expected: <5> but was: <0>}} and {{expected: <1> but was: 
<2>}}.

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