[ 
https://issues.apache.org/jira/browse/CAMEL-25613?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Claus Ibsen updated CAMEL-25613:
--------------------------------
    Fix Version/s: 4.24.0

> camel-jmx - consumer with reconnectOnConnectionFailure=true closes and 
> replaces a working connection every reconnectDelay for ever after a lost 
> connection
> ----------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-25613
>                 URL: https://issues.apache.org/jira/browse/CAMEL-25613
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-jmx
>            Reporter: shashank
>            Assignee: shashank
>            Priority: Major
>             Fix For: 4.24.0
>
>
> All of this is about a remote JMX consumer with 
> {{reconnectOnConnectionFailure=true}}. The option is off by default; it is 
> the option one sets so that the consumer survives an outage of the monitored 
> JVM, and the defect follows every such outage. {{JMXConsumer}}, lines at main 
> a7169d7c6ab6:
> * {{initNetworkConnection}} (139-154) closes the previous connector (142), 
> connects (150), adds the connection listener (151) and stores the connection 
> id (152);
> * {{ConnectionNotificationListener.handleNotification}} (190-207) ignores a 
> notification whose connection id is not the stored one (193) and calls 
> {{scheduleReconnect()}} for {{NOTIFS_LOST}}, {{CLOSED}} and {{FAILED}} 
> (196-201);
> * {{scheduleReconnect}} (213-226) schedules one task per call; the task calls 
> {{initNetworkConnection()}} and {{addNotificationListener()}}, and any 
> exception schedules it again (220).
> h3. 1. One lost connection schedules two reconnects, and the replacement 
> never stops
> The RMI connector of the JDK reports a connection that can no longer be used 
> with {{jmx.remote.connection.failed}} and then, from the close that follows, 
> with {{jmx.remote.connection.closed}}, both with the id of that connection 
> (observed with JDK 21; 
> {{RMIConnector.RMIClientCommunicatorAdmin.gotIOException}} sends the first 
> and calls {{close(true)}}, which sends the second). Both pass the id check 
> (193), so two reconnect tasks are scheduled.
> When the server is back, the first task connects and subscribes. The second 
> task then closes that working connector (142). Closing a connector whose 
> connection works sends {{jmx.remote.connection.closed}} with the id of that 
> connection to the connection listener, synchronously, inside {{close()}}; the 
> id is still the stored one (it is replaced at 152), so the listener schedules 
> a third task; and so on. Result: after an outage the consumer closes and 
> replaces a working connection every {{reconnectDelay}} (10 seconds by 
> default) for as long as the route runs, with a WARN "Lost JMX connection" 
> each time, and notifications that the MBean sends between a close and the 
> next subscription are lost.
> Observed in a local run with main's {{JMXConsumer}}, a JMX server in a second 
> JVM that was killed and started again on the same port, and 
> {{reconnectDelay=1}}: the connector delivered {{failed}} and {{closed}} with 
> the stored id from one thread, the consumer logged "Lost JMX connection" and 
> "Delaying JMX consumer reconnection" twice, and the new server accepted 16 
> connections from the consumer in the first 8 seconds (two every second).
> The "closed" notification of its own connector keeps the replacement going in 
> two more cases:
> * after one {{NOTIFS_LOST}} (the server dropped notifications for this 
> client; the connection itself is fine);
> * when an attempt connected but {{addNotificationListener}} failed, typically 
> with {{InstanceNotFoundException}}: the JMX connector of a restarting 
> application is usually reachable before the application has registered its 
> MBeans.
> h3. 2. The consumer does not follow the connection id after the connector 
> reconnected by itself
> When the server object is gone but the server still answers 
> ({{NoSuchObjectException}}, for example a connector server that was stopped 
> and started again inside a JVM that keeps running), the RMI connector tries 
> once to re-establish its connection. If that works it sends 
> {{jmx.remote.connection.opened}} with a new connection id, and it adds again 
> only the listeners whose MBean exists at that moment (read in the JDK 
> sources: {{RMIClientCommunicatorAdmin.doStart}} and 
> {{reconnectNotificationListeners}}). The consumer ignores that notification 
> and keeps the old id, so:
> * every later {{FAILED}} / {{CLOSED}} / {{NOTIFS_LOST}} carries the new id 
> and is ignored (193): the consumer never reconnects again, although 
> {{reconnectOnConnectionFailure=true}}, and nothing is logged;
> * when the MBean was not registered at the moment of the reconnection, the 
> consumer has a connection and no subscription, receives nothing, and logs 
> nothing.
> h3. 3. A notification after stop reconnects the stopped consumer
> The lost-connection branch of the listener (196-201) does not check whether 
> the consumer still runs. {{doStop}} shuts the scheduled executor down and 
> forgets it (266-269); a notification that is handled after that makes 
> {{getExecutor()}} (232-238) create a new executor, and the stopped consumer 
> connects again.
> h3. Reproduction
> New {{JMXConsumerReconnectTest}} (RMI registry and connector server in the 
> test JVM, set up like the existing {{JMXRobustRemoteConnectionTest}}; 
> {{reconnectDelay=1}}; the connector server counts the client connections it 
> accepts). On main all five tests fail:
> * {{FAILED}} and then {{CLOSED}} with the current connection id are given to 
> the connection listener: one new connection is expected and then none for 
> three seconds. On main: {{the consumer connected again although it was 
> subscribed on a working connection ==> expected: <false> but was: <true>}};
> * a {{NOTIFS_LOST}} notification for the current connection: one new 
> connection and then none. On main the same message;
> * the server is stopped and started again without the MBean, the test waits 
> for two new connections, registers the MBean and waits for a notification; 
> after that the connections must stop. On main: {{the consumer kept connecting 
> although it was subscribed on a working connection}} (when the connector 
> reported the loss) or {{the consumer did not keep trying to subscribe after 
> the server came back without the MBean}} (when the connector reconnected by 
> itself; which of the two happens depends on timing);
> * an {{OPENED}} notification with another connection id: the consumer must 
> open a connection of its own again. On main: {{the consumer did not subscribe 
> again after the connector had reconnected by itself ==> expected: <true> but 
> was: <false>}};
> * the route is stopped and {{FAILED}} with the last connection id is given to 
> the listener: no connection may follow. On main: {{the consumer connected 
> again although it was stopped ==> expected: <false> but was: <true>}}.
> h3. Proposed fix
> * {{scheduleReconnect}} schedules at most one reconnect at a time: a flag is 
> set with {{compareAndSet}} when a reconnect is scheduled, cleared when the 
> task begins (so that a failure reported during the attempt schedules the next 
> one) and reset in {{doStop}}. It schedules nothing when the consumer is not 
> allowed to run, and the task ends at once in that case.
> * {{initNetworkConnection}} removes the connection listener from the previous 
> connector before it closes it.
> * An {{OPENED}} notification with another id: the consumer stores the new id 
> and, with {{reconnectOnConnectionFailure=true}}, schedules its normal 
> reconnect, which subscribes again on a new connection (it cannot know whether 
> the connector kept its listener). A WARN line is logged; with 
> {{reconnectOnConnectionFailure=false}} only a WARN line that the consumer may 
> not be subscribed anymore.
> * The listener ignores a lost connection when the consumer is not allowed to 
> run.
> * The fields that the notification threads read are volatile.
> One consequence to know: when the connector re-established the connection by 
> itself and all listeners were added again, main keeps receiving without a gap 
> (and is deaf to later failures). With the fix the consumer replaces that 
> connection once, {{reconnectDelay}} later; notifications that the MBean sends 
> between the close and the new subscription are lost that one time.
> Not changed: with {{testConnectionOnStartup=false}} a delayed start that 
> connects but cannot add the listener still logs "An unrecoverable exception 
> has occurred" at ERROR and does not try again; {{doStop}} still fails when 
> the server cannot be reached; a reconnect attempt that is already connecting 
> when the consumer is stopped is not interrupted and may leave a connection 
> open; the WARN "Lost JMX connection" is still logged for each of the two 
> notifications.
> camel-jmx tests with the fix: 58 run, 0 failures, 0 skipped. With the fix and 
> the same second-JVM run as above the new server accepted one connection and 
> the next notification of the MBean was delivered. Not run: a JMX server on 
> another host.
> How it was found: a sweep of the reconnect code of the components, then a 
> small TLA+ model (server up or down, MBean registered, connector state, 
> stored id, pending and running reconnect, the "failed" and "closed" pair, 
> stop). For main TLC reports two pending reconnects after one lost connection 
> (4 steps), a replaced connection without a cause, "nothing pending, server 
> up, MBean registered, not subscribed", a run that never ends subscribed with 
> nothing pending, and a reconnect scheduled after stop. With the fix the 
> properties hold for three environment events and one stop (739 states). The 
> model is a sketch; the tests are the evidence.
> Affected: main (a7169d7c6ab6; tested). camel-4.23.x, camel-4.22.x, 
> camel-4.18.x and camel-4.14.x have the same id check, the same 
> {{initNetworkConnection}} and the same {{scheduleReconnect}} (read, not run).
> Duplicate check (2026-10-13): JIRA text "jmx reconnectOnConnectionFailure" 
> (CAMEL-5613), "JMXConsumer reconnect" (CAMEL-19527, tests only), "jmx 
> consumer connection notification" (nothing related); GitHub pull requests 
> "JMXConsumer", "camel-jmx reconnect": #2052 (2017, close the connector on 
> shutdown), none for this.
> _Filed with Claude Code on behalf of allthingssecurity._



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to