shashank created CAMEL-26642:
--------------------------------

             Summary: camel-core - Aggregate EIP: a recover pass that outlives 
a route restart can send a recovered exchange twice
                 Key: CAMEL-26642
                 URL: https://issues.apache.org/jira/browse/CAMEL-26642
             Project: Camel
          Issue Type: Bug
          Components: camel-core
            Reporter: shashank
            Assignee: shashank


Found while checking the processors that start their own background tasks in 
doStart for work that goes on after a quick stop and start (same family as 
CAMEL-26634, the batch Resequencer). Shown with deterministic tests (latches in 
the repository's {{scan}} and {{recover}}), no sleeps.

{{AggregateProcessor.doStop}} shuts down the executor of the recover task 
({{RecoverableAggregationRepository}} with {{useRecovery}}, such as the JDBC, 
LevelDB or key-value repositories) but does not wait for a recover pass that is 
running. A pass checks {{isRunAllowed()}} before each exchange, so a pass that 
is in one long call during the stop (the {{scan}} of the repository, the 
{{recover}} load of an exchange, a dead letter send) goes on after a quick 
start, next to the recover task of the new start. Then:
* the two passes share the processor's 
{{inProgressCompleteExchangesForRecoveryTask}} set and {{recoveryInProgress}} 
flag, and each pass clears them when it ends;
* {{doStart}} creates a new aggregation lock, so the two passes no longer 
exclude each other.

{noformat}
old pass:  scan() ... (slow, many exchanges to recover)
admin:     stopRoute; startRoute            (doStop does not wait for the old 
pass)
new pass:  snapshot = {} (Z not in progress), scan() ...
old pass:  scan -> {Z}; recover(Z) and send it; ends: recoveryInProgress = 
false, snapshot cleared
new pass:  scan -> {Z} (not confirmed yet); the snapshot is empty -> recover(Z) 
and send it again
{noformat}

or, with the old pass in {{recover(Z)}} across the restart: the new pass (new 
lock) recovers and sends Z, then the old pass sends the copy it loaded. Either 
way Z is sent twice. On the second path the old pass also unlocks the lock of 
the new start ({{finally lock.unlock()}} reads the field again), which throws 
an {{IllegalMonitorStateException}}.

Without a restart the passes never overlap (fixed delay on one thread), so the 
in-progress check of CAMEL-24943 holds.

Ways to get there: a stop + start or restart of the route (JMX, control bus, 
CLI, route policies, the supervising route controller) while a recover pass 
runs. A pass can take long when many exchanges wait to be recovered (one 
{{recover}} read per exchange), which is when recovery matters.

Expected: a recover pass of an earlier start ends at the stop, and an exchange 
is recovered and sent once.

Suggested fix: run one recover pass at a time 
({{recoveryInProgress.compareAndSet}}: a pass skips while another runs), and 
unlock the lock that the pass locked. The same code is on 4.18.x and 4.14.x.

_Filed with Claude Code on behalf of allthingssecurity._



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to