allthingssecurity opened a new pull request, #29372: URL: https://github.com/apache/camel/pull/29372
# Description [CAMEL-26642](https://issues.apache.org/jira/browse/CAMEL-26642) With a recoverable aggregation repository (`useRecovery`), a stop + start of the route while a recover pass was running could recover and send the same exchange twice. `AggregateProcessor.doStop` shuts down the executor of the recover task but does not wait for a pass that is running (no change there). 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) went on after a quick start, next to the recover task of the new start. The two passes shared the snapshot of the exchanges in progress and the `recoveryInProgress` flag (each pass clears them when it ends), and they no longer excluded each other because `doStart` creates a new aggregation lock: ``` old pass: scan() ... admin: stopRoute, startRoute (the stop does not wait for the old pass) new pass: snapshot = {}, scan() ... old pass: scan -> {Z}, recover Z and send it; ends: recoveryInProgress = false, snapshot cleared new pass: scan -> {Z} (not confirmed yet), snapshot empty -> recover Z and send it again ``` or, with the old pass in `recover(Z)` across the restart, the new pass sends Z and then the old pass sends the copy it loaded. On that 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. 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). Change (`AggregateProcessor.RecoverTask`): - one recover pass at a time: a pass starts only if no other pass runs (`recoveryInProgress.compareAndSet` instead of `set`), so the task of the new start skips while an old pass is still running. The old pass then is the only one, as one long pass is without a restart; - a pass unlocks the lock it locked. Waiting in `doStop` for the pass was not chosen: a pass can be stuck in a slow repository call, and the stop would wait for it. A recover pass blocked in the repository now also delays the recover task of the next start until it returns (before, the two could run together and send an exchange twice). Tests: `AggregateRecoverRestartTest` (an in-memory recoverable repository whose `scan` or `recover` can be held with latches; no sleeps; the tests wait for one run of the recover task of the new start through a recording `ThreadPoolFactory`, so they hold whether that task recovers or skips): - the old pass held in `scan` across the restart, the new pass held in `scan` until the old pass ended; - the old pass held in `recover` across the restart. On main both fail (the exchange is sent twice); with the fix 2/2, and 20 runs each (40/40). Related camel-core tests (the aggregator package, `*Aggregat*`, `*Recover*`, `*Suspend*`, `*Shutdown*`): 375 (7 skipped), 0 failures. The same `RecoverTask` code is on `camel-4.18.x` and `camel-4.14.x`; the change and the test should cherry-pick to them if a backport is wanted. # Target - [x] I checked that the commit is targeting the correct branch (Camel 4 uses the `main` branch) # Tracking - [x] If this is a large change, bug fix, or code improvement, I checked there is a [JIRA issue](https://issues.apache.org/jira/browse/CAMEL) filed for the change (usually before you start working on it): [CAMEL-26642](https://issues.apache.org/jira/browse/CAMEL-26642). # Apache Camel coding standards and style - [x] I checked that each commit in the pull request has a meaningful subject line and body. - [ ] I have run `mvn clean install -DskipTests` locally from root folder and I have committed all auto-generated changes. (I built the core modules and camel-core with the formatter and import-sort plugins and ran the related camel-core tests; no generated file changes. I did not run the full root build.) # AI-assisted contributions - [x] If this PR includes AI-generated code, commits have proper co-authorship attribution (e.g., `Co-authored-by` trailers) and the PR description identifies the AI tool used. This PR was prepared with Claude Code (Claude Opus 5.5). The commit carries a `Co-Authored-By` trailer. _Claude Code on behalf of allthingssecurity_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
