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 eefa74fb8a2f CAMEL-25072: camel-management - EIP and service MBeans:
fix the remaining follow-ups from the deep review (#27074)
eefa74fb8a2f is described below
commit eefa74fb8a2f75fda0d6c4eaf89823a4ea053880
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Sep 29 14:00:34 2026 +0200
CAMEL-25072: camel-management - EIP and service MBeans: fix the remaining
follow-ups from the deep review (#27074)
* CAMEL-25072: camel-management - EIP and service MBeans: fix the remaining
follow-ups from the deep review
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
.../engine/DefaultEndpointServiceRegistry.java | 10 +-
.../api/management/mbean/CamelOpenMBeanTypes.java | 2 +-
.../mbean/ManagedEndpointServiceRegistry.java | 4 +
.../management/mbean/ManagedEventNotifier.java | 34 +++---
.../camel/management/mbean/StatisticMaximum.java | 12 +-
.../camel/management/mbean/StatisticMinimum.java | 12 +-
.../JmxNotificationEventNotifierTest.java | 25 +++++
.../ManagedEndpointServiceRegistryRuntimeTest.java | 37 ++++++
.../ManagedEndpointServiceRegistryTest.java | 125 +++++++++++++++++++++
.../mbean/StatisticMinimumMaximumTest.java | 95 ++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 7 ++
11 files changed, 330 insertions(+), 33 deletions(-)
diff --git
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultEndpointServiceRegistry.java
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultEndpointServiceRegistry.java
index c949d02ed401..5a7fe24c1444 100644
---
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultEndpointServiceRegistry.java
+++
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultEndpointServiceRegistry.java
@@ -83,7 +83,8 @@ public class DefaultEndpointServiceRegistry extends
ServiceSupport implements En
hosted = dc.isHostedService();
routeId = dc.getRouteId();
}
- var stat = findStats(endpoint.getEndpointUri(), dir);
+ // the stats of the route of the consumer, as several routes can
consume from the same endpoint
+ var stat = findStats(endpoint.getEndpointUri(), dir, routeId);
long hits = 0;
if (stat.isPresent()) {
var s = stat.get();
@@ -92,7 +93,7 @@ public class DefaultEndpointServiceRegistry extends
ServiceSupport implements En
}
if ("out".equals(dir) && stat.isEmpty()) {
// no OUT stat, then the endpoint may be used only for IN
- stat = findStats(endpoint.getEndpointUri(), "in");
+ stat = findStats(endpoint.getEndpointUri(), "in", null);
if (stat.isPresent()) {
return null;
}
@@ -115,12 +116,13 @@ public class DefaultEndpointServiceRegistry extends
ServiceSupport implements En
return size;
}
- private Optional<RuntimeEndpointRegistry.Statistic> findStats(String uri,
String direction) {
+ private Optional<RuntimeEndpointRegistry.Statistic> findStats(String uri,
String direction, String routeId) {
if (camelContext.getRuntimeEndpointRegistry() == null) {
return Optional.empty();
}
return
camelContext.getRuntimeEndpointRegistry().getEndpointStatistics().stream()
- .filter(s -> uri.equals(s.getUri()) && (direction == null ||
s.getDirection().equals(direction)))
+ .filter(s -> uri.equals(s.getUri()) && (direction == null ||
s.getDirection().equals(direction))
+ && (routeId == null || routeId.equals(s.getRouteId())))
.findFirst();
}
diff --git
a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/CamelOpenMBeanTypes.java
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/CamelOpenMBeanTypes.java
index c1a686c01abf..5d5a65249025 100644
---
a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/CamelOpenMBeanTypes.java
+++
b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/CamelOpenMBeanTypes.java
@@ -34,7 +34,7 @@ public final class CamelOpenMBeanTypes {
CompositeType ct = listEndpointServicesCompositeType();
return new TabularType(
"listEndpointServices", "Lists all the endpoint services in
the registry", ct,
- new String[] { "component", "dir", "serviceUrl", "endpointUri"
});
+ new String[] { "component", "dir", "serviceUrl",
"endpointUri", "routeId" });
}
public static CompositeType listEndpointServicesCompositeType() throws
OpenDataException {
diff --git
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedEndpointServiceRegistry.java
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedEndpointServiceRegistry.java
index 56428490709f..b933dfe8aeab 100644
---
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedEndpointServiceRegistry.java
+++
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedEndpointServiceRegistry.java
@@ -88,6 +88,10 @@ public class ManagedEndpointServiceRegistry extends
ManagedService implements Ma
m.forEach((k, v) -> sj.add(k + "=" + v));
metadata = sj.toString();
}
+ if (answer.containsKey(new Object[] { component, dir,
serviceUrl, endpointUri, routeId })) {
+ // endpoints of the same route that only differ in a
secret are the same uri when sanitized
+ continue;
+ }
CompositeData data = new CompositeDataSupport(
ct,
diff --git
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedEventNotifier.java
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedEventNotifier.java
index 1ccfe2c12dfc..e8383d672990 100644
---
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedEventNotifier.java
+++
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedEventNotifier.java
@@ -20,10 +20,12 @@ import java.util.ArrayList;
import java.util.List;
import javax.management.MBeanNotificationInfo;
+import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import org.apache.camel.CamelContext;
import org.apache.camel.api.management.JmxNotificationBroadcasterAware;
+import org.apache.camel.spi.CamelEvent;
import org.apache.camel.spi.EventNotifier;
import org.apache.camel.spi.ManagementStrategy;
@@ -163,26 +165,22 @@ public class ManagedEventNotifier extends
NotificationBroadcasterSupport impleme
@Override
public MBeanNotificationInfo[] getNotificationInfo() {
- // all the class names in the event package
- String[] names = {
- "CamelContextStartedEvent", "CamelContextStartingEvent",
"CamelContextStartupFailureEvent",
- "CamelContextStopFailureEvent", "CamelContextStoppedEvent",
"CamelContextStoppingEvent",
- "CamelContextSuspendingEvent", "CamelContextSuspendedEvent",
"CamelContextResumingEvent",
- "CamelContextResumedEvent",
- "CamelContextResumeFailureEvent", "ExchangeCompletedEvent",
"ExchangeCreatedEvent", "ExchangeFailedEvent",
- "ExchangeFailureHandledEvent", "ExchangeRedeliveryEvents",
"ExchangeSendingEvent", "ExchangeSentEvent",
- "RouteStartedEvent",
- "RouteStoppedEvent", "ServiceStartupFailureEvent",
"ServiceStopFailureEvent",
- "StepStartedEvent", "StepCompletedEvent", "StepFailedEvent" };
-
+ // JmxNotificationEventNotifier uses the simple class name of the
event as the notification type
List<MBeanNotificationInfo> infos = new ArrayList<>();
- for (String name : names) {
- MBeanNotificationInfo info = new MBeanNotificationInfo(
- new String[] { "org.apache.camel.management.event" },
- "org.apache.camel.management.event." + name, "The event "
+ name + " occurred");
- infos.add(info);
+ for (CamelEvent.Type type : CamelEvent.Type.values()) {
+ if (type == CamelEvent.Type.Custom) {
+ // custom events have their own class names
+ continue;
+ }
+ String name = type.name();
+ if (name.startsWith("Routes")) {
+ // the routes events of the CamelContext
+ name = "CamelContext" + name;
+ }
+ name = name + "Event";
+ infos.add(new MBeanNotificationInfo(
+ new String[] { name }, Notification.class.getName(), "The
event " + name + " occurred"));
}
-
return infos.toArray(new MBeanNotificationInfo[0]);
}
diff --git
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/StatisticMaximum.java
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/StatisticMaximum.java
index 01aef94189f0..39e752c048d7 100644
---
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/StatisticMaximum.java
+++
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/StatisticMaximum.java
@@ -24,12 +24,14 @@ public class StatisticMaximum extends Statistic {
@Override
public void updateValue(long newValue) {
- // its okay its not 100% thread safe (these jmx counters are not
guaranteed to be accurate for min/max values)
- // if we use the atomic operation updateAndGet then the JVM creates a
new lambda per call which creates a new object
- // in the JVM and causes higher memory footprint
+ // compare and set in a loop (and not updateAndGet, which creates a
new lambda per call), so a concurrent
+ // update cannot be lost
long current = value.get();
- if (current == -1 || current < newValue) {
- value.set(newValue);
+ while (current == -1 || current < newValue) {
+ if (value.compareAndSet(current, newValue)) {
+ return;
+ }
+ current = value.get();
}
}
diff --git
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/StatisticMinimum.java
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/StatisticMinimum.java
index 3e7872a07c9b..abacb4cbcd38 100644
---
a/core/camel-management/src/main/java/org/apache/camel/management/mbean/StatisticMinimum.java
+++
b/core/camel-management/src/main/java/org/apache/camel/management/mbean/StatisticMinimum.java
@@ -24,12 +24,14 @@ public class StatisticMinimum extends Statistic {
@Override
public void updateValue(long newValue) {
- // its okay its not 100% thread safe (these jmx counters are not
guaranteed to be accurate for min/max values)
- // if we use the atomic operation updateAndGet then the JVM creates a
new lambda per call which creates a new object
- // in the JVM and causes higher memory footprint
+ // compare and set in a loop (and not updateAndGet, which creates a
new lambda per call), so a concurrent
+ // update cannot be lost
long current = value.get();
- if (current == -1 || current > newValue) {
- value.set(newValue);
+ while (current == -1 || current > newValue) {
+ if (value.compareAndSet(current, newValue)) {
+ return;
+ }
+ current = value.get();
}
}
diff --git
a/core/camel-management/src/test/java/org/apache/camel/management/JmxNotificationEventNotifierTest.java
b/core/camel-management/src/test/java/org/apache/camel/management/JmxNotificationEventNotifierTest.java
index 1785833dc61b..278a5ec7f86d 100644
---
a/core/camel-management/src/test/java/org/apache/camel/management/JmxNotificationEventNotifierTest.java
+++
b/core/camel-management/src/test/java/org/apache/camel/management/JmxNotificationEventNotifierTest.java
@@ -16,6 +16,11 @@
*/
package org.apache.camel.management;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.management.MBeanNotificationInfo;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
@@ -30,7 +35,9 @@ import org.junit.jupiter.api.condition.OS;
import static
org.apache.camel.management.DefaultManagementObjectNameStrategy.TYPE_EVENT_NOTIFIER;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
@DisabledOnOs(OS.AIX)
public class JmxNotificationEventNotifierTest extends ManagementTestSupport {
@@ -77,6 +84,18 @@ public class JmxNotificationEventNotifierTest extends
ManagementTestSupport {
assertEquals(8, listener.getEventCounter(), "Get a wrong number of
events");
+ // the notification types that were sent are advertised by the MBean
+ Set<String> advertised = new HashSet<>();
+ for (MBeanNotificationInfo info :
context.getManagementStrategy().getManagementAgent().getMBeanServer()
+ .getMBeanInfo(on).getNotifications()) {
+ assertEquals(Notification.class.getName(), info.getName());
+ advertised.addAll(Arrays.asList(info.getNotifTypes()));
+ }
+ assertFalse(listener.getTypes().isEmpty());
+ for (String type : listener.getTypes()) {
+ assertTrue(advertised.contains(type), "Notification type " + type
+ " should be advertised");
+ }
+
context.stop();
}
@@ -117,11 +136,17 @@ public class JmxNotificationEventNotifierTest extends
ManagementTestSupport {
private class MyNotificationListener implements NotificationListener {
private int eventCounter;
+ private final Set<String> types = new HashSet<>();
@Override
public void handleNotification(Notification notification, Object
handback) {
log.debug("Get the notification : {}", notification);
eventCounter++;
+ types.add(notification.getType());
+ }
+
+ public Set<String> getTypes() {
+ return types;
}
public int getEventCounter() {
diff --git
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedEndpointServiceRegistryRuntimeTest.java
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedEndpointServiceRegistryRuntimeTest.java
new file mode 100644
index 000000000000..0d4d545af397
--- /dev/null
+++
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedEndpointServiceRegistryRuntimeTest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.management;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.impl.engine.DefaultRuntimeEndpointRegistry;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+/**
+ * The same as {@link ManagedEndpointServiceRegistryTest} with the runtime
endpoint registry enabled, which gives the
+ * hits and route of each endpoint service.
+ */
+@DisabledOnOs(OS.AIX)
+public class ManagedEndpointServiceRegistryRuntimeTest extends
ManagedEndpointServiceRegistryTest {
+
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+ CamelContext context = super.createCamelContext();
+ context.setRuntimeEndpointRegistry(new
DefaultRuntimeEndpointRegistry());
+ return context;
+ }
+}
diff --git
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedEndpointServiceRegistryTest.java
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedEndpointServiceRegistryTest.java
new file mode 100644
index 000000000000..836644a4d50c
--- /dev/null
+++
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedEndpointServiceRegistryTest.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.management;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.TabularData;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Consumer;
+import org.apache.camel.Endpoint;
+import org.apache.camel.MultipleConsumersSupport;
+import org.apache.camel.Processor;
+import org.apache.camel.Producer;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.EndpointServiceLocation;
+import org.apache.camel.support.DefaultComponent;
+import org.apache.camel.support.DefaultConsumer;
+import org.apache.camel.support.DefaultEndpoint;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+import static
org.apache.camel.management.DefaultManagementObjectNameStrategy.TYPE_SERVICE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@DisabledOnOs(OS.AIX)
+public class ManagedEndpointServiceRegistryTest extends ManagementTestSupport {
+
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+ CamelContext context = super.createCamelContext();
+ context.addComponent("service", new DefaultComponent() {
+ @Override
+ protected Endpoint createEndpoint(String uri, String remaining,
Map<String, Object> parameters) {
+ return new ServiceEndpoint(uri, this);
+ }
+ });
+ // the registry is created on first use, so create it before the
context is started to have it managed
+ context.getCamelContextExtension().getEndpointServiceRegistry();
+ return context;
+ }
+
+ @Test
+ public void testTwoRoutesConsumeSameService() throws Exception {
+ MBeanServer mbeanServer = getMBeanServer();
+ ObjectName on = getCamelObjectName(TYPE_SERVICE,
"DefaultEndpointServiceRegistry");
+
+ // both routes consume the same service, which failed with
KeyAlreadyExistsException
+ TabularData data = (TabularData) mbeanServer.invoke(on,
"listEndpointServices", null, null);
+
+ List<String> routeIds = new ArrayList<>();
+ for (Object row : data.values()) {
+ CompositeData cd = (CompositeData) row;
+ assertEquals("localhost:8080", cd.get("serviceUrl"));
+ if ("in".equals(cd.get("dir"))) {
+ routeIds.add((String) cd.get("routeId"));
+ }
+ }
+ routeIds.sort(null);
+ assertEquals(List.of("a", "b"), routeIds);
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("service:foo").routeId("a").to("mock:a");
+ from("service:foo").routeId("b").to("mock:b");
+ }
+ };
+ }
+
+ private static class ServiceEndpoint extends DefaultEndpoint implements
EndpointServiceLocation, MultipleConsumersSupport {
+
+ ServiceEndpoint(String uri, DefaultComponent component) {
+ super(uri, component);
+ }
+
+ @Override
+ public String getServiceUrl() {
+ return "localhost:8080";
+ }
+
+ @Override
+ public String getServiceProtocol() {
+ return "tcp";
+ }
+
+ @Override
+ public boolean isMultipleConsumersSupported() {
+ return true;
+ }
+
+ @Override
+ public Producer createProducer() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Consumer createConsumer(Processor processor) {
+ return new DefaultConsumer(this, processor);
+ }
+ }
+}
diff --git
a/core/camel-management/src/test/java/org/apache/camel/management/mbean/StatisticMinimumMaximumTest.java
b/core/camel-management/src/test/java/org/apache/camel/management/mbean/StatisticMinimumMaximumTest.java
new file mode 100644
index 000000000000..f9994aeecc4a
--- /dev/null
+++
b/core/camel-management/src/test/java/org/apache/camel/management/mbean/StatisticMinimumMaximumTest.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.management.mbean;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.RepeatedTest;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class StatisticMinimumMaximumTest {
+
+ private static final int THREADS = 8;
+ private static final int UPDATES = 20000;
+
+ @Test
+ public void testMinimumMaximum() {
+ StatisticMinimum min = new StatisticMinimum();
+ StatisticMaximum max = new StatisticMaximum();
+ assertFalse(min.isUpdated());
+ assertFalse(max.isUpdated());
+ assertEquals(0, min.getValue());
+ assertEquals(0, max.getValue());
+
+ for (long v : new long[] { 5, 3, 8, 3, 6 }) {
+ min.updateValue(v);
+ max.updateValue(v);
+ }
+ assertTrue(min.isUpdated());
+ assertEquals(3, min.getValue());
+ assertEquals(8, max.getValue());
+
+ min.reset();
+ max.reset();
+ assertFalse(min.isUpdated());
+ assertFalse(max.isUpdated());
+ }
+
+ @RepeatedTest(5)
+ public void testConcurrentUpdates() throws Exception {
+ StatisticMinimum min = new StatisticMinimum();
+ StatisticMaximum max = new StatisticMaximum();
+
+ // each thread updates with its own range of values, in the order that
makes each update a new min and max
+ ExecutorService executor = Executors.newFixedThreadPool(THREADS);
+ try {
+ CountDownLatch start = new CountDownLatch(1);
+ List<Future<?>> futures = new ArrayList<>();
+ for (int t = 0; t < THREADS; t++) {
+ final int thread = t;
+ futures.add(executor.submit(() -> {
+ start.await();
+ for (int i = 0; i < UPDATES; i++) {
+ max.updateValue((long) i * THREADS + thread + 1);
+ min.updateValue((long) (UPDATES - i) * THREADS -
thread);
+ }
+ return null;
+ }));
+ }
+ start.countDown();
+ for (Future<?> future : futures) {
+ future.get(30, TimeUnit.SECONDS);
+ }
+ } finally {
+ executor.shutdownNow();
+ }
+
+ // a lost update would leave a value from another thread that is not
the real min or max
+ assertEquals((long) UPDATES * THREADS, max.getValue());
+ assertEquals(1L, min.getValue());
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index bbe263fc2f08..370e1e134d0f 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -564,6 +564,13 @@ Some tabular data returned by the JMX MBeans had a key
that was not unique, so t
`KeyAlreadyExistsException`. The tabular data of the Choice EIP and doTry EIP
`extendedInformation` and of
`listTasks` of the task manager registry now have an `index` item as their
key, the exchange factories of `listStatistics`
are keyed by `url` and `routeId`, and endpoints that only differ in a secret
(which is masked) are shown once.
+The `listEndpointServices` operation of the endpoint service registry is keyed
by `routeId` as well, so two routes
+that consume from the same service are both listed.
+
+The notification types advertised by the event notifier MBean
(`getNotificationInfo`) are now the types that
+`JmxNotificationEventNotifier` sends, which is the simple class name of the
event (such as `ExchangeCompletedEvent`),
+and the notification class is `javax.management.Notification`. Before, the
advertised types used a
+`org.apache.camel.management.event.` prefix and several events were missing.
=== camel-exec