shashank created CAMEL-26535:
--------------------------------
Summary: camel-kamelet - adding a route that uses a Kamelet can
deadlock with a toD creating the route of another Kamelet (model lock vs
Kamelet component lock)
Key: CAMEL-26535
URL: https://issues.apache.org/jira/browse/CAMEL-26535
Project: Camel
Issue Type: Bug
Components: camel-kamelet, camel-core
Reporter: shashank
Assignee: shashank
Found with a TLA+ model of the route-management locks of camel-core and
camel-kamelet (the lock of the model, the lock of the Kamelet component, the
route lock of the CamelContext), then reproduced on the real classes.
Adding a route that uses a Kamelet while the CamelContext runs can deadlock for
good with an exchange that sends to a new Kamelet endpoint (such as
{{toD("kamelet:b?x=${header.x}")}}). The two threads take the same two locks in
opposite orders:
* adding routes ({{addRoutes}}, a route reload, a route created from a template
at runtime with {{addRouteFromTemplate}} or templated routes, rest definitions
added at runtime): {{DefaultModel.addRouteDefinitions}} holds the lock of the
model while it creates and starts the new route; creating the route resolves
its {{kamelet:a}} endpoint, whose {{doInit}} creates the route of the Kamelet
in {{KameletComponent.LifecycleHandler.createRouteForEndpoint}}, which takes
the lock of the Kamelet component. Order: model, then Kamelet component.
* the toD resolves the new endpoint with no lock held, so
{{createRouteForEndpoint}} takes the lock of the Kamelet component first, and
{{addRouteFromKamelet}} then adds the route with {{addRouteDefinitions}}, which
takes the lock of the model. Order: Kamelet component, then model.
Short trace of the model:
{noformat}
1 toD thread : createRouteForEndpoint takes the Kamelet component lock
2 add thread : addRouteDefinitions takes the model lock
3 add thread : reifying the new route -> kamelet:a doInit -> wants the Kamelet
component lock
toD thread : addRouteFromKamelet -> addRouteDefinitions -> wants the model
lock -> deadlock
{noformat}
Both locks are taken with {{lock()}}, so both threads hang, and so does
everything that needs the lock of the model afterwards (adding and removing
routes, every later Kamelet route). A test with two hooks of the public API
({{ModelLifecycleStrategy}} and a route template converter) forces the
interleaving and {{ThreadMXBean.findDeadlockedThreads()}} reports the two
threads each waiting for the {{ReentrantLock}} the other holds. Without hooks,
adding 200 routes with new Kamelet endpoints while another thread sends 200
messages through toD to new Kamelet endpoints hung in each of 9 runs.
*Proposal:* the Kamelet component creates the route of a Kamelet holding the
lock of the model instead of a lock of its own (one lock order). Every path
through {{createRouteForEndpoint}} that creates a route already takes the lock
of the model inside it, so Kamelet route creation stays serialized. The model
does not expose its lock, so {{Model}} gets a new method {{getModelLock()}}
(upgrade guide entry for custom implementations of {{Model}}).
Related, not part of this: in the same model, {{stopRoute}} holds the route
lock of the CamelContext while the shutdown strategy waits for the in-flight
exchanges, and an in-flight exchange of that route that sends to a new Kamelet
endpoint needs that lock to start the Kamelet's route, so it waits until the
shutdown times out and the stop is forced (bounded by the shutdown timeout).
Affected: main, 4.23.x, 4.22.x, 4.18.x, 4.14.x (the same two lock orders; on
4.18.x and 4.14.x {{DefaultModel.addRouteDefinitions}} is {{synchronized}}).
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)