This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new c96869e6300f CAMEL-26080: camel-jgroups - do not close a channel that
the user gave to the component or an endpoint when Camel stops (#29201)
c96869e6300f is described below
commit c96869e6300f14dbc9f8e45c7588f6d3062ecf5d
Author: allthingssecurity <[email protected]>
AuthorDate: Thu Nov 5 12:34:34 2026 +0530
CAMEL-26080: camel-jgroups - do not close a channel that the user gave to
the component or an endpoint when Camel stops (#29201)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../camel/component/jgroups/JGroupsComponent.java | 32 ++++++------
.../camel/component/jgroups/JGroupsEndpoint.java | 2 +-
.../JGroupsComponentChannelEndpointsTest.java | 57 ++++++++++++++++++++--
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 9 ++++
4 files changed, 80 insertions(+), 20 deletions(-)
diff --git
a/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsComponent.java
b/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsComponent.java
index 264a3738c861..8ac4ed72cbe8 100644
---
a/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsComponent.java
+++
b/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsComponent.java
@@ -17,11 +17,9 @@
package org.apache.camel.component.jgroups;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -30,6 +28,8 @@ import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.annotations.Component;
import org.apache.camel.support.DefaultComponent;
import org.jgroups.JChannel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Component providing support for messages multicasted from- or to JGroups
channels ({@code org.jgroups.Channel}).
@@ -37,6 +37,8 @@ import org.jgroups.JChannel;
@Component("jgroups")
public class JGroupsComponent extends DefaultComponent {
+ private static final Logger LOG =
LoggerFactory.getLogger(JGroupsComponent.class);
+
@Metadata
private JChannel channel;
@Metadata
@@ -66,8 +68,6 @@ public class JGroupsComponent extends DefaultComponent {
// the connected producers and consumers of each channel that the
endpoints did not create (such as the channel of
// this component), as several endpoints use such a channel
private final Map<JChannel, Integer> sharedChannelUsers = new
IdentityHashMap<>();
- // the channels that the endpoints did not create and connected, closed
when this component stops
- private final Set<JChannel> sharedChannels = Collections.newSetFromMap(new
IdentityHashMap<>());
private final Lock sharedChannelLock = new ReentrantLock();
public JGroupsComponent() {
@@ -158,7 +158,6 @@ public class JGroupsComponent extends DefaultComponent {
try {
sharedChannel.connect(clusterName);
sharedChannelUsers.merge(sharedChannel, 1, Integer::sum);
- sharedChannels.add(sharedChannel);
} finally {
sharedChannelLock.unlock();
}
@@ -188,22 +187,25 @@ public class JGroupsComponent extends DefaultComponent {
@Override
protected void doStop() throws Exception {
super.doStop();
- List<JChannel> channels;
+ List<JChannel> connected;
sharedChannelLock.lock();
try {
- channels = new ArrayList<>(sharedChannels);
+ connected = new ArrayList<>(sharedChannelUsers.keySet());
sharedChannelUsers.clear();
- sharedChannels.clear();
} finally {
sharedChannelLock.unlock();
}
- // the channel of the component is used by all its endpoints, so it is
closed with the component, as is a
- // channel given to an endpoint
- if (channel != null && !channels.contains(channel)) {
- channels.add(channel);
- }
- for (JChannel sharedChannel : channels) {
- sharedChannel.close();
+ // the channels that the endpoints did not create (the channel of this
component, or a channel given to an
+ // endpoint) belong to the user, who closes them: a closed JChannel
cannot be connected again, so they are only
+ // disconnected, and the routes can connect them again when Camel
starts again. A channel is still connected
+ // here when a producer that Camel does not stop (such as one that the
application created and started itself)
+ // still uses it; it is disconnected, as the node must leave the
cluster when Camel stops
+ for (JChannel sharedChannel : connected) {
+ try {
+ sharedChannel.disconnect();
+ } catch (Exception e) {
+ LOG.warn("Error disconnecting the JGroups channel {}: {}",
sharedChannel, e.getMessage(), e);
+ }
}
}
diff --git
a/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsEndpoint.java
b/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsEndpoint.java
index 6dbd0d58c75a..f78e0edb68ed 100644
---
a/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsEndpoint.java
+++
b/components/camel-jgroups/src/main/java/org/apache/camel/component/jgroups/JGroupsEndpoint.java
@@ -190,7 +190,7 @@ public class JGroupsEndpoint extends DefaultEndpoint {
@Override
protected void doStop() throws Exception {
// a channel this endpoint did not create (such as the channel of the
component) is used by other endpoints,
- // and is closed by the component
+ // and is disconnected by its last user or by the component, but not
closed (it belongs to the user)
if (resolvedChannel != null && isOwnChannel()) {
connectLock.lock();
try {
diff --git
a/components/camel-jgroups/src/test/java/org/apache/camel/component/jgroups/JGroupsComponentChannelEndpointsTest.java
b/components/camel-jgroups/src/test/java/org/apache/camel/component/jgroups/JGroupsComponentChannelEndpointsTest.java
index 74498c81132c..6621ea677367 100644
---
a/components/camel-jgroups/src/test/java/org/apache/camel/component/jgroups/JGroupsComponentChannelEndpointsTest.java
+++
b/components/camel-jgroups/src/test/java/org/apache/camel/component/jgroups/JGroupsComponentChannelEndpointsTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.component.jgroups;
+import org.apache.camel.Producer;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.spi.Registry;
@@ -24,13 +25,16 @@ import org.jgroups.JChannel;
import org.jgroups.ObjectMessage;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* The channel of the component is used by every endpoint of the component: an
endpoint whose producer or consumer
* stops, or that is stopped itself, must not disconnect or close the channel
while the other endpoints use it. The
- * channel is closed when the CamelContext stops, as is a channel given to an
endpoint.
+ * channel and a channel given to an endpoint belong to the user: the
CamelContext disconnects them when it stops, but
+ * does not close them, so they can be used again when it starts again.
*/
class JGroupsComponentChannelEndpointsTest extends CamelTestSupport {
@@ -140,12 +144,57 @@ class JGroupsComponentChannelEndpointsTest extends
CamelTestSupport {
}
@Test
- void stoppingTheCamelContextClosesTheChannels() {
+ void stoppingTheCamelContextDisconnectsTheChannelsButDoesNotCloseThem() {
+ assertTrue(componentChannel.isConnected());
assertTrue(endpointChannel.isConnected());
context.stop();
- assertTrue(componentChannel.isClosed());
- assertTrue(endpointChannel.isClosed());
+ assertFalse(componentChannel.isConnected());
+ assertFalse(endpointChannel.isConnected());
+ assertFalse(componentChannel.isClosed());
+ assertFalse(endpointChannel.isClosed());
+ }
+
+ @Test
+ void
stoppingTheCamelContextDisconnectsTheChannelThatAProducerNotStoppedByCamelStillUses()
throws Exception {
+ // a producer that the application created and started itself: Camel
does not stop it
+ JGroupsEndpoint endpoint = context.getEndpoint("jgroups:" + CLUSTER,
JGroupsEndpoint.class);
+ Producer producer = endpoint.createProducer();
+ producer.start();
+
+ context.stop();
+
+ assertFalse(componentChannel.isConnected(), "the node leaves the
cluster when Camel stops");
+ assertFalse(componentChannel.isClosed());
+ // the late stop of that producer finds the channel disconnected
+ assertDoesNotThrow(producer::stop);
+ assertFalse(componentChannel.isClosed());
+ }
+
+ @Test
+ void theRoutesReceiveAgainAfterTheCamelContextIsStoppedAndStartedAgain()
throws Exception {
+ context.stop();
+ // the CamelContext of the test drops the component added in the route
builder and the beans of its registry
+ // when it stops: configure them again with the same channels, as an
application that keeps them has them
+ JGroupsComponent component = new JGroupsComponent();
+ component.setChannel(componentChannel);
+ context.addComponent("jgroups", component);
+ context.getRegistry().bind("endpointChannel", endpointChannel);
+ context.start();
+ assertTrue(componentChannel.isConnected());
+ assertTrue(endpointChannel.isConnected());
+
+ MockEndpoint mock = getMockEndpoint("mock:received");
+ mock.expectedBodiesReceived("after a restart");
+ MockEndpoint endpointChannelMock =
getMockEndpoint("mock:endpointChannel");
+ endpointChannelMock.expectedBodiesReceived("after a restart, endpoint
channel");
+
+ sender.send(new ObjectMessage(null, "after a restart"));
+ try (JChannel senderB = new JChannel()) {
+ senderB.connect(CLUSTER + "B");
+ senderB.send(new ObjectMessage(null, "after a restart, endpoint
channel"));
+ MockEndpoint.assertIsSatisfied(context);
+ }
}
}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index aa2c10c7a18d..9162438e1c97 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -3583,6 +3583,15 @@ content, so the members of the cluster receive a
`byte[]`. Any other body that i
`InvalidPayloadException` that says to convert it first, before it reaches
JGroups. An `onException` for
`NotSerializableException` must handle `InvalidPayloadException` instead.
+=== camel-jgroups - a channel given by the user is not closed when Camel stops
+
+When the CamelContext stops, the `jgroups` component no longer closes the
channel of the component (its `channel`
+option) and the channels given to an endpoint (`channel=#myChannel`). It
disconnects them, so the node leaves the
+cluster, and leaves them open, as a closed `JChannel` cannot be connected
again: the routes can now use them again when
+the CamelContext is started again. The channels that the endpoints create
(with `channelProperties` or the default
+configuration) are closed as before. Close a channel you give to Camel
yourself when the application no longer needs
+it, for example as a bean with a destroy method (`close`).
+
=== camel-jira - the producers wait for the answer of Jira
The `addComment`, `addIssueLink`, `addWorkLog`, `attach`, `deleteIssue`,
`transitionIssue` and `watchers` producers