This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 25c7ed08b1a06b2679b6c8e97a2e69ae54d350ee Author: opencode <[email protected]> AuthorDate: Mon Oct 26 15:06:34 2026 +0100 Registering a DataSource before its pool exists no longer fails DataSource.registerJmx() dereferenced the lazily created ConnectionPool, so an MBean registration performed before the first getConnection()/createPool() - the documented "register the DataSource yourself" route - threw NullPointerException from preRegister(). Per the JMX specification an exception from preRegister aborts the whole registration, losing both the DataSource MBean and the pool MBean, and left the oname registration handle set although nothing had been registered. The pool-side registration is now skipped with a warning when no pool exists, oname is only kept for an actually published MBean, and preRegister() never propagates pool-side runtime state to the MBean server. Note this does not defer the registration: a pool created after the skipped registration must be (re-)registered explicitly. --- .../org/apache/tomcat/jdbc/pool/DataSource.java | 26 +++++++++++++++-- .../tomcat/jdbc/pool/jmx/TestJmxMonitor.java | 33 ++++++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSource.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSource.java index 1bdc5e5810..21a63342aa 100644 --- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSource.java +++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSource.java @@ -107,7 +107,15 @@ public class DataSource extends DataSourceProxy implements javax.sql.DataSource, } } }catch (MalformedObjectNameException x) { + this.oname = null; log.error("Unable to create object name for JDBC pool.",x); + }catch (RuntimeException x) { + // Never break the caller's MBean registration because of + // pool-side state (for instance a pool that has not been + // created yet); preRegister throwing aborts the whole + // registration in the MBean server. + this.oname = null; + log.error("Unable to register JDBC pool MBean.",x); } return name; } @@ -134,12 +142,24 @@ public class DataSource extends DataSourceProxy implements javax.sql.DataSource, } /** - * Registers the ConnectionPoolMBean under a unique name based on the ObjectName for the DataSource + * Registers the ConnectionPoolMBean under a unique name based on the + * ObjectName for the DataSource. A pool that has not been created yet + * (or was already closed) has no MBean to publish; the registration is + * then skipped rather than failing, since the caller of + * {@link #preRegister(MBeanServer, ObjectName)} must not see an + * exception over pool-side lifecycle state. */ protected void registerJmx() { - if (pool.getJmxPool()!=null) { - JmxUtil.registerJmx(oname, null, pool.getJmxPool()); + final ConnectionPool p = pool; + if (p == null || p.getJmxPool() == null) { + log.warn("JDBC pool MBean [" + oname + "] not registered: the pool " + + "has not been created yet or has already been closed."); + // oname is the handle postDeregister() unregisters; do not keep + // one that was never registered + this.oname = null; + return; } + JmxUtil.registerJmx(oname, null, p.getJmxPool()); } /** diff --git a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/jmx/TestJmxMonitor.java b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/jmx/TestJmxMonitor.java index 38e600441f..910e5dbc36 100644 --- a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/jmx/TestJmxMonitor.java +++ b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/jmx/TestJmxMonitor.java @@ -17,6 +17,7 @@ package org.apache.tomcat.jdbc.pool.jmx; import java.lang.management.ManagementFactory; +import java.lang.reflect.Field; import java.sql.Connection; import java.util.HashSet; import java.util.Properties; @@ -227,6 +228,38 @@ class TestJmxMonitor extends JdbcPoolBaseTest { } } + @Test + void registrationBeforeThePoolExistsKeepsTheContainerCallback() + throws Exception { + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + DataSource ds = newDataSource(); + ds.getPoolProperties().setJmxEnabled(true); + // Deliberately no createPool(): a container may register the + // DataSource before the embedder has started the pool. That must + // not break the MBeanRegistration callback - preRegister throwing + // makes the MBean server abort the whole registration. + ObjectName dsName = uniqueDataSourceName("nopool"); + Set<ObjectName> before = poolMBeans(server); + Assertions.assertDoesNotThrow(() -> ds.preRegister(server, dsName), + "preRegister must not fail over a pool that does not exist yet"); + Set<ObjectName> added = new HashSet<>(poolMBeans(server)); + added.removeAll(before); + Assertions.assertTrue(added.isEmpty(), + "a pool that does not exist yet must not publish MBeans, added: " + + added); + // The registration then completes, so the container will also + // deregister later. The pool-side handle postDeregister() + // unregisters must not claim a name that was never registered. + Field handle = DataSource.class.getDeclaredField("oname"); + handle.setAccessible(true); + Assertions.assertNull(handle.get(ds), + "a skipped pool MBean registration must not leave its " + + "ObjectName behind as a registration handle"); + Assertions.assertDoesNotThrow(ds::postDeregister, + "deregistering a DataSource whose pool MBean was never " + + "published must be a clean no-op"); + } + @Test void disabledJmxPublishesNothing() throws Exception { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
