This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 11.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit fbe316b3687bad79a87cf5d16335a60933c93b32 Author: opencode <[email protected]> AuthorDate: Mon Oct 26 13:09:54 2026 +0100 Share the slow query JMX notifier across a pool's interceptor instances The SlowQueryReportJmx interceptor is instantiated once per pooled connection. With notifyPool=false, each instance kept its own NotificationBroadcasterSupport while all instances registered their MBean under the same object name derived from the pool name. A JMX client could only subscribe to the MBean the platform server resolved first, so slow and failed query notifications raised by every other interceptor instance were sent to a broadcaster nobody could subscribe to and were silently dropped. Resolve the notification broadcaster by pool name through a static registry so that all interceptor instances sharing a pool name share the bcaster behind the registered MBean, and route every listener registration/removal and notification through that shared instance. When an instance unregisters its MBean, the pool name is released so a surviving same-named instance can take over the MBean. Add a regression test that configures the interceptor twice on one pool with notifyPool=false and asserts that a listener registered through the MBean receives notifications raised by both interceptor instances. --- .../jdbc/pool/interceptor/SlowQueryReportJmx.java | 44 ++++++++-- .../interceptor/TestSlowQueryJmxNotifications.java | 93 ++++++++++++++++++++++ 2 files changed, 130 insertions(+), 7 deletions(-) diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReportJmx.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReportJmx.java index ab238cbf0f..f44c209590 100644 --- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReportJmx.java +++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/SlowQueryReportJmx.java @@ -83,13 +83,41 @@ public class SlowQueryReportJmx extends SlowQueryReport implements NotificationE protected static final ConcurrentHashMap<String,SlowQueryReportJmxMBean> mbeans = new ConcurrentHashMap<>(); + /** + * Notification broadcasters for each connection pool. Interceptor + * instances are created per pooled connection, but JMX clients + * attach their listeners to the single MBean registered per pool, so + * notifications raised by any instance of a pool must reach the + * broadcaster of the pool rather than a broadcaster private to the + * instance. + */ + protected static final ConcurrentHashMap<String,NotificationBroadcasterSupport> notifiers = + new ConcurrentHashMap<>(); + //==============================JMX STUFF======================== /** - * Notification broadcaster support for JMX notifications. + * Notification broadcaster used before the pool name is known; + * once the pool name is known, {@link #getNotifier()} returns the + * broadcaster shared by all interceptor instances of that pool. */ protected volatile NotificationBroadcasterSupport notifier = new NotificationBroadcasterSupport(); + /** + * Returns the notification broadcaster that all interceptor + * instances of the current pool share, so that notifications raised + * on any connection reach the listeners registered with the pool's + * MBean. + * @return the shared broadcaster for this interceptor's pool + */ + protected NotificationBroadcasterSupport getNotifier() { + String name = poolName; + if (name == null) { + return notifier; + } + return notifiers.computeIfAbsent(name, key -> new NotificationBroadcasterSupport()); + } + /** * Adds a notification listener. * @param listener the listener to add @@ -99,7 +127,7 @@ public class SlowQueryReportJmx extends SlowQueryReport implements NotificationE */ @Override public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws IllegalArgumentException { - notifier.addNotificationListener(listener, filter, handback); + getNotifier().addNotificationListener(listener, filter, handback); } @@ -109,7 +137,7 @@ public class SlowQueryReportJmx extends SlowQueryReport implements NotificationE */ @Override public MBeanNotificationInfo[] getNotificationInfo() { - return notifier.getNotificationInfo(); + return getNotifier().getNotificationInfo(); } /** @@ -119,7 +147,7 @@ public class SlowQueryReportJmx extends SlowQueryReport implements NotificationE */ @Override public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException { - notifier.removeNotificationListener(listener); + getNotifier().removeNotificationListener(listener); } @@ -132,7 +160,7 @@ public class SlowQueryReportJmx extends SlowQueryReport implements NotificationE */ @Override public void removeNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws ListenerNotFoundException { - notifier.removeNotificationListener(listener, filter, handback); + getNotifier().removeNotificationListener(listener, filter, handback); } @@ -249,7 +277,8 @@ public class SlowQueryReportJmx extends SlowQueryReport implements NotificationE this.pool.getJmxPool().notify(type, query); } } else { - if (notifier!=null) { + NotificationBroadcasterSupport target = getNotifier(); + if (target!=null) { Notification notification = new Notification(type, this, @@ -257,7 +286,7 @@ public class SlowQueryReportJmx extends SlowQueryReport implements NotificationE System.currentTimeMillis(), query); - notifier.sendNotification(notification); + target.sendNotification(notification); } } } catch (RuntimeOperationsException e) { @@ -364,6 +393,7 @@ public class SlowQueryReportJmx extends SlowQueryReport implements NotificationE if (mbeans.remove(poolName)!=null) { ObjectName oname = getObjectName(getClass(),poolName); JmxUtil.unregisterJmx(oname); + notifiers.remove(poolName); } } catch (MalformedObjectNameException | RuntimeOperationsException e) { log.warn("Jmx deregistration failed.",e); diff --git a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/interceptor/TestSlowQueryJmxNotifications.java b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/interceptor/TestSlowQueryJmxNotifications.java new file mode 100644 index 0000000000..85af065940 --- /dev/null +++ b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/interceptor/TestSlowQueryJmxNotifications.java @@ -0,0 +1,93 @@ +/* + * 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.tomcat.jdbc.pool.interceptor; + +import java.lang.management.ManagementFactory; +import java.sql.Connection; +import java.sql.Statement; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; + +import javax.management.MBeanServer; +import javax.management.Notification; +import javax.management.NotificationListener; +import javax.management.ObjectName; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; + +import org.apache.tomcat.jdbc.JdbcPoolBaseTest; +import org.apache.tomcat.jdbc.pool.DataSource; + +/** + * JMX notifications of the {@code SlowQueryReportJmx} interceptor in + * the documented {@code notifyPool=false} mode. Interceptor chains are + * created per pooled connection, while JMX clients attach their + * listeners to the single MBean registered per pool: a slow query + * occurring on any connection must reach those listeners, not only the + * ones occurring on the connection whose interceptor instance happened + * to win the MBean registration. + */ +class TestSlowQueryJmxNotifications extends JdbcPoolBaseTest { + + @Test + void notifyPoolFalseDeliversNotificationsOfEveryConnection() throws Exception { + String poolName = "JmxNotify-" + System.nanoTime(); + DataSource ds = newDataSource(); + ds.setName(poolName); + ds.setJdbcInterceptors("org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReportJmx" + + "(notifyPool=false,threshold=-1)"); + ds.getPoolProperties().setMaxActive(2); + ds.getPoolProperties().setInitialSize(0); + + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + ObjectName oname = new ObjectName("tomcat.jdbc:type=" + + SlowQueryReportJmx.class.getName() + ",name=" + poolName); + List<Notification> received = new CopyOnWriteArrayList<>(); + NotificationListener listener = (notification, handback) -> received.add(notification); + + try (Connection first = ds.getConnection()) { + assertTrue(server.isRegistered(oname), + "the interceptor MBean must be registered once the pool is in use"); + server.addNotificationListener(oname, listener, null, null); + try (Connection second = ds.getConnection()) { + // two concurrent borrows with no pre-created connections + // means two physical connections, hence two interceptor + // instances - only one of which can own the MBean + assertEquals(2, ds.getNumActive()); + try (Statement st1 = first.createStatement(); + Statement st2 = second.createStatement()) { + st1.execute("SELECT 1"); + st2.execute("SELECT 2"); + } + } + server.removeNotificationListener(oname, listener); + } + + List<String> messages = received.stream().map(Notification::getMessage) + .collect(Collectors.toList()); + assertEquals(2, messages.size(), + "every slow query of every connection must notify, received: " + messages); + assertTrue(messages.stream().anyMatch(m -> m.contains("SELECT 1")), + "the query run on the first connection must be reported, received: " + messages); + assertTrue(messages.stream().anyMatch(m -> m.contains("SELECT 2")), + "the query run on the second physical connection must also reach the listener, received: " + + messages); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
