shashank created CAMEL-26577:
--------------------------------
Summary: camel-core - RebalancingCamelClusterService: every
rebalancing pass takes a reference on the views and never releases it, so the
view of a removed clustered route keeps the leadership and a released view is
started again
Key: CAMEL-26577
URL: https://issues.apache.org/jira/browse/CAMEL-26577
Project: Camel
Issue Type: Bug
Components: camel-kubernetes, camel-core
Reporter: shashank
Assignee: shashank
{{RebalancingCamelClusterService}} (camel-support) wraps a
{{CamelPreemptiveClusterService}} (the Kubernetes cluster service) and runs
{{reconcile()}} on its own scheduled thread every period. Each pass looks at
every namespace through {{delegate.getView(namespace)}}, in {{members()}},
{{owned()}} and {{setDisabled()}}, several times per pass, and never calls
{{releaseView}}.
{{AbstractCamelClusterService.getView}} is not a plain lookup: it takes a
reference on the view (and starts it on the first one), and {{releaseView}}
stops the view when the last reference is released
({{CamelClusterService.releaseView}}: "Release a view if it has no references";
CAMEL-25089 made sure a released view is not started again by a restart of the
service). So with the rebalancing service:
* a namespace whose clustered route is removed keeps its view running: the
{{ClusteredRoutePolicy}} releases its reference, but the rebalancer holds one
more per pass. This node stays in the election, and when it is the leader of
that namespace no other node runs the route (the rebalancer even counts it as
one of its partitions);
* a view that was already released (stopped) is started again by the next pass,
as its {{getView}} takes the first reference again;
* {{members()}} then also compares the members of views nobody uses.
Found with a TLA+ model of the reference count of one namespace with three
actors (the route policy retaining at onInit and releasing at onRemove, the
rebalancing thread, the service stop/start). Property: once the last route
using the namespace is removed and the rebalancer is between passes, the view
is stopped and holds no reference. Short trace on main: route added (count 1,
view started) -> route removed (count 0, view stopped) -> reconcile pass
{{getView}} (count 1, view started again) -> pass ends, nothing released -> the
view keeps running with no user. The same with the pass before the removal: the
count never drops to 0. The fixed model (look only at views in use, release
every reference taken) holds; both paths are reachable (witnesses).
Reproduced on the real classes with {{DefaultCamelContext}}, a
{{ClusteredRoutePolicy}} route and a test {{CamelPreemptiveClusterService}}
extending {{AbstractCamelClusterService}} (no Kubernetes): after
{{removeRoute}} the view is still {{Started}}; a view released through the API
is {{Started}} again after one {{reconcile()}}. Both tests are deterministic
(the passes are run explicitly, the period is one hour).
Proposed fix: the rebalancer takes a reference only on the views that are in
use (a package-private lookup of {{AbstractCamelClusterService}} that does not
create or start a view) and releases each one in a {{finally}}; namespaces with
no user are not partitions to balance. Delegates that do not extend
{{AbstractCamelClusterService}} get {{getView}}/{{releaseView}} pairs.
Not part of this issue (minor): {{doStop}} does not wait for a pass in flight,
which reschedules itself on the {{serializedExecutor}} field, so after a quick
stop/start one more chain of passes runs on the new executor.
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)