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]

Reply via email to