allthingssecurity opened a new pull request, #28753: URL: https://github.com/apache/camel/pull/28753
# Description [CAMEL-26104](https://issues.apache.org/jira/browse/CAMEL-26104) `ThrottlingExceptionRoutePolicy.addHalfOpenTimer` created a new `java.util.Timer`, and with it a new thread (`Timer-N`), every time the circuit opened or was kept open. These threads are not named or managed by Camel, the timers of a removed route kept running (with `keepOpen` or a `halfOpenHandler` that is not ready, each check scheduled the next one forever), and in camel-test-dst the check fired after `halfOpenAfter` of real time on a thread outside the harness, so a DST world could not see the circuit half open in virtual time (test-dst.adoc listed it as an escape). Changes (`ThrottlingExceptionRoutePolicy`, camel-support): - The half open check runs on a single thread scheduled executor that the policy creates through the `ExecutorServiceManager` the first time it schedules a check (`newSingleThreadScheduledExecutor(this, "ThrottlingExceptionRoutePolicy")`, so the thread is named `Camel (<context>) thread #N - ThrottlingExceptionRoutePolicy` and the pool is managed like the other thread pools). The same thread runs all later checks of the policy. - The semantics of CAMEL-26291 (#28467) stay as they are: one check at a time (scheduling a check cancels the one scheduled before, with `cancel(false)`, so a check that is running is not interrupted, like `Timer.cancel()`), and a check that finds the circuit already half opened by an exchange leaves it to the next exchange. `calculateState` and the state handling are unchanged. - CAMEL-26313 (#28574) is kept as it is: `addHalfOpenTimer(Route)` calls the protected `addHalfOpenTimer(Route, long delay)`, and that overload now schedules on the scheduler with its `delay`. So a check that finds `halfOpenAfter` raised while the circuit was open schedules the next check for the time that is left, on the same thread, and an exchange that is done while the circuit is open still schedules nothing. A subclass that overrides `addHalfOpenTimer(Route, long)` (as `ThrottlingExceptionRoutePolicyInflightWhileOpenTest` does) sees every check, as on main. - The policy is a service of the CamelContext, so removing its route does not stop it. `onRemove` now shuts the scheduler down when the last route of the policy is removed (the policy tracks the ids of its routes from `onInit`), and `doStop` shuts it down when the policy stops. A policy shared by two routes keeps its scheduler while one of them is left. A pending check is cancelled; a check that is running at that moment finishes, and it does not create a new scheduler (a flag, cleared again by `onInit`, `onStart` and `doStart`, so a route added again or a restarted context get checks again). - The scheduler and its flag have their own lock (`schedulerLock`), not the `lock` that a running check holds while it suspends or resumes the consumer, so `onRemove` and `doStop` do not wait for a running check. - `onInit` sets the CamelContext from the route when it was not injected. - A negative `halfOpenAfter` made `Timer.schedule` throw on main (the circuit stayed open and no check ran); the scheduler runs that check at once, as it does for 0. Docs: - test-dst.adoc (and its catalog copy): the half-open check is no longer listed as a wait outside virtual time, and the escape bullet is removed. - Upgrade guide 4.24: its own section, `camel-core - ThrottlingExceptionRoutePolicy runs the half open check on a thread pool of Camel`, after the sections main added since (the thread name and the thread pool are visible in thread dumps and JMX). Tests: - New `ThrottlingExceptionRoutePolicyHalfOpenSchedulerTest` (camel-core, 3 tests, latches and Awaitility, no sleeps): - `testHalfOpenChecksRunOnOneThreadOfThePolicy`: two openings, both checks half open the circuit on the same thread, named by the `ExecutorServiceManager`. Fails on main (two different `Timer-N` threads). - `testRemovingTheRouteEndsTheThreadOfTheHalfOpenCheck`: with a check pending (`halfOpenAfter` 60 s), stop and remove the route; the scheduler thread ends and the check does not run. Fails on main (no thread of the policy's scheduler). - `testRemovingOneOfTwoRoutesOfAPolicyKeepsItsHalfOpenCheck`: a policy on two routes, the circuit opened by the second; removing the first keeps the pending check, which then half opens the circuit; removing the second ends the thread. A control: it passes on main, and fails when `onRemove` shuts the scheduler down on every removal. - `ThrottlingExceptionRoutePolicyFailureWindowTest` (CAMEL-26291): the race test gated the check by the thread name `Timer-` and then joined the timer thread. It now gates on the new thread name, and waits for the end of the check by submitting an empty task to the policy's scheduler (one thread, so it runs after the check; the test captures the scheduler with a `DefaultThreadPoolFactory` subclass). The assertions are unchanged. - New `DstHalfOpenCheckTest` (camel-test-dst, strict mode, 2 tests): - `theCircuitIsHalfOpenedAfterHalfOpenAfterOfVirtualTime`: a failing exchange at 1 s opens the circuit (`halfOpenAfter` 30 s); after 30 s of virtual time it is still open, and the run then half opens it at exactly 31 000 ms, the scheduler shows in the trace as `thread pool created: ThrottlingExceptionRoutePolicy (1 threads)`, in well under a second of real time. - `aRaisedHalfOpenAfterHalfOpensTheCircuitWhenTheNewTimeIsUp` (with CAMEL-26313): the same opening at 1 s, and `halfOpenAfter` raised to 40 s at 20 s; the check at 31 s finds the time not up and schedules the next one for the 10 s that are left, so the circuit is half opened at exactly 41 000 ms. It fails when `addHalfOpenTimer(Route, long)` schedules `halfOpenAfter` instead of its `delay` (half opened at 71 000 ms) and when the check does not schedule again. - Both fail on main ("nothing can run any more while waiting for: the circuit is half opened": the `Timer` is outside the run). - On current main (`*Throttl*` test classes and all of camel-test-dst, one Maven run, `forkCount=1`): camel-core 76/0 (including main's `ThrottlingExceptionRoutePolicyRaiseHalfOpenAfterTest` and `ThrottlingExceptionRoutePolicyInflightWhileOpenTest`), camel-support 3/0, camel-test-dst 59/0. In camel-management, 10 of 12 passed; `ManagedThrottlingExceptionRoutePolicyTest` and `ManagedThrottlingInflightRoutePolicyTest` failed in that run (`expected: <1> but was: <8>`: they count the route MBeans of the whole JVM and saw those of the other throttler test classes). They fail the same way with main's `ThrottlingExceptionRoutePolicy` in the same set of test classes, and both pass on their own. The formatter and import-sort plugins ran and changed nothing. - `ThrottlingExceptionRoutePolicyHalfOpenSchedulerTest`, `ThrottlingExceptionRoutePolicyFailureWindowTest` and `DstHalfOpenCheckTest`: 20 runs each in one JVM, no failure (60, 60 and 40 tests). Merge note: the upgrade guide section is added after the last camel-core/camel-util section, where the queued Failover load balancer change (CAMEL-26328) adds its section too; whichever merges second has a conflict of two adjacent insertions. Backport: builds on CAMEL-26291 and the `TimeSource` of CAMEL-25911, both only on main, so main only. # 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-26104) filed for the change (usually before you start working on it): [CAMEL-26104](https://issues.apache.org/jira/browse/CAMEL-26104). # 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, the components on the camel-core test classpath, camel-management and camel-test-dst and ran the tests above, including the formatter and import-sort plugins. The test-dst.adoc change is copied to its catalog copy. 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]
