shashank created CAMEL-26310:
--------------------------------
Summary: camel-core - a component created on first use can be used
by another thread before it has started (endpoints created on a component that
is still starting)
Key: CAMEL-26310
URL: https://issues.apache.org/jira/browse/CAMEL-26310
Project: Camel
Issue Type: Bug
Components: camel-core
Reporter: shashank
Assignee: shashank
A component that is created on first use after CamelContext has started is
started by the thread that created it, after {{components.computeIfAbsent}} has
returned ({{AbstractCamelContext.getComponent}}, camel-base-engine, main
c536c99caaaa). Another thread that asks for the same component meanwhile gets
it from the map before it has started ({{created}} is false for that thread, so
it does not start it), and {{doGetEndpoint}} goes on to
{{component.createEndpoint(...)}} and starts the new endpoint on a component
that is still starting (or not started at all yet).
A component that sets up in {{doStart}} what its endpoints use then fails for
that thread. For example {{VertxHttpComponent.doStart}} creates its managed
{{Vertx}}, and {{VertxHttpEndpoint.doStart}} creates its {{WebClient}} from
{{getComponent().getVertx()}}: the second thread gets {{WebClient.create(null,
...)}}. The first sends to a new scheme after startup are usually made from
several threads at once (a ProducerTemplate in a web service, a toD, recipient
list, routing slip or enrich with a scheme that is only known at runtime), so
they fail intermittently with {{ResolveEndpointFailedException}} or a
NullPointerException from the component.
CAMEL-14891 (3.3.0, toD) and CAMEL-20230 (4.4.0, enrich and pollEnrich) work
around it by resolving the component of a uri with a fixed scheme when the
route starts; the race in {{getComponent}} remains for the other paths (a
{{ProducerTemplate}}, recipient list, routing slip, or a toD / enrich whose
scheme is only known at runtime). It also happens while CamelContext is
starting: the consumers of the routes that have already started can send while
the later routes start.
Reproduced on main (latches, no sleeps): a component bound in the registry
whose {{doStart}} waits on a latch and creates a client its {{createEndpoint}}
uses; after context start, thread 1 calls {{context.getEndpoint("slow:a")}}
(creates and starts the component, held in {{doStart}}), then thread 2 calls
{{context.getEndpoint("slow:b")}}: thread 2 creates its endpoint while the
component is {{Starting}} and fails ({{ResolveEndpointFailedException}} ...
{{Cannot invoke "Object.hashCode()" because "this.client" is null}}). The same
from a {{StartupListener.onCamelContextStarting}} (CamelContext starting).
*Fix*: when {{getComponent}} returns a component that another thread has just
created and that has not started yet or is starting (CamelContext started or
starting, {{autoStart}}), it starts it with {{ServiceHelper.startService}}: the
service lock of the component makes it wait while the creating thread is in
{{doStart}}, and it is a no-op once started. If the creating thread failed to
start it, the waiting thread tries to start it once more and gets an exception
or a started component. A component that has been stopped (such as by
{{removeComponent}}) or that failed to start before is returned as it is, as
before. The component is still started outside {{computeIfAbsent}} (the
deadlock the comment there mentions).
*Tests*: new {{ComponentStartedBeforeUseTest}} (camel-core; latches, no sleeps;
a component whose {{doStart}} waits on a latch and then creates the client its
{{createEndpoint}} uses): {{testOtherThreadWaitsForTheComponentToStart}} (after
CamelContext has started) and
{{testOtherThreadWaitsForTheComponentToStartWhileCamelContextStarts}} (from
{{StartupListener.onCamelContextStarting}}) fail on main
({{ResolveEndpointFailedException}} caused by a null client) and pass with the
fix; {{testStoppedComponentIsNotStartedAgain}} is a control that passes on
both. camel-management component, endpoint and CamelContext tests: 94, 0
failures.
*How it was found*: by reading the check-then-act uses of {{ConcurrentHashMap}}
in camel-base-engine ({{computeIfAbsent}} with a side effect after it returns);
reproduced on HotSpot with the test above.
*Affected*: main, camel-4.22.x, camel-4.18.x and camel-4.14.x have the same
{{getComponent}} (read, not run).
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)