+ * This can be used to close really old connections. + *
+ */ + public int getMaxAgeMinutes() { + return maxAgeMinutes; + } + + /** + * Set the maximum age a connection can be in minutes. + */ + public void setMaxAgeMinutes(int maxAgeMinutes) { + this.maxAgeMinutes = maxAgeMinutes; + } + /** * Set the time in seconds a connection can be idle after which it can be * trimmed from the pool. @@ -380,7 +415,7 @@ public class DataSourceConfig { public void setOffline(boolean offline) { this.offline = offline; } - + /** * Return a map of custom properties for the jdbc driver connection. */ @@ -420,6 +455,7 @@ public class DataSourceConfig { this.leakTimeMinutes = properties.getInt(prefix + "leakTimeMinutes", 30); this.maxInactiveTimeSecs = properties.getInt(prefix + "maxInactiveTimeSecs", 720); this.trimPoolFreqSecs = properties.getInt(prefix + "trimPoolFreqSecs", 59); + this.maxAgeMinutes = properties.getInt(prefix + "maxAgeMinutes", 0); this.minConnections = properties.getInt(prefix + "minConnections", 0); this.maxConnections = properties.getInt(prefix + "maxConnections", 20); @@ -429,6 +465,7 @@ public class DataSourceConfig { this.waitTimeoutMillis = properties.getInt(prefix + "waitTimeout", 1000); this.heartbeatSql = properties.get(prefix + "heartbeatSql", null); + this.heartbeatTimeoutSeconds = properties.getInt(prefix + "heartbeatTimeoutSeconds", 3); this.poolListener = properties.get(prefix + "poolListener", null); this.offline = properties.getBoolean(prefix + "offline", false); diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServerFactory.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServerFactory.java index 2bf4dc6fe..6aa762474 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServerFactory.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServerFactory.java @@ -410,32 +410,10 @@ public class DefaultServerFactory implements BootupEbeanManager { return null; } - if (dsConfig.getHeartbeatSql() == null) { - // use default heartbeatSql from the DatabasePlatform - String heartbeatSql = getHeartbeatSql(dsConfig.getDriver()); - dsConfig.setHeartbeatSql(heartbeatSql); - } - DataSourceAlert notify = new SimpleDataSourceAlert(); return new DataSourcePool(notify, config.getName(), dsConfig); } - /** - * Return a heartbeatSql depending on the jdbc driver name. - */ - private String getHeartbeatSql(String driver) { - if (driver != null) { - String d = driver.toLowerCase(); - if (d.contains("oracle")) { - return "select 'x' from dual"; - } - if (d.contains(".h2.") || d.contains(".mysql.") || d.contains("postgre")) { - return "select 1"; - } - } - return null; - } - /** * Check the autoCommit and Transaction Isolation levels of the DataSource. *diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/BusyConnectionBuffer.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/BusyConnectionBuffer.java index 98b55d682..98b50346b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/BusyConnectionBuffer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/BusyConnectionBuffer.java @@ -1,9 +1,6 @@ package com.avaje.ebeaninternal.server.lib.sql; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,7 +50,7 @@ class BusyConnectionBuffer { /** * We can only grow (not shrink) the capacity. */ - private void setCapacity(int newCapacity) { + protected void setCapacity(int newCapacity) { if (newCapacity > slots.length){ PooledConnection[] current = this.slots; this.slots = new PooledConnection[newCapacity]; @@ -74,12 +71,7 @@ class BusyConnectionBuffer { } protected boolean isEmpty() { - for (int i = 0; i < slots.length; i++) { - if (slots[i] != null) { - return false; - } - } - return true; + return size == 0; } protected int add(PooledConnection pc){ @@ -120,22 +112,70 @@ class BusyConnectionBuffer { } /** - * Get a shallow read only List of the busy connections. - *
- * Note that the {@link #remove(PooledConnection)} MUST be used to remove PooledConnections. - *
- * @return + * Close connections that should be considered leaked. */ - protected List@@ -378,17 +402,19 @@ public class DataSourcePool implements DataSource { *
*/ public void checkDataSource() { + + // first trim idle connections + trimIdleConnections(); + Connection conn = null; try { // Get a connection from the pool and test it conn = getConnection(); - testConnection(conn); - - notifyDataSourceIsUp(); - - if (System.currentTimeMillis() > (lastTrimTime + (trimPoolFreqSecs * 1000))) { - queue.trim(maxInactiveTimeSecs); - lastTrimTime = System.currentTimeMillis(); + if (testConnection(conn)) { + notifyDataSourceIsUp(); + + } else { + notifyDataSourceIsDown(null); } } catch (SQLException ex) { @@ -491,33 +517,38 @@ public class DataSourcePool implements DataSource { return waitTimeoutMillis; } - /** - * Set the time after which inactive connections are trimmed. - */ - public void setMaxInactiveTimeSecs(int maxInactiveTimeSecs) { - this.maxInactiveTimeSecs = maxInactiveTimeSecs; - } - /** * Return the time after which inactive connections are trimmed. */ - public int getMaxInactiveTimeSecs() { - return maxInactiveTimeSecs; + public int getMaxInactiveMillis() { + return maxInactiveMillis; + } + + /** + * Return the maximum age a connection is allowed to be before it is trimmed + * out of the pool. This value can be 0 which means there is no maximum age. + */ + public long getMaxAgeMillis() { + return maxAgeMillis; } - private void testConnection(Connection conn) throws SQLException { + private boolean testConnection(Connection conn) throws SQLException { if (heartbeatsql == null) { - return; + return conn.isValid(heartbeatTimeoutSeconds); } Statement stmt = null; ResultSet rset = null; try { - // It should only error IF the DataSource is down ? (or a network - // issue?) + // It should only error IF the DataSource is down or a network issue stmt = conn.createStatement(); + if (heartbeatTimeoutSeconds > 0) { + stmt.setQueryTimeout(heartbeatTimeoutSeconds); + } rset = stmt.executeQuery(heartbeatsql); conn.commit(); + + return true; } finally { try { @@ -543,13 +574,7 @@ public class DataSourcePool implements DataSource { */ protected boolean validateConnection(PooledConnection conn) { try { - if (heartbeatsql == null) { - logger.debug("Can not test connection as heartbeatsql is not set"); - return false; - } - - testConnection(conn); - return true; + return testConnection(conn); } catch (Exception e) { logger.warn("heartbeatsql test failed on connection[" + conn.getName() + "]"); @@ -647,7 +672,6 @@ public class DataSourcePool implements DataSource { notifyDataSourceIsDown(ex); throw ex; } - } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/FreeConnectionBuffer.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/FreeConnectionBuffer.java index 6186aebd8..8fede2fc2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/FreeConnectionBuffer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/FreeConnectionBuffer.java @@ -1,179 +1,106 @@ package com.avaje.ebeaninternal.server.lib.sql; import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.avaje.ebeaninternal.server.lib.sql.PooledConnectionStatistics.LoadValues; /** * A buffer designed especially to hold free pooled connections. *- * It is circular in nature. - *
- ** All thread safety controlled externally (by PooledConnectionQueue). *
- * - * @author rbygrave - * */ class FreeConnectionBuffer { - /** - * The buffer itself. - */ - private PooledConnection[] conns; + private static final Logger logger = LoggerFactory.getLogger(FreeConnectionBuffer.class); + + /** + * Buffer oriented for add and remove. + */ + private final LinkedList
@@ -117,8 +141,6 @@ public class PooledConnection extends ConnectionDelegator {
private long exeStartNanos;
- private final PooledConnectionStatistics stats = new PooledConnectionStatistics();
-
/**
* The last statement executed by this connection.
*/
@@ -273,7 +295,7 @@ public class PooledConnection extends ConnectionDelegator {
}
if (logger.isDebugEnabled()) {
- logger.debug("Closing Connection[{}] slot[{}] Stats: {} , PstmtStats: {} ", name, slotId, stats.getValues(false), pstmtCache.getDescription());
+ logger.debug("Closing Connection[{}] slot[{}] reason[{}] stats: {} , pstmtStats: {} ", name, slotId, closeReason, stats.getValues(false), pstmtCache.getDescription());
}
try {
@@ -290,11 +312,9 @@ public class PooledConnection extends ConnectionDelegator {
}
try {
- Iterator
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnectionQueue.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnectionQueue.java
index 1076f9ad0..e355394d1 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnectionQueue.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnectionQueue.java
@@ -1,7 +1,6 @@
package com.avaje.ebeaninternal.server.lib.sql;
import java.sql.SQLException;
-import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@@ -55,10 +54,12 @@ public class PooledConnectionQueue {
private int connectionId;
- private long waitTimeoutMillis;
-
- private long leakTimeMinutes;
+ private final long waitTimeoutMillis;
+ private final long leakTimeMinutes;
+
+ private final long maxAgeMillis;
+
private int warningSize;
private int maxSize;
@@ -103,9 +104,10 @@ public class PooledConnectionQueue {
this.warningSize = pool.getWarningSize();
this.waitTimeoutMillis = pool.getWaitTimeoutMillis();
this.leakTimeMinutes = pool.getLeakTimeMinutes();
-
+ this.maxAgeMillis = pool.getMaxAgeMillis();
+
this.busyList = new BusyConnectionBuffer(maxSize, 20);
- this.freeList = new FreeConnectionBuffer(maxSize);
+ this.freeList = new FreeConnectionBuffer();
this.lock = new ReentrantLock(false);
this.notEmpty = lock.newCondition();
@@ -191,7 +193,7 @@ public class PooledConnectionQueue {
if (maxSize < this.minSize){
throw new IllegalArgumentException("maxSize "+maxSize+" < minSize "+this.minSize);
}
- freeList.setCapacity(maxSize);
+ this.busyList.setCapacity(maxSize);
this.maxSize = maxSize;
} finally {
lock.unlock();
@@ -244,8 +246,9 @@ public class PooledConnectionQueue {
if (!busyList.remove(c)) {
logger.error("Connection [{}] not found in BusyList? ", c);
}
- if (c.getCreationTime() <= lastResetTime) {
+ if (c.shouldTrimOnReturn(lastResetTime, maxAgeMillis)) {
c.closeConnectionFully(false);
+
} else {
freeList.add(c);
notEmpty.signal();
@@ -310,9 +313,9 @@ public class PooledConnectionQueue {
PooledConnection c = pool.createConnectionForQueue(connectionId++);
int busySize = registerBusyConnection(c);
- String msg = "DataSourcePool [" + name + "] grow; id["+c.getName()+"] busy["+busySize+"] max["+maxSize+"]";
- logger.debug(msg);
-
+ if (logger.isDebugEnabled()) {
+ logger.debug("DataSourcePool [{}] grow; id[{}] busy[{}] max[{}]", name, c.getName(), busySize, maxSize);
+ }
checkForWarningSize();
return c;
}
@@ -372,7 +375,7 @@ public class PooledConnectionQueue {
doingShutdown = true;
Status status = createStatus();
DataSourcePoolStatistics statistics = pool.getStatistics(false);
- logger.debug("DataSourcePool [" + name + "] shutdown {} - Statistics {}", status, statistics);
+ logger.debug("DataSourcePool [{}] shutdown {} - Statistics {}", name, status, statistics);
closeFreeConnections(true);
@@ -398,7 +401,7 @@ public class PooledConnectionQueue {
lock.lock();
try {
Status status = createStatus();
- logger.info("Reseting DataSourcePool [" + name + "] "+status);
+ logger.info("Reseting DataSourcePool [{}] {}", name, status);
lastResetTime = System.currentTimeMillis();
closeFreeConnections(false);
@@ -411,12 +414,16 @@ public class PooledConnectionQueue {
}
}
- public void trim(int maxInactiveTimeSecs) throws SQLException {
+ public void trim(long maxInactiveMillis, long maxAgeMillis) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
- if (trimInactiveConnections(maxInactiveTimeSecs) > 0) {
- ensureMinimumConnections();
+ if (trimInactiveConnections(maxInactiveMillis, maxAgeMillis) > 0) {
+ try {
+ ensureMinimumConnections();
+ } catch (SQLException e) {
+ logger.error("Error trying to ensure minimum connections", e);
+ }
}
} finally {
lock.unlock();
@@ -426,11 +433,12 @@ public class PooledConnectionQueue {
/**
* Trim connections that have been not used for some time.
*/
- private int trimInactiveConnections(int maxInactiveTimeSecs) {
+ private int trimInactiveConnections(long maxInactiveMillis, long maxAgeMillis) {
- long usedSince = System.currentTimeMillis() - (maxInactiveTimeSecs * 1000);
-
- int trimedCount = freeList.trim(usedSince);
+ long usedSince = System.currentTimeMillis() - maxInactiveMillis;
+ long createdSince = (maxAgeMillis == 0) ? 0 : System.currentTimeMillis() - maxAgeMillis;
+
+ int trimedCount = freeList.trim(usedSince, createdSince);
if (trimedCount > 0) {
logger.debug("DataSourcePool [{}] trimmed [{}] inactive connections. New size[{}]", name, trimedCount, totalConnections());
}
@@ -467,42 +475,11 @@ public class PooledConnectionQueue {
final ReentrantLock lock = this.lock;
lock.lock();
try {
-
- long olderThanTime = System.currentTimeMillis() - (leakTimeMinutes*60000);
-
- List