You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by kf...@apache.org on 2019/03/19 05:56:11 UTC

[tomcat] branch master updated: Improved maxAge handling. This closes #140

This is an automated email from the ASF dual-hosted git repository.

kfujino pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/master by this push:
     new 116a63b  Improved maxAge handling. This closes #140
116a63b is described below

commit 116a63b516fc13ea614ca219dff1fc9d61257b90
Author: KeiichiFujino <kf...@apache.org>
AuthorDate: Tue Mar 19 14:54:23 2019 +0900

    Improved maxAge handling.
    This closes #140
---
 modules/jdbc-pool/doc/jdbc-pool.xml                | 15 ++--
 .../apache/tomcat/jdbc/pool/ConnectionPool.java    | 79 +++++++++++++++++++---
 .../apache/tomcat/jdbc/pool/PoolConfiguration.java | 22 +++---
 .../apache/tomcat/jdbc/pool/PoolProperties.java    |  1 +
 .../tomcat/jdbc/pool/jmx/ConnectionPool.java       | 40 ++++++-----
 .../apache/tomcat/jdbc/test/PoolCleanerTest.java   | 41 ++++++++++-
 webapps/docs/changelog.xml                         |  9 +++
 7 files changed, 164 insertions(+), 43 deletions(-)

diff --git a/modules/jdbc-pool/doc/jdbc-pool.xml b/modules/jdbc-pool/doc/jdbc-pool.xml
index 7138771..890fe40 100644
--- a/modules/jdbc-pool/doc/jdbc-pool.xml
+++ b/modules/jdbc-pool/doc/jdbc-pool.xml
@@ -335,7 +335,7 @@
     <attribute name="timeBetweenEvictionRunsMillis" required="false">
       <p>(int) The number of milliseconds to sleep between runs of the idle connection validation/cleaner thread.
          This value should not be set under 1 second. It dictates how often we check for idle, abandoned connections, and how often
-         we validate idle connections.
+         we validate idle connections. This value will be overridden by <code>maxAge</code> if the latter is non-zero and lower.
          The default value is <code>5000</code> (5 seconds). <br/>
       </p>
     </attribute>
@@ -463,17 +463,22 @@
     </attribute>
 
     <attribute name="maxAge" required="false">
-      <p>(long) Time in milliseconds to keep this connection. This attribute
-         works both when returning connection and when borrowing connection.
+      <p>(long) Time in milliseconds to keep a connection before recreating it.
          When a connection is borrowed from the pool, the pool will check to see
          if the <code>now - time-when-connected > maxAge</code> has been reached
          , and if so, it reconnects before borrow it. When a connection is
          returned to the pool, the pool will check to see if the
          <code>now - time-when-connected > maxAge</code> has been reached, and
-         if so, it closes the connection rather than returning it to the pool.
+         if so, it tries to reconnect.
+         When a connection is idle and <code>timeBetweenEvictionRunsMillis</code> is
+         greater than zero, the pool will periodically check to see if the
+         <code>now - time-when-connected > maxAge</code> has been reached, and
+         if so, it tries to reconnect.
+         Setting <code>maxAge</code> to a value lower than <code>timeBetweenEvictionRunsMillis</code>
+         will override it (so idle connection validation/cleaning will run more frequently).
          The default value is <code>0</code>, which implies that connections
          will be left open and no age check will be done upon borrowing from the
-         pool and returning the connection to the pool.</p>
+         pool, returning the connection to the pool or when checking idle connections.</p>
     </attribute>
 
     <attribute name="useEquals" 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 b2ede14..f5b1862 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
@@ -524,6 +524,11 @@ public class ConnectionPool {
             log.warn("maxIdle is smaller than minIdle, setting maxIdle to: "+properties.getMinIdle());
             properties.setMaxIdle(properties.getMinIdle());
         }
