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]

Reply via email to