davsclaus opened a new pull request, #28902: URL: https://github.com/apache/camel/pull/28902
Fixes [CAMEL-26123](https://issues.apache.org/jira/browse/CAMEL-26123). ### 1. With `configFile`, a new `SolverManager` per exchange that is never closed **Was wrong:** with `configFile` and no `SolverManager` (neither the `solverManager` option nor the `CamelOptaPlannerSolverManager` header), `OptaPlannerProducer.getSolverManager` built a new `SolverConfig`, `SolverFactory` and `SolverManager` for every exchange and never closed it. Each one keeps a fixed thread pool, so every exchange left a solver thread behind. It also meant a `ProblemChange` sent to that endpoint went to a fresh `SolverManager` that was not solving anything. **Changed:** `OptaPlannerEndpoint.getOrCreateSolverManager()` creates the `SolverManager` once, on first use, and `doStop` closes it. It stays lazy because `configFile` defaults to the path of the URI, so it must not be loaded when a `SolverManager` comes from the option or the header. ### 2. Solvers and listeners in JVM-wide static maps **Was wrong:** `OptaPlannerEndpoint` kept `SOLVERS` and `SOLUTION_LISTENER` in static maps, the listeners keyed only by `problemId` (default `1`). A consumer in any `CamelContext` of the JVM got the best solutions of every producer with the same `problemId`. Also, stopping any endpoint terminated the solvers of all endpoints. **Changed:** the listeners are now kept on `OptaPlannerComponent` (one per `CamelContext`), still keyed by `problemId`. They stay at component level, not per endpoint, because the producer and consumer usually have different URIs (`async=true` on the producer only), so they are different endpoints. The listener sets are now `CopyOnWriteArraySet` (they were a plain `HashSet` iterated from solver threads while consumers add and remove). The `solvers` map is now an instance field. ### 3. Sync mode sent no events to consumers **Was wrong:** with `async=false` the producer called `solverManager.solve(problemId, body)` without a best solution listener, so a consumer on the same `problemId` got no events. **Changed:** the sync path now uses `solveAndListen` with the same `OptaplannerEventSupport` as the async path, and still waits for the final best solution. ### Tests Three new tests. Each one failed on `main` and passes with the fix: - `OptaPlannerConfigFileSolverManagerTest`: sends more sync exchanges than one `SolverManager` has solver threads. It then checks that the number of live `pool-N-thread-M` threads grows by at most `parallelSolverCount`, and that it goes back to the baseline after the context stops. On main it timed out because one thread leaked per exchange. - `OptaPlannerConsumerCamelContextTest`: a consumer in a second `CamelContext` with the same `problemId` gets no event. On main it got the events of the first context. - `OptaPlannerSyncConsumerTest`: a consumer gets the best solutions of a sync producer. On main it got 0 messages. The new tests use `solverConfigQuick.xml`, a copy of the test solver config with a 200 ms limit instead of 10 s. `mvn clean install -Psourcecheck` in the module: 10 tests, 0 failures. ### Docs and generated files - Component doc: the consumer section says that events come from sync and async producers of the same `CamelContext`. A new paragraph says how the `configFile` `SolverManager` is shared and closed. - `configFile` option description and the `consumerOut` body type description are updated. Regenerated `optaplanner.json`, the catalog copies (json + adoc) and the endpoint DSL. - Upgrade guide 4.24: new `camel-optaplanner` section. A shared `SolverManager` means that solving a problem while another one with the same `problemId` is still being solved on the same endpoint now fails with `The problemId (1) is already solving`. That was already the case with a `SolverManager` you configure. Before, each exchange had its own leaking manager, so concurrent solves of the same `problemId` happened to work. The section also covers the per-context consumers and the sync events. ### Not changed - Listeners are still keyed by `problemId` only, within a `CamelContext`. Two producers with different `SolverManager`s and the same `problemId` in one context still feed the same consumers. Keying by `problemName` as well would break routes whose consumer URI uses another name than the producer. - The unused protected `getOrCreateSolver` / `createSolver` / `getSolver` methods are kept (now per endpoint), so subclasses still compile. _Claude Code on behalf of Claus Ibsen (davsclaus)_ 🤖 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]
