shashank created CAMEL-26541:
--------------------------------
Summary: camel-core - interceptSendToEndpoint: an endpoint created
while a route starts or stops gets the wrong interceptors
Key: CAMEL-26541
URL: https://issues.apache.org/jira/browse/CAMEL-26541
Project: Camel
Issue Type: Bug
Components: camel-core
Reporter: shashank
Assignee: shashank
Found with a TLA+ model of {{InterceptSendToEndpointManager}} (CAMEL-25140): a
thread that creates an endpoint (a {{toD}} to an endpoint that is not in the
registry yet) and the lifecycle thread that starts, stops, removes and adds
again a route with {{interceptSendToEndpoint}}. Then reproduced on the real
classes.
Since CAMEL-25140 a route registers its interceptor when it starts and
unregisters it when it stops. {{register}} and {{unregister}} run under the
manager's lock and update the wrapped endpoints that are *in the endpoint
registry*. A new endpoint gets the interceptors in {{registerEndpoint}} (the
endpoint strategy), without that lock, and
{{AbstractCamelContext.addEndpointToRegistry}} puts it in the registry only
after the strategies returned. A route that starts or stops between the two
steps does not see the new endpoint:
* *start*: the running route's interceptor is missing on the endpoint, so its
{{interceptSendToEndpoint}} does not apply to it.
* *stop or remove*: the stopped route's interceptor stays on the endpoint. It
runs on the stopped route's error handler, so the sends that use it fail with
{{RouteStoppingException}} ("The exchange cannot continue: its route is being
stopped or reloaded"): the sends of routes without an interceptor of their own
and of a {{ProducerTemplate}}, including the send that created the endpoint.
After a stop this lasts until the route starts again. After a remove it stays
when the route is added again, and the new route's own sends fail too (the old
interceptor has the same route id and runs first).
Short trace (start):
{noformat}
toD thread: registerEndpoint: wrapper created, registrations = [] (no
interceptor added)
lifecycle: startRoute -> register: registrations = [r]; scan registry
(endpoint not there)
toD thread: endpoints.put(wrapper) -> route
r running, endpoint not intercepted
{noformat}
Reproducer (the test pauses the thread that creates the endpoint just before
{{endpoints.put}}, with an endpoint registry that waits on a latch, and starts
or stops the route meanwhile):
{code:java}
// builder 1
interceptSendToEndpoint("mock:dyn*").to("mock:intercepted");
from("direct:start").routeId("start").toD("mock:${header.name}");
// builder 2, no interceptor
from("direct:other").routeId("other").toD("mock:${header.name}");
// thread A: template.sendBodyAndHeader("direct:other", "Hello", "name",
"dyn1") -> paused before endpoints.put
// main: routeController.startRoute("start") (it was stopped before)
// release A
template.sendBodyAndHeader("direct:start", "Bye", "name", "dyn1"); //
mock:intercepted gets nothing
{code}
On main (e05c5485d507) the start, stop and remove-and-add-again cases fail; the
same steps one after the other pass. Without the latch, a stress loop (new
{{toD}} endpoints while the route starts and stops) hit it on main too. The
window is small, but route reloads and route policies stop and start routes
under traffic, and the result stays (until the endpoint is created again or,
for a missing interceptor, until a route with {{interceptSendToEndpoint}}
starts).
*Proposal*
* {{registerEndpoint}} wraps the endpoint under the manager's lock (a read
lock; {{addPattern}}, {{register}} and {{unregister}} take the write lock), and
the manager remembers the wrappers it returns (weak keys, as a {{toD}} endpoint
can be evicted from the registry), so {{register}} and {{unregister}} also
update the wrappers that are not in the registry yet. Endpoints that match no
pattern take no lock. This keeps the CAMEL-25140 design; the model holds with
it.
* Not covered (it would need the registry put under the same lock): a route
added at runtime with a new uri pattern, while a matching endpoint is being
created, can leave that endpoint unwrapped until the next start of a route with
{{interceptSendToEndpoint}}.
Affects: 4.23.0 (CAMEL-25140), main. CAMEL-25141 would narrow the stop/remove
case to the route's own sends, not remove it.
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)