allthingssecurity opened a new pull request, #28710: URL: https://github.com/apache/camel/pull/28710
# Description [CAMEL-26310](https://issues.apache.org/jira/browse/CAMEL-26310) A component that is created on first use after CamelContext has started is started by the thread that created it. That happens after `components.computeIfAbsent` has returned in `AbstractCamelContext.getComponent`. A second thread that asks for the same component in the meantime gets it from the map before it has started. `created` is false for that thread, so it does not start the component, and `doGetEndpoint` goes on to create and start an endpoint on a component that is still starting. A component that sets up in `doStart` what its endpoints use then fails for that second thread. For example, `VertxHttpComponent.doStart` creates its managed `Vertx`, and `VertxHttpEndpoint.doStart` creates its `WebClient` from `getComponent().getVertx()`, which is still null for the second thread. The first sends to a scheme after startup often come from several threads at once: a `ProducerTemplate` in a web service, or a toD, recipient list, routing slip or enrich whose scheme is only known at runtime. These sends then fail now and then with `ResolveEndpointFailedException` or an exception from the component. CAMEL-14891 (toD, 3.3.0) and CAMEL-20230 (enrich and pollEnrich, 4.4.0) work around it for a uri with a fixed scheme by resolving the component when the route starts. The race in `getComponent` is still there for the other paths. It also happens while CamelContext is starting, as the consumers of the routes that have already started can send while the later routes start. Changes: - When `getComponent` returns a component that another thread has just created and that has not started yet or is starting, and CamelContext is started or starting, it calls `ServiceHelper.startService(component)`. The service lock of the component makes this thread wait while the creating thread is in `doStart`, and once the component is started the call does nothing. If the creating thread fails to start it, the waiting thread tries once more, so it gets either an exception or a started component. The component is still started outside `computeIfAbsent`, so this does not bring back the deadlock that the comment there warns about. - A component that has been stopped (for example by `removeComponent` while another thread still holds it) or that failed to start earlier is returned as it is, as before. Nothing changes with `autoStart=false`. - The check runs only when the component was not created by this call: a type check and a few reads of the status field. - A thread now waits for a component that another thread is starting. A component whose `doStart` takes the CamelContext lock (starts or stops routes), or calls `getComponent` for another component whose `doStart` asks for the first one, could deadlock with a thread that waits for it. A search of the `doInit` and `doStart` methods of the components in the repository found none that does (the only `getComponent` call there is `ClassComponent.doInit` asking for `bean`, which does not ask back). Tests: - New `ComponentStartedBeforeUseTest` (camel-core; latches, no sleeps). A component bound in the registry has a `doStart` that waits on a latch and then creates a client, which its `createEndpoint` uses. - `testOtherThreadWaitsForTheComponentToStart`: after CamelContext has started, one thread resolves `slow:a`: it creates the component and is held in `doStart`. A second thread then resolves `slow:b`. The test waits until that thread has either created its endpoint or is blocked, then releases the first thread. On main the second thread creates its endpoint while the component is `Starting` and fails (`ResolveEndpointFailedException: ... Cannot invoke "Object.hashCode()" because "this.client" is null`). With the change it waits, and both endpoints are created on a started component. - `testOtherThreadWaitsForTheComponentToStartWhileCamelContextStarts`: the same from a `StartupListener.onCamelContextStarting`, while CamelContext is starting. Fails on main the same way. - `testStoppedComponentIsNotStartedAgain`: a control that also passes on main: a component that was started and then stopped is returned by `getComponent` without being started again. - The first two fail on main (3 of 3 surefire runs), all three pass with the change. - camel-core, the `org.apache.camel.impl` and `org.apache.camel.component` tests plus `*Component*Test`, `*Endpoint*Test`, `*Dynamic*Test`, `*ProducerTemplate*Test`, `*StartupListener*Test` and `*CamelContext*Test`: 2883 tests, 11 failures, 27 skipped. The failures are in `RouteChecksumTest` (7), `FileProducerChecksumOfWrittenFileTest` (3) and `FilePollEnrichDynamicFileNameTest` (1). The same 11 fail the same way with the `AbstractCamelContext` of main in this offline environment, which uses older snapshot jars of the modules outside the core reactor (camel-file among them). camel-management, `*Component*Test`, `*Endpoint*Test` and `*CamelContext*Test`: 94 tests, 0 failures. The formatter and import-sort plugins ran. How it was found: while reading the check-then-act uses of `ConcurrentHashMap` in camel-base-engine (`computeIfAbsent` with a side effect after it returns), and reproduced on HotSpot with the test above. # 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-26310) filed for the change (usually before you start working on it): [CAMEL-26310](https://issues.apache.org/jira/browse/CAMEL-26310). # 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 ran the tests above, including the formatter and import-sort plugins. 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]