+        if (properties.getMaxAge()>0 && properties.isPoolSweeperEnabled() &&
+                properties.getTimeBetweenEvictionRunsMillis()>properties.getMaxAge()) {
+            log.warn("timeBetweenEvictionRunsMillis is larger than maxAge, setting timeBetweenEvictionRunsMillis to: " + properties.getMaxAge());
+            properties.setTimeBetweenEvictionRunsMillis((int)properties.getMaxAge());
+        }
     }
 
     public void initializePoolCleaner(PoolConfiguration properties) {
@@ -824,10 +829,9 @@ public class ConnectionPool {
             try {
                 con.reconnect();
                 reconnectedCount.incrementAndGet();
-                int validationMode = getPoolProperties().isTestOnConnect() || getPoolProperties().getInitSQL()!=null ?
-                    PooledConnection.VALIDATE_INIT :
-                    PooledConnection.VALIDATE_BORROW;
-
+                int validationMode = isInitNewConnections() ?
+                        PooledConnection.VALIDATE_INIT:
+                        PooledConnection.VALIDATE_BORROW;
                 if (con.validate(validationMode)) {
                     //set the timestamp
                     con.setTimestamp(now);
@@ -861,6 +865,18 @@ public class ConnectionPool {
             }
         }
     }
+
+    /**
+     * Returns whether new connections should be initialized by invoking
+     * {@link PooledConnection#validate(int)} with {@link PooledConnection#VALIDATE_INIT}.
+     *
+     * @return true if pool is either configured to test connections on connect or a non-NULL init
+     * SQL has been configured
+     */
+    private boolean isInitNewConnections() {
+        return getPoolProperties().isTestOnConnect() || getPoolProperties().getInitSQL()!=null;
+    }
+
     /**
      * Terminate the current transaction for the given connection.
      * @param con The connection
@@ -898,8 +914,32 @@ public class ConnectionPool {
         if (isClosed()) return true;
         if (!con.validate(action)) return true;
         if (!terminateTransaction(con)) return true;
-        if (con.isMaxAgeExpired()) return true;
-        else return false;
+        return false;
+    }
+
+    /**
+     * Checks whether this connection has {@link PooledConnection#isMaxAgeExpired() expired} and tries to reconnect if it has.
+     *
+     * @return true if the connection was either not expired or expired but reconnecting succeeded,
+     * false if reconnecting failed (either because a new connection could not be established or
+     * validating the newly created connection failed)
+     * @see PooledConnection#isMaxAgeExpired()
+     */
+    protected boolean reconnectIfExpired(PooledConnection con) {
+        if (con.isMaxAgeExpired()) {
+            try {
+                if (log.isDebugEnabled()) log.debug( "Connection ["+this+"] expired because of maxAge, trying to reconnect" );
+                con.reconnect();
+                reconnectedCount.incrementAndGet();
+                if ( isInitNewConnections() && !con.validate( PooledConnection.VALIDATE_INIT)) {
+                    return false;
+                }
+            } catch(Exception e) {
+                log.error("Failed to re-connect connection ["+this+"] that expired because of maxAge",e);
+                return false;
+            }
+        }
+        return true;
     }
 
     /**
@@ -933,7 +973,7 @@ public class ConnectionPool {
                 }
                 if (busy.remove(con)) {
 
-                    if (!shouldClose(con,PooledConnection.VALIDATE_RETURN)) {
+                    if (!shouldClose(con,PooledConnection.VALIDATE_RETURN) && reconnectIfExpired(con)) {
                         con.clearWarnings();
                         con.setStackTrace(null);
                         con.setTimestamp(System.currentTimeMillis());
@@ -1071,6 +1111,15 @@ public class ConnectionPool {
      * Forces a validation of all idle connections if {@link PoolProperties#testWhileIdle} is set.
      */
     public void testAllIdle() {
+        testAllIdle(false);
+    }
+
+    /**
+     * Forces a validation of all idle connections if {@link PoolProperties#testWhileIdle} is set.
+     * @param checkMaxAgeOnly whether to only check {@link PooledConnection#isMaxAgeExpired()} but
+     *                        not invoke {@link PooledConnection#validate(int)}
+     */
+    public void testAllIdle(boolean checkMaxAgeOnly) {
         try {
             if (idle.isEmpty()) return;
             Iterator<PooledConnection> unlocked = idle.iterator();
@@ -1081,7 +1130,14 @@ public class ConnectionPool {
                     //the con been taken out, we can't clean it up
                     if (busy.contains(con))
                         continue;
-                    if (!con.validate(PooledConnection.VALIDATE_IDLE)) {
+
+                    boolean release;
+                    if (checkMaxAgeOnly) {
+                        release = !reconnectIfExpired(con);
+                    } else {
+                        release = !reconnectIfExpired(con) || !con.validate(PooledConnection.VALIDATE_IDLE);
+                    }
+                    if (release) {
                         idle.remove(con);
                         release(con);
                     }
@@ -1469,8 +1525,11 @@ public class ConnectionPool {
                     if (pool.getPoolProperties().getMinIdle() < pool.idle
                             .size())
                         pool.checkIdle();
-                    if (pool.getPoolProperties().isTestWhileIdle())
-                        pool.testAllIdle();
+                    if (pool.getPoolProperties().isTestWhileIdle()) {
+                        pool.testAllIdle(false);
+                    } else if (pool.getPoolProperties().getMaxAge() > 0) {
+                        pool.testAllIdle(true);
+                    }
                 } catch (Exception x) {
                     log.error("", x);
                 }
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 82e3290..f5962e9 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
@@ -699,12 +699,13 @@ public interface PoolConfiguration {
     public void setUseEquals(boolean useEquals);
 
     /**
-     * Time in milliseconds to keep this connection alive even when used.
-     * When a connection is returned to the pool, the pool will check to see if the
-     * ((now - time-when-connected) &gt; maxAge) has been reached, and if so,
-     * it closes the connection rather than returning it to the pool.
+     * Time in milliseconds to keep this connection before reconnecting.
+     * When a connection is idle, returned to the pool or borrowed from the pool, the pool will
+     * check to see if the ((now - time-when-connected) &gt; maxAge) has been reached, and if so,
+     * it reconnects. Note that the age of idle connections will only be checked if
+     * {@link #getTimeBetweenEvictionRunsMillis()} returns a value greater than 0.
      * The default value is 0, which implies that connections will be left open and no
-     * age check will be done upon returning the connection to the pool.
+     * age checks will be done.
      * This is a useful setting for database sessions that leak memory as it ensures that the session
      * will have a finite life span.
      * @return the time in milliseconds a connection will be open for when used
@@ -712,12 +713,13 @@ public interface PoolConfiguration {
     public long getMaxAge();
 
     /**
-     * Time in milliseconds to keep this connection alive even when used.
-     * When a connection is returned to the pool, the pool will check to see if the
-     * ((now - time-when-connected) &gt; maxAge) has been reached, and if so,
-     * it closes the connection rather than returning it to the pool.
+     * Time in milliseconds to keep this connection before reconnecting.
+     * When a connection is idle, returned to the pool or borrowed from the pool, the pool will
+     * check to see if the ((now - time-when-connected) &gt; maxAge) has been reached, and if so,
+     * it reconnects. Note that the age of idle connections will only be checked if
+     * {@link #getTimeBetweenEvictionRunsMillis()} returns a value greater than 0.
      * The default value is 0, which implies that connections will be left open and no
-     * age check will be done upon returning the connection to the pool.
+     * age checks will be done.
      * This is a useful setting for database sessions that leak memory as it ensures that the session
      * will have a finite life span.
      * @param maxAge the time in milliseconds a connection will be open for when used
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 2d995d9..13dee2b 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
@@ -921,6 +921,7 @@ public class PoolProperties implements PoolConfiguration, Cloneable, Serializabl
         result = result || (timer && getSuspectTimeout()>0);
         result = result || (timer && isTestWhileIdle());
         result = result || (timer && getMinEvictableIdleTimeMillis()>0);
+        result = result || (timer && getMaxAge()>0);
         return result;
     }
 
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 e606382..5ca7a38 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
@@ -521,7 +521,11 @@ public class ConnectionPool extends NotificationBroadcasterSupport
 
     @Override
     public void setMaxAge(long maxAge) {
+        boolean wasEnabled = getPoolProperties().isPoolSweeperEnabled();
         getPoolProperties().setMaxAge(maxAge);
+        //make sure the pool is properly configured
+        pool.checkPoolConfiguration(getPoolProperties());
+        poolCleanerAttributeUpdated(wasEnabled);
     }
 
     @Override
@@ -631,10 +635,7 @@ public class ConnectionPool extends NotificationBroadcasterSupport
     public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) {
         boolean wasEnabled = getPoolProperties().isPoolSweeperEnabled();
         getPoolProperties().setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
-        boolean shouldBeEnabled = getPoolProperties().isPoolSweeperEnabled();
-        //make sure pool cleaner starts/stops when it should
-        if (!wasEnabled && shouldBeEnabled) pool.initializePoolCleaner(getPoolProperties());
-        else if (wasEnabled && !shouldBeEnabled) pool.terminatePoolCleaner();
+        poolCleanerAttributeUpdated(wasEnabled);
     }
 
 
@@ -662,10 +663,7 @@ public class ConnectionPool extends NotificationBroadcasterSupport
     public void setRemoveAbandoned(boolean removeAbandoned) {
         boolean wasEnabled = getPoolProperties().isPoolSweeperEnabled();
         getPoolProperties().setRemoveAbandoned(removeAbandoned);
-        boolean shouldBeEnabled = getPoolProperties().isPoolSweeperEnabled();
-        //make sure pool cleaner starts/stops when it should
-        if (!wasEnabled && shouldBeEnabled) pool.initializePoolCleaner(getPoolProperties());
-        else if (wasEnabled && !shouldBeEnabled) pool.terminatePoolCleaner();
+        poolCleanerAttributeUpdated(wasEnabled);
     }
 
 
@@ -673,10 +671,7 @@ public class ConnectionPool extends NotificationBroadcasterSupport
     public void setRemoveAbandonedTimeout(int removeAbandonedTimeout) {
         boolean wasEnabled = getPoolProperties().isPoolSweeperEnabled();
         getPoolProperties().setRemoveAbandonedTimeout(removeAbandonedTimeout);
-        boolean shouldBeEnabled = getPoolProperties().isPoolSweeperEnabled();
-        //make sure pool cleaner starts/stops when it should
-        if (!wasEnabled && shouldBeEnabled) pool.initializePoolCleaner(getPoolProperties());
-        else if (wasEnabled && !shouldBeEnabled) pool.terminatePoolCleaner();
+        poolCleanerAttributeUpdated(wasEnabled);
     }
 
 
@@ -702,10 +697,7 @@ public class ConnectionPool extends NotificationBroadcasterSupport
     public void setTestWhileIdle(boolean testWhileIdle) {
         boolean wasEnabled = getPoolProperties().isPoolSweeperEnabled();
         getPoolProperties().setTestWhileIdle(testWhileIdle);
-        boolean shouldBeEnabled = getPoolProperties().isPoolSweeperEnabled();
-        //make sure pool cleaner starts/stops when it should
-        if (!wasEnabled && shouldBeEnabled) pool.initializePoolCleaner(getPoolProperties());
-        else if (wasEnabled && !shouldBeEnabled) pool.terminatePoolCleaner();
+        poolCleanerAttributeUpdated(wasEnabled);
     }
 
 
@@ -713,6 +705,21 @@ public class ConnectionPool extends NotificationBroadcasterSupport
     public void setTimeBetweenEvictionRunsMillis(int timeBetweenEvictionRunsMillis) {
         boolean wasEnabled = getPoolProperties().isPoolSweeperEnabled();
         getPoolProperties().setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
+        //make sure the pool is properly configured
+        pool.checkPoolConfiguration(getPoolProperties());
+        poolCleanerAttributeUpdated(wasEnabled);
+    }
+
+    /**
+     * Starts/stops pool cleaner thread as necessary after its configuration properties
+     * were updated.
+     *
+     * This method must be called <b>after</b> configuration properties affecting the pool cleaner
+     * have been updated.
+     *
+     * @param wasEnabled whether the pool cleaner was enabled <b>before</b> the configuration change occurred.
+     */
+    private void poolCleanerAttributeUpdated(boolean wasEnabled) {
         boolean shouldBeEnabled = getPoolProperties().isPoolSweeperEnabled();
         //make sure pool cleaner starts/stops when it should
         if (!wasEnabled && shouldBeEnabled) {
@@ -725,7 +732,6 @@ public class ConnectionPool extends NotificationBroadcasterSupport
         }
     }
 
-
     @Override
     public void setUrl(String url) {
         getPoolProperties().setUrl(url);
diff --git a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/test/PoolCleanerTest.java b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/test/PoolCleanerTest.java
index 515fd9b..452c81d 100644
--- a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/test/PoolCleanerTest.java
+++ b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/test/PoolCleanerTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.tomcat.jdbc.test;
 
+import java.sql.Connection;
 import java.util.Map;
 
 import org.junit.Assert;
@@ -96,4 +97,42 @@ public class PoolCleanerTest extends DefaultTestCase {
         Thread.sleep(3000);
         Assert.assertEquals("Pool should have 0 idle.", 0, datasource.getIdle());
     }
-}
+
+    @Test
+    public void testIdleReconnect() throws Exception {
+        // timeBetweenEvictionRunsMillis > maxAge to test that maxAge setting overrides it
+        datasource.getPoolProperties().setTimeBetweenEvictionRunsMillis(20000);
+        datasource.getPoolProperties().setMaxAge( 1000 );
+        datasource.getPoolProperties().setTestWhileIdle(false);
+        datasource.getPoolProperties().setMaxIdle(1);
+        datasource.getPoolProperties().setInitialSize(0);
+        datasource.getPoolProperties().setMinIdle(1);
+
+        Assert.assertEquals(0,datasource.getPool().getReconnectedCount());
+        datasource.getConnection().close();
+        Assert.assertEquals(0,datasource.getPool().getReconnectedCount());
+        Assert.assertEquals("Pool should have 1 idle.", 1, datasource.getIdle());
+        Thread.sleep(4000);
+        Assert.assertEquals("Pool should have 1 idle.", 1, datasource.getIdle());
+        Assert.assertTrue(datasource.getPool().getReconnectedCount()>0);
+    }
+
+    @Test
+    public void testReconnectOnReturn() throws Exception {
+        datasource.getPoolProperties().setMaxAge( 1500 );
+        datasource.getPoolProperties().setTestWhileIdle(false);
+        datasource.getPoolProperties().setMaxIdle(1);
+        datasource.getPoolProperties().setInitialSize(0);
+        datasource.getPoolProperties().setMinIdle(1);
+
+        Assert.assertEquals(0,datasource.getPool().getReconnectedCount());
+        final Connection con = datasource.getConnection();
+        Assert.assertEquals(0,datasource.getPool().getReconnectedCount());
+        Assert.assertEquals("Pool should have 0 idle.", 0, datasource.getIdle());
+        Thread.sleep(4000);
+        Assert.assertEquals(0,datasource.getPool().getReconnectedCount());
+        con.close();
+        Assert.assertEquals("Pool should have 1 idle.", 1, datasource.getIdle());
+        Assert.assertTrue(datasource.getPool().getReconnectedCount()>0);
+    }
+}
\ No newline at end of file
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 30fa02a..9db8f06 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -93,6 +93,15 @@
       </fix>
     </changelog>
   </subsection>
+  <subsection name="jdbc-pool">
+    <changelog>
+      <fix>
+        Improved maxAge handling. Add support for age check on idle connections.
+        Connection that expired reconnects rather than closes it. Patch provided
+        by toby1984. (kfujino)
+      </fix>
+    </changelog>
+  </subsection>
 </section>
 <section name="Tomcat 9.0.17 (markt)" rtext="2019-03-18">
   <subsection name="Catalina">


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org