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 a4999ecc9f457c3b10bbc99e7d980755c98abf26 Author: opencode <[email protected]> AuthorDate: Tue Oct 27 09:50:45 2026 +0100 Always use the disposable connection facade; deprecate useDisposableConnectionFacade With the facade switched off, the pool handed out a proxy onto the pooled connection's cached interceptor chain, which is re-armed for the next borrower. A handle kept after close() therefore stayed usable: its close() returned - or its calls drove - the connection the current borrower was still using, including on the XA proxy class. The facade's documented purpose is safety, it was never documented as a performance switch, and measuring shows switching it off saves only one small object allocation per borrow. The pool now always installs the facade. The property is deprecated, has no effect, but is still accepted and stored so existing configurations keep loading. Tests: the facadeless identity suite is replaced by TestFacadeAlwaysUsed (plain and XA stale-handle pins, released-handle contract) and a factory round-trip pin for the deprecated attribute. --- modules/jdbc-pool/doc/jdbc-pool.xml | 12 +- .../apache/tomcat/jdbc/pool/ConnectionPool.java | 18 +-- .../apache/tomcat/jdbc/pool/DataSourceFactory.java | 22 ++- .../apache/tomcat/jdbc/pool/DataSourceProxy.java | 12 ++ .../apache/tomcat/jdbc/pool/PoolConfiguration.java | 30 +++- .../apache/tomcat/jdbc/pool/PoolProperties.java | 19 ++- .../tomcat/jdbc/pool/jmx/ConnectionPool.java | 12 ++ .../apache/tomcat/jdbc/pool/mbeans-descriptors.xml | 2 +- .../tomcat/jdbc/pool/TestFacadeAlwaysUsed.java | 163 +++++++++++++++++++ .../jdbc/pool/TestFacadelessProxyIdentity.java | 175 --------------------- .../tomcat/jdbc/pool/TestPoolProperties.java | 16 ++ .../apache/tomcat/jdbc/pool/TestProxyContract.java | 8 +- webapps/docs/changelog.xml | 11 ++ 13 files changed, 298 insertions(+), 202 deletions(-) diff --git a/modules/jdbc-pool/doc/jdbc-pool.xml b/modules/jdbc-pool/doc/jdbc-pool.xml index 481270535b..c57cfae0fa 100644 --- a/modules/jdbc-pool/doc/jdbc-pool.xml +++ b/modules/jdbc-pool/doc/jdbc-pool.xml @@ -544,8 +544,16 @@ </p> </attribute> <attribute name="useDisposableConnectionFacade" required="false"> - <p>(boolean) Set this to true if you wish to put a facade on your connection so that it cannot be reused after it has been closed. This prevents a thread holding on to a - reference of a connection it has already called closed on, to execute queries on it. Default value is <code>true</code>. + <p>(boolean) Deprecated. This property no longer has any effect. The pool + always puts a facade on the connections it hands out, so that a + connection cannot be reused after it has been closed: after + <code>close()</code> has been called, all calls on the handle + except <code>close()</code> and <code>isClosed()</code> throw an + exception. Switching the facade off used to leave a handle usable + after close, which allowed it to return or drive the connection of + a later borrower; setting this property to + <code>false</code> is therefore not supported. The property is + still accepted so that existing configurations keep loading. </p> </attribute> <attribute name="logValidationErrors" required="false"> diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java index f0032e645a..6a26dbef31 100644 --- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java +++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java @@ -354,15 +354,15 @@ public class ConnectionPool { try { Constructor<?> proxyConstructor = getProxyConstructor(con.getXAConnection() != null); //create the proxy - //TODO possible optimization, keep track if this connection was returned properly, and don't generate a new facade - Connection connection = null; - if (getPoolProperties().getUseDisposableConnectionFacade() ) { - connection = (Connection)proxyConstructor.newInstance(new Object[] { new DisposableConnectionFacade(handler) }); - } else { - connection = (Connection)proxyConstructor.newInstance(new Object[] {handler}); - } - //return the connection - return connection; + //TODO possible optimization, keep track if this connection was returned properly, and don't generate a new handle and facade + // The disposable connection facade is always used; a handle that + // its borrower has closed must stay inert, as the interceptor + // chain below is cached on the pooled connection and re-armed + // for the next borrower. (The useDisposableConnectionFacade + // property that used to switch this off is deprecated and no + // longer has any effect.) + return (Connection)proxyConstructor.newInstance( + new Object[] { new DisposableConnectionFacade(handler) }); }catch (Exception x) { SQLException s = new SQLException(); s.initCause(x); diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java index c949381bb4..ded16d4f93 100644 --- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java +++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java @@ -287,7 +287,9 @@ public class DataSourceFactory implements ObjectFactory { protected static final String PROP_ROLLBACKONRETURN = "rollbackOnReturn"; /** - * Property name for useDisposableConnectionFacade. + * Property name for useDisposableConnectionFacade. The property is + * deprecated and has no effect; the disposable connection facade is + * always used. */ protected static final String PROP_USEDISPOSABLECONNECTIONFACADE = "useDisposableConnectionFacade"; @@ -717,7 +719,8 @@ public class DataSourceFactory implements ObjectFactory { value = properties.getProperty(PROP_USEDISPOSABLECONNECTIONFACADE); if (value != null) { - poolProperties.setUseDisposableConnectionFacade(Boolean.parseBoolean(value)); + storeDeprecatedUseDisposableConnectionFacade(poolProperties, + Boolean.parseBoolean(value)); } value = properties.getProperty(PROP_LOGVALIDATIONERRORS); @@ -817,4 +820,19 @@ public class DataSourceFactory implements ObjectFactory { return PoolProperties.getProperties(propText,null); } + /** + * Stores the configured value of the deprecated + * useDisposableConnectionFacade property. The property has no effect any + * more, the disposable connection facade is always used; the value is + * stored as configured so that the property keeps showing up the way it + * was set, the same as through any other configuration path. + * @param poolProperties the properties to store the value on + * @param value the configured value + */ + @SuppressWarnings("deprecation") + private static void storeDeprecatedUseDisposableConnectionFacade( + PoolConfiguration poolProperties, boolean value) { + poolProperties.setUseDisposableConnectionFacade(value); + } + } diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java index 6b66d57631..7fee44776a 100644 --- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java +++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java @@ -1226,12 +1226,24 @@ public class DataSourceProxy implements PoolConfiguration { return getPoolProperties().getRollbackOnReturn(); } + /** + * {@inheritDoc} + * @deprecated The disposable connection facade is always used, so this + * property no longer has any effect. + */ @Override + @Deprecated public void setUseDisposableConnectionFacade(boolean useDisposableConnectionFacade) { getPoolProperties().setUseDisposableConnectionFacade(useDisposableConnectionFacade); } + /** + * {@inheritDoc} + * @deprecated The disposable connection facade is always used, so this + * property no longer has any effect. + */ @Override + @Deprecated public boolean getUseDisposableConnectionFacade() { return getPoolProperties().getUseDisposableConnectionFacade(); } diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java index 35dabb6667..3ca87d9831 100644 --- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java +++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java @@ -851,17 +851,31 @@ public interface PoolConfiguration { boolean getRollbackOnReturn(); /** - * If set to <code>true</code>, the connection will be wrapped with facade that will disallow the connection to be used after - * {@link java.sql.Connection#close()} is called. If set to <code>true</code>, after {@link java.sql.Connection#close()} all calls except - * {@link java.sql.Connection#close()} and {@link java.sql.Connection#isClosed()} will throw an exception. - * @param useDisposableConnectionFacade <code>true</code> to use a facade - */ + * Stores the value of the deprecated {@code useDisposableConnectionFacade} + * property. The pool always wraps the connection handle in a facade that + * disallows use of the connection after {@link java.sql.Connection#close()} + * has been called, whatever value is stored here. + * @param useDisposableConnectionFacade the configured value, retained but + * without effect + * @deprecated The disposable connection facade is always used, so this + * property no longer has any effect. Setting it to + * <code>false</code> used to hand out connection handles that + * stayed usable, or worse, able to return and drive the + * connection of a later borrower. + */ + @Deprecated void setUseDisposableConnectionFacade(boolean useDisposableConnectionFacade); /** - * Returns <code>true</code> if this connection pool is configured to use a connection facade to prevent re-use of connection after - * {@link java.sql.Connection#close()} has been invoked - * @return <code>true</code> if {@link java.sql.Connection#close()} has been invoked. + * Returns the value stored for the deprecated + * {@code useDisposableConnectionFacade} property. The pool always wraps the + * connection handle in a facade that disallows use of the connection after + * {@link java.sql.Connection#close()} has been called, whatever value is + * returned here. + * @return the configured value, retained but without effect + * @deprecated The disposable connection facade is always used, so this + * property no longer has any effect. */ + @Deprecated boolean getUseDisposableConnectionFacade(); /** diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java index afb0bd4527..695b03eab4 100644 --- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java +++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java @@ -148,7 +148,12 @@ public class PoolProperties implements PoolConfiguration, Cloneable, Serializabl private volatile boolean commitOnReturn = false; /** Whether to rollback on connection return. */ private volatile boolean rollbackOnReturn = false; - /** Whether to use a disposable connection facade. */ + /** + * Whether to use a disposable connection facade. + * @deprecated The disposable connection facade is always used, so this + * property no longer has any effect. + */ + @Deprecated private volatile boolean useDisposableConnectionFacade = true; /** Whether to log validation errors. */ private volatile boolean logValidationErrors = false; @@ -1180,12 +1185,24 @@ public class PoolProperties implements PoolConfiguration, Cloneable, Serializabl return this.rollbackOnReturn; } + /** + * {@inheritDoc} + * @deprecated The disposable connection facade is always used, so this + * property no longer has any effect. + */ @Override + @Deprecated public void setUseDisposableConnectionFacade(boolean useDisposableConnectionFacade) { this.useDisposableConnectionFacade = useDisposableConnectionFacade; } + /** + * {@inheritDoc} + * @deprecated The disposable connection facade is always used, so this + * property no longer has any effect. + */ @Override + @Deprecated public boolean getUseDisposableConnectionFacade() { return useDisposableConnectionFacade; } diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java index d1169824cd..cb15d51faf 100644 --- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java +++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java @@ -910,12 +910,24 @@ public class ConnectionPool extends NotificationBroadcasterSupport return getPoolProperties().getRollbackOnReturn(); } + /** + * {@inheritDoc} + * @deprecated The disposable connection facade is always used, so this + * property no longer has any effect. + */ @Override + @Deprecated public void setUseDisposableConnectionFacade(boolean useDisposableConnectionFacade) { getPoolProperties().setUseDisposableConnectionFacade(useDisposableConnectionFacade); } + /** + * {@inheritDoc} + * @deprecated The disposable connection facade is always used, so this + * property no longer has any effect. + */ @Override + @Deprecated public boolean getUseDisposableConnectionFacade() { return getPoolProperties().getUseDisposableConnectionFacade(); } diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml index 677a1b438e..bb78d0097a 100644 --- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml +++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml @@ -300,7 +300,7 @@ writeable="false"/> <attribute name="useDisposableConnectionFacade" - description="If true, connection pool is configured to use a connection facade to prevent re-use of connection after close() has been invoked" + description="Deprecated, no longer has any effect: the pool always uses a connection facade to prevent re-use of connection after close() has been invoked" type="java.lang.Boolean" is="false" writeable="false"/> diff --git a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestFacadeAlwaysUsed.java b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestFacadeAlwaysUsed.java new file mode 100644 index 0000000000..60c2816943 --- /dev/null +++ b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestFacadeAlwaysUsed.java @@ -0,0 +1,163 @@ +/* + * 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; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import org.apache.tomcat.jdbc.JdbcPoolBaseTest; + +/** + * The {@code useDisposableConnectionFacade} property is deprecated and has + * no effect: the pool always wraps the connection handle in a + * {@link DisposableConnectionFacade}. Switching the facade off used to + * expose the cached, re-armed interceptor chain directly, where a handle + * kept from an earlier borrow could return or drive the connection of the + * current borrower. These tests pin the facade against a configuration + * that still asks for it to be switched off. + */ +class TestFacadeAlwaysUsed extends JdbcPoolBaseTest { + + @SuppressWarnings("deprecation") + private DataSource facadelessConfiguration() { + DataSource ds = newDataSource(); + ds.getPoolProperties().setUseDisposableConnectionFacade(false); + return ds; + } + + @Test + void aHandleFromDeprecatedFacadelessConfigurationStaysDisposable() + throws Exception { + DataSource ds = facadelessConfiguration(); + + Connection first = ds.getConnection(); + setSessionMarker(first, 7); + first.close(); // returned to the pool + + Connection second = ds.getConnection(); + Assertions.assertEquals(7, sessionMarker(second).intValue(), + "precondition: the pool re-armed the same physical session"); + + first.close(); // stale double-close from the previous borrower + + // Pre-deprecation, with the facade switched off this stale close ran + // pool.returnConnection() on the connection the second borrower is + // still using. + Assertions.assertEquals(0, ds.getPool().getIdle(), + "the stale close must not hand the current borrower's " + + "connection back to the pool"); + Assertions.assertEquals(1, ds.getPool().getActive()); + + // The current borrower keeps full use of its connection. + setSessionMarker(second, 9); + Assertions.assertEquals(9, sessionMarker(second).intValue()); + + // A stale call is refused instead of driving the live handle. + Assertions.assertThrows(SQLException.class, first::getAutoCommit); + + second.close(); + Assertions.assertEquals(1, ds.getPool().getIdle()); + } + + @Test + void aReleasedHandleFromDeprecatedFacadelessConfigurationKeepsTheContract() + throws Exception { + DataSource ds = facadelessConfiguration(); + + Connection pooled = ds.getConnection(); + Assertions.assertTrue(pooled.equals(pooled)); + int hash = pooled.hashCode(); + pooled.close(); + + // The disposed facade keeps answering: inert close, closed and + // invalid answers, stable identity. + Assertions.assertTrue(pooled.isClosed(), + "a released handle reports itself closed"); + Assertions.assertFalse(pooled.isValid(1), + "a released handle reports itself invalid"); + Assertions.assertNotNull(pooled.toString(), + "a released handle stays printable for logging"); + Assertions.assertEquals(hash, pooled.hashCode(), + "closing must not change the hash code of a handle"); + Assertions.assertTrue(pooled.equals(pooled), + "closing must not change the identity of a handle"); + pooled.close(); // a further close stays a no-op + } + + @Test + void twoBorrowsOverOneSessionAreDistinctHandlesEvenInDeprecatedConfiguration() + throws Exception { DataSource ds = facadelessConfiguration(); + + Connection first = ds.getConnection(); + setSessionMarker(first, 42); + first.close(); + + try (Connection second = ds.getConnection()) { + Assertions.assertEquals(Integer.valueOf(42), sessionMarker(second), + "the test must actually be looking at one reused physical session"); + Assertions.assertFalse(first.equals(second), + "two borrows are two handles even over one physical session"); + Assertions.assertNotEquals(first.hashCode(), second.hashCode(), + "two handles must not share an identity hash"); + } + } + + @SuppressWarnings("deprecation") + @Test + void anXaHandleFromDeprecatedFacadelessConfigurationStaysDisposable() + throws Exception { + org.h2.jdbcx.JdbcDataSource inner = new org.h2.jdbcx.JdbcDataSource(); + inner.setUrl(getUrl()); + inner.setUser("sa"); + inner.setPassword(""); + XADataSource ds = new XADataSource(); + ds.setDriverClassName("org.h2.Driver"); + ds.getPoolProperties().setJmxEnabled(false); + ds.getPoolProperties().setInitialSize(1); + ds.getPoolProperties().setMinIdle(0); + ds.getPoolProperties().setDataSource(inner); + ds.getPoolProperties().setUseDisposableConnectionFacade(false); + manage(ds); + ds.createPool(); + + Connection first = ds.getConnection(); + setSessionMarker(first, 5); + first.close(); + + Connection second = ds.getConnection(); + Assertions.assertEquals(5, sessionMarker(second).intValue(), + "precondition: the pool re-armed the same physical session"); + + // The forced facade covers the XA proxy class too: a close through + // the stale XA handle must not return the session the current + // borrower is using. + first.close(); + Assertions.assertEquals(0, ds.getPool().getIdle(), + "the stale close must not hand the current borrower's " + + "connection back to the pool"); + Assertions.assertEquals(1, ds.getPool().getActive()); + Assertions.assertTrue(first.isClosed()); + Assertions.assertThrows(SQLException.class, first::getAutoCommit, + "a stale XA handle must not drive the current borrower"); + + second.close(); + Assertions.assertEquals(1, ds.getPool().getIdle()); + } +} diff --git a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestFacadelessProxyIdentity.java b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestFacadelessProxyIdentity.java deleted file mode 100644 index fbe30d3067..0000000000 --- a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestFacadelessProxyIdentity.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * 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; - -import java.sql.Connection; -import java.util.HashSet; -import java.util.Set; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import org.apache.tomcat.jdbc.JdbcPoolBaseTest; - -/** - * The pool's own {@code equals}/{@code hashCode} handling on the handle it - * hands out, below the {@link DisposableConnectionFacade}. With the facade - * enabled (the default) the facade answers these calls and - * {@link ProxyConnection} never sees them; with the facade switched off the - * calls reach {@code ProxyConnection}, which used to forward them down the - * XA dispatch branch - on a plain pool that resolves the XA connection to - * {@code null} and the call threw, and on an XA pool it compared against - * the underlying {@code XAConnection} instead of the handle. Any - * {@code HashSet<Connection>} of pooled connections broke, and even after - * {@code close()} the contract still has to hold, so the calls must stay - * answerable on a released handle too. - * - * <p>Identity semantics are pinned per borrow, matching the facade-on - * behaviour asserted by {@link TestProxyContract}: a handle is equal only - * to itself, even when a later borrow maps to the same physical session.</p> - */ -class TestFacadelessProxyIdentity extends JdbcPoolBaseTest { - - private DataSource newDataSourceWithoutFacade() { - DataSource ds = newDataSource(); - ds.getPoolProperties().setUseDisposableConnectionFacade(false); - return ds; - } - - @Test - void aLiveHandleHonoursEqualsHashCodeContract() throws Exception { - DataSource ds = newDataSourceWithoutFacade(); - ds.createPool(); - - Connection pooled = ds.getConnection(); - // Pre-fix these threw because the Object methods fell into the XA - // dispatch branch with a null XA connection. - Assertions.assertTrue(pooled.equals(pooled), "a handle must be equal to itself"); - Assertions.assertFalse(pooled.equals(null), - "Object.equals(null) must answer false, not throw"); - Assertions.assertFalse(pooled.equals("a foreign object"), - "equals against a non-proxy must answer false, not throw"); - Object foreignProxy = java.lang.reflect.Proxy.newProxyInstance( - getClass().getClassLoader(), - new Class<?>[] { Runnable.class }, - (p, m, a) -> null); - Assertions.assertFalse(pooled.equals(foreignProxy), - "equals against a foreign dynamic proxy must answer false"); - int hash = pooled.hashCode(); - Assertions.assertEquals(hash, pooled.hashCode(), "hashCode must be stable"); - pooled.close(); - } - - @Test - void aReleasedHandleKeepsTheContract() throws Exception { - DataSource ds = newDataSourceWithoutFacade(); - ds.createPool(); - - Connection pooled = ds.getConnection(); - Assertions.assertTrue(pooled.equals(pooled)); - int hash = pooled.hashCode(); - pooled.close(); - - // hashCode/equals are identity, not liveness - a released handle - // still has to answer them (pre-fix: UndeclaredThrowableException - // caused by the "Connection has already been closed" SQLException). - Assertions.assertEquals(hash, pooled.hashCode(), - "closing must not change the hash code of a handle"); - Assertions.assertTrue(pooled.equals(pooled), - "closing must not change the identity of a handle"); - Assertions.assertFalse(pooled.equals(null), - "a released handle must keep the equals(null) contract"); - } - - @Test - void twoBorrowsOverOneSessionAreDistinctHandles() throws Exception { - DataSource ds = newDataSourceWithoutFacade(); - ds.createPool(); - - Connection first = ds.getConnection(); - setSessionMarker(first, 42); - Assertions.assertTrue(first.equals(first)); - int firstHash = first.hashCode(); - first.close(); - - try (Connection second = ds.getConnection()) { - Assertions.assertEquals(Integer.valueOf(42), sessionMarker(second), - "the test must actually be looking at one reused physical session"); - Assertions.assertFalse(first.equals(second), - "two borrows are two handles even over one physical session"); - Assertions.assertFalse(second.equals(first)); - Assertions.assertNotEquals(firstHash, second.hashCode(), - "two handles must not share an identity hash"); - } - } - - @Test - void pooledConnectionsUsableAsHashCollectionKeys() throws Exception { - DataSource ds = newDataSourceWithoutFacade(); - ds.createPool(); - - Set<Connection> seen = new HashSet<>(); - Connection a = ds.getConnection(); - Connection b = ds.getConnection(); - Assertions.assertTrue(seen.add(a), "first insert must succeed"); - Assertions.assertTrue(seen.add(b), "second insert must succeed"); - Assertions.assertFalse(seen.add(a), "a handle already in the set must not re-insert"); - Assertions.assertTrue(seen.contains(a), "collection lookup must find the handle"); - Assertions.assertTrue(seen.contains(b)); - a.close(); - b.close(); - Assertions.assertTrue(seen.contains(a), - "a released handle must still hash to its bucket"); - } - - @Test - void anXaCapableHandleKeepsIdentityForItself() throws Exception { - org.h2.jdbcx.JdbcDataSource inner = new org.h2.jdbcx.JdbcDataSource(); - inner.setUrl(getUrl()); - inner.setUser("sa"); - inner.setPassword(""); - XADataSource ds = new XADataSource(); - ds.setDriverClassName("org.h2.Driver"); - ds.getPoolProperties().setJmxEnabled(false); - ds.getPoolProperties().setInitialSize(1); - ds.getPoolProperties().setMinIdle(0); - ds.getPoolProperties().setDataSource(inner); - ds.getPoolProperties().setUseDisposableConnectionFacade(false); - manage(ds); - ds.createPool(); - - Connection first = ds.getConnection(); - // Pre-fix the calls were dispatched at the live XAConnection: - // a handle was not equal to itself (the XA connection was asked - // to compare itself against the proxy) and two handles over one - // session shared the XA connection's hash. - Assertions.assertTrue(first.equals(first), - "an XA-capable handle must be equal to itself"); - Assertions.assertFalse(first.equals(null)); - int firstHash = first.hashCode(); - Assertions.assertEquals(firstHash, first.hashCode(), "hashCode must be stable"); - first.close(); - - try (Connection second = ds.getConnection()) { - Assertions.assertTrue(second.equals(second)); - Assertions.assertFalse(first.equals(second), - "two borrows are two handles even over one physical session"); - Assertions.assertNotEquals(firstHash, second.hashCode(), - "two handles must not share the XA connection's hash"); - } - } -} diff --git a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestPoolProperties.java b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestPoolProperties.java index c487d15ecd..2016c4f153 100644 --- a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestPoolProperties.java +++ b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestPoolProperties.java @@ -112,6 +112,22 @@ class TestPoolProperties { "clearing the configuration must round-trip as well"); } + @Test + @SuppressWarnings("deprecation") + void theDeprecatedFacadePropertyIsStillStoredWhenConfigured() { + Properties attrs = new Properties(); + attrs.setProperty("useDisposableConnectionFacade", "false"); + + PoolConfiguration configured = + DataSourceFactory.parsePoolProperties(attrs); + + // The property has no effect any more, but existing configuration + // must keep loading and keep reading back as it was written. + Assertions.assertFalse(configured.getUseDisposableConnectionFacade(), + "the deprecated value must still round-trip through the " + + "factory path"); + } + @Test void setConnectionPropertiesRoundTripsThroughTheFactory() { Properties attrs = new Properties(); diff --git a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestProxyContract.java b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestProxyContract.java index 7eb64c67ec..52d1ed131e 100644 --- a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestProxyContract.java +++ b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/pool/TestProxyContract.java @@ -34,10 +34,10 @@ import org.apache.tomcat.jdbc.JdbcPoolBaseTest; * for the callers that need it. * * <p>Scenario harvested from the retired legacy classes - * {@code MultipleCloseTest} and {@code EqualsHashCodeTest}. The pool's own - * {@code equals}/{@code hashCode} handling <em>below</em> the facade (with - * {@code useDisposableConnectionFacade=false}) is pinned separately in - * {@link TestFacadelessProxyIdentity}.</p> + * {@code MultipleCloseTest} and {@code EqualsHashCodeTest}. The same + * contract now also holds for configurations that still ask for the + * (deprecated, no-effect) {@code useDisposableConnectionFacade=false}; + * that is pinned in {@link TestFacadeAlwaysUsed}.</p> */ class TestProxyContract extends JdbcPoolBaseTest { diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 50242ef57d..d31b1b6afc 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -870,6 +870,17 @@ interceptors exist to provide silently missed exactly the work most likely to be slow. (remm) </fix> + <fix> + Always wrap connection handles in the disposable connection + facade. The <code>useDisposableConnectionFacade</code> property + that could switch the facade off is deprecated and no longer has + any effect; it is still accepted so existing configurations keep + loading. Switching the facade off exposed the pool's cached + interceptor chain directly to the application: a handle kept from + an earlier borrow stayed usable and its close returned – or its + calls drove – the connection a later borrower was still using. + (remm) + </fix> </changelog> </subsection> <subsection name="Other"> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
