From d16f33d26bb31ed82bac90e1461e4295c29fe876 Mon Sep 17 00:00:00 2001
From: Rob Bygrave
@@ -21,6 +26,8 @@ import java.util.List;
*/
class BusyConnectionBuffer {
+ private static final Logger logger = LoggerFactory.getLogger(BusyConnectionBuffer.class);
+
private PooledConnection[] slots;
private int growBy;
@@ -66,8 +73,13 @@ class BusyConnectionBuffer {
return size;
}
- protected boolean isEmpty(){
- return size == 0;
+ protected boolean isEmpty() {
+ for (int i = 0; i < slots.length; i++) {
+ if (slots[i] != null) {
+ return false;
+ }
+ }
+ return true;
}
protected int add(PooledConnection pc){
@@ -75,23 +87,37 @@ class BusyConnectionBuffer {
// grow the capacity
setCapacity(slots.length + growBy);
}
- ++size;
int slot = nextEmptySlot();
pc.setSlotId(slot);
slots[slot] = pc;
- return size;
+ return ++size;
}
protected boolean remove(PooledConnection pc) {
- --size;
+
int slotId = pc.getSlotId();
if (slots[slotId] != pc){
+ PooledConnection heldBy = slots[slotId];
+ logger.warn("Failed to remove from slot[{}] PooledConnection[{}] - HeldBy[{}]", pc.getSlotId(), pc, heldBy);
return false;
}
slots[slotId] = null;
+ --size;
return true;
}
+ /**
+ * Collect the load statistics from all the busy connections.
+ * @param reset
+ */
+ protected void collectStatistics(LoadValues values, boolean reset) {
+
+ for (int i = 0; i < slots.length; i++) {
+ if (slots[i] != null){
+ values.plus(slots[i].getStatistics().getValues(reset));
+ }
+ }
+ }
/**
* Get a shallow read only List of the busy connections.
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePool.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePool.java
index c54d5582e..3cb861427 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePool.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePool.java
@@ -15,11 +15,12 @@ import java.util.Set;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
-import com.avaje.ebean.config.DataSourceConfig;
-import com.avaje.ebeaninternal.api.ClassUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.avaje.ebean.config.DataSourceConfig;
+import com.avaje.ebeaninternal.api.ClassUtil;
+
/**
* A robust DataSource.
*
@@ -73,7 +74,7 @@ public class DataSourcePool implements DataSource {
* The sql used to test a connection.
*/
private final String heartbeatsql;
-
+
private final int heartbeatFreqSecs;
/**
@@ -573,7 +574,15 @@ public class DataSourcePool implements DataSource {
}
queue.returnPooledConnection(pooledConnection);
}
-
+
+ /**
+ * Collect statistics of a connection that is fully closing
+ */
+ protected void reportClosingConnection(PooledConnection pooledConnection) {
+
+ queue.reportClosingConnection(pooledConnection);
+ }
+
/**
* Returns information describing connections that are currently being used.
*/
@@ -834,6 +843,14 @@ public class DataSourcePool implements DataSource {
public Status getStatus(boolean reset) {
return queue.getStatus(reset);
}
+
+ /**
+ * Return the aggregated load statistics collected on all the connections in the pool.
+ */
+ public DataSourcePoolStatistics getStatistics(boolean reset) {
+
+ return queue.getStatistics(reset);
+ }
/**
* Deregister the JDBC driver.
@@ -873,8 +890,8 @@ public class DataSourcePool implements DataSource {
}
public String toString() {
- return "min:" + minSize + " max:" + maxSize + " free:" + free + " busy:" + busy + " waiting:" + waiting
- + " highWaterMark:" + highWaterMark + " waitCount:" + waitCount + " hitCount:" + hitCount;
+ return "min[" + minSize + "] max[" + maxSize + "] free[" + free + "] busy[" + busy + "] waiting[" + waiting
+ + "] highWaterMark[" + highWaterMark + "] waitCount[" + waitCount + "] hitCount[" + hitCount+"]";
}
/**
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePoolStatistics.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePoolStatistics.java
new file mode 100644
index 000000000..5bdc1c9ad
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePoolStatistics.java
@@ -0,0 +1,97 @@
+package com.avaje.ebeaninternal.server.lib.sql;
+
+/**
+ * Represents aggregated statistics collected from the DataSourcePool.
+ *
+ * The goal is to present insight into the overload load of the DataSourcePool.
+ * These statistics can be collected and reported regularly to show load over
+ * time.
+ *
+ * Each pooled connection collects statistics. When a pooled connection is fully
+ * closed it can report it's statistics to the pool to be included as part of
+ * the collected statistics.
+ *
@@ -48,11 +50,31 @@ class FreeConnectionBuffer {
* Add at connection.
*/
protected void add(PooledConnection pc) {
+ if (conns[addIndex] != null) {
+ throw new RuntimeException("Buffer slot ["+addIndex+"] already full?");
+ }
conns[addIndex] = pc;
addIndex = inc(addIndex);
++size;
}
+ protected void closeAll(boolean logErrors) {
+
+ final PooledConnection[] items = this.conns;
+
+ this.conns = new PooledConnection[items.length];
+ this.size = 0;
+ this.removeIndex = 0;
+ this.addIndex = 0;
+
+ for (int i = 0; i < items.length; i++) {
+ PooledConnection c = items[i];
+ if (c != null) {
+ c.closeConnectionFully(logErrors);
+ }
+ }
+ }
+
/**
* Remove a connection at current remove position.
*/
@@ -79,6 +101,18 @@ class FreeConnectionBuffer {
return copy;
}
+ /**
+ * Collect the load statistics from all the free connections.
+ */
+ protected void collectStatistics(LoadValues values, boolean reset) {
+
+ for (int i = 0; i < conns.length; i++) {
+ if (conns[i] != null){
+ values.plus(conns[i].getStatistics().getValues(reset));
+ }
+ }
+ }
+
/**
* Set the free list to be the connections in this copy. This is done after
* unused connections have been trimmed.
diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnection.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnection.java
index 5b29b7fb5..d6c3293bc 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnection.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PooledConnection.java
@@ -34,8 +34,7 @@ import org.slf4j.LoggerFactory;
* statement that was executed. Keeps statistics on how long it is in use.
*
- * Used to help finding connection pool leaks. - *
- */ - public String getCreatedByMethod() { - if (createdByMethod != null) { - return createdByMethod; - } - if (stackTrace == null) { - return null; - } + public CallableStatement prepareCall(String s, int i, int x, int y) throws SQLException { + try { + return connection.prepareCall(s, i, x, y); + } catch (SQLException ex) { + addError(ex); + throw ex; + } + } - for (int j = 0; j < stackTrace.length; j++) { - String methodLine = stackTrace[j].toString(); - if (skipElement(methodLine)) { - // ignore these methods... - } else { - createdByMethod = methodLine; - return createdByMethod; - } - } + /** + * Returns the method that created the connection. + *+ * Used to help finding connection pool leaks. + *
+ */ + public String getCreatedByMethod() { + if (createdByMethod != null) { + return createdByMethod; + } + if (stackTrace == null) { + return null; + } - return null; - } + for (int j = 0; j < stackTrace.length; j++) { + String methodLine = stackTrace[j].toString(); + if (skipElement(methodLine)) { + // ignore these methods... + } else { + createdByMethod = methodLine; + return createdByMethod; + } + } - private boolean skipElement(String methodLine) { - if (methodLine.startsWith("java.lang.")) { - return true; - } else if (methodLine.startsWith("java.util.")) { - return true; - } else if (methodLine.startsWith("com.avaje.ebeaninternal.server.query.CallableQuery.+ * These are aggregated up to get a total for the DataSourcePool. + *
+ */ + public static class LoadValues { + + private long collectionStart; + private long count; + private long errorCount; + private long hwmMicros; + private long totalMicros; + + public LoadValues() { + } + + public LoadValues(long collectionStart, long count, long errorCount, long hwmMicros, long totalMicros) { + this.collectionStart = collectionStart; + this.count = count; + this.errorCount = errorCount; + this.hwmMicros = hwmMicros; + this.totalMicros = totalMicros; + } + + public void plus(LoadValues additional) { + collectionStart = (collectionStart == 0) ? additional.collectionStart : Math.min(collectionStart, additional.collectionStart); + count += additional.count; + errorCount += additional.errorCount; + hwmMicros = Math.max(hwmMicros, additional.hwmMicros); + totalMicros += additional.totalMicros; + } + + public String toString() { + return "count["+count+"] errors["+errorCount+"] totalMicros["+totalMicros+"] hwmMicros["+hwmMicros+"] avgMicros["+getAvgMicros()+"]"; + } + + public long getCollectionStart() { + return collectionStart; + } + + public long getCount() { + return count; + } + + public long getErrorCount() { + return errorCount; + } + + public long getHwmMicros() { + return hwmMicros; + } + + public long getTotalMicros() { + return totalMicros; + } + + public long getAvgMicros() { + return (count == 0) ? 0 : totalMicros/count; + } + } + + + + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PstmtCache.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PstmtCache.java index 065d3c4ce..f28bfe46d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PstmtCache.java +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/PstmtCache.java @@ -14,37 +14,37 @@ public class PstmtCache extends LinkedHashMap+ * This defaults to 59 seconds meaning that the pool trim check will run every + * minute assuming the heart beat check runs every 30 seconds. + *
+ */ + public int getTrimPoolFreqSecs() { + return trimPoolFreqSecs; + } + + /** + * Set the minimum trim gap between pool trim checks. + */ + public void setTrimPoolFreqSecs(int trimPoolFreqSecs) { + this.trimPoolFreqSecs = trimPoolFreqSecs; + } + /** * Return the pool listener. */ @@ -388,18 +409,17 @@ public class DataSourceConfig { this.username = properties.get(prefix + "username", null); this.password = properties.get(prefix + "password", null); - String v; + String dbDriver = properties.get(prefix + "databaseDriver", null); + this.driver = properties.get(prefix + "driver", dbDriver); - v = properties.get(prefix + "databaseDriver", null); - this.driver = properties.get(prefix + "driver", v); - - v = properties.get(prefix + "databaseUrl", null); - this.url = properties.get(prefix + "url", v); + String dbUrl = properties.get(prefix + "databaseUrl", null); + this.url = properties.get(prefix + "url", dbUrl); this.captureStackTrace = properties.getBoolean(prefix + "captureStackTrace", false); this.maxStackTraceSize = properties.getInt(prefix + "maxStackTraceSize", 5); this.leakTimeMinutes = properties.getInt(prefix + "leakTimeMinutes", 30); - this.maxInactiveTimeSecs = properties.getInt(prefix + "maxInactiveTimeSecs", 900); + this.maxInactiveTimeSecs = properties.getInt(prefix + "maxInactiveTimeSecs", 720); + this.trimPoolFreqSecs = properties.getInt(prefix + "trimPoolFreqSecs", 59); this.minConnections = properties.getInt(prefix + "minConnections", 0); this.maxConnections = properties.getInt(prefix + "maxConnections", 20); diff --git a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePool.java b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePool.java index 3cb861427..0dff23448 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePool.java +++ b/src/main/java/com/avaje/ebeaninternal/server/lib/sql/DataSourcePool.java @@ -77,6 +77,8 @@ public class DataSourcePool implements DataSource { private final int heartbeatFreqSecs; + private final int trimPoolFreqSecs; + /** * The transaction isolation level as per java.sql.Connection. */ @@ -181,6 +183,7 @@ public class DataSourcePool implements DataSource { this.waitTimeoutMillis = params.getWaitTimeoutMillis(); this.heartbeatsql = params.getHeartbeatSql(); this.heartbeatFreqSecs = params.getHeartbeatFreqSecs(); + this.trimPoolFreqSecs = params.getTrimPoolFreqSecs(); queue = new PooledConnectionQueue(this); @@ -383,7 +386,7 @@ public class DataSourcePool implements DataSource { notifyDataSourceIsUp(); - if (System.currentTimeMillis() > (lastTrimTime + (maxInactiveTimeSecs * 1000))) { + if (System.currentTimeMillis() > (lastTrimTime + (trimPoolFreqSecs * 1000))) { queue.trim(maxInactiveTimeSecs); lastTrimTime = System.currentTimeMillis(); } 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 813740633..6186aebd8 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 @@ -19,10 +19,19 @@ import com.avaje.ebeaninternal.server.lib.sql.PooledConnectionStatistics.LoadVal */ class FreeConnectionBuffer { + /** + * The buffer itself. + */ private PooledConnection[] conns; + /** + * The position in the buffer where the next connection is removed from. + */ private int removeIndex; + /** + * Position in the buffer where the next connection is added to. + */ private int addIndex; /** @@ -51,13 +60,16 @@ class FreeConnectionBuffer { */ protected void add(PooledConnection pc) { if (conns[addIndex] != null) { - throw new RuntimeException("Buffer slot ["+addIndex+"] already full?"); + throw new IllegalStateException("Buffer slot ["+addIndex+"] already full?"); } conns[addIndex] = pc; addIndex = inc(addIndex); ++size; } + /** + * Close all connections in this buffer. + */ protected void closeAll(boolean logErrors) { final PooledConnection[] items = this.conns; @@ -88,19 +100,25 @@ class FreeConnectionBuffer { } /** - * Return a shallow copy of the free connections. + * Trim any inactive connections that have not been used since usedSince. */ - protected List- * Not a particularly performant approach but this should not be called very - * often - *
+ * Return a shallow copy of the free connections. */ - protected void setShallowCopy(List+ * 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