Skip to content

Commit a9c5723

Browse files
committed
Add validation on stale connections obtained from idle connections
With Lambda we might prefer this connection validation over periodic validation performed by the heartbeat.
1 parent 36f59e1 commit a9c5723

File tree

4 files changed

+72
-5
lines changed

4 files changed

+72
-5
lines changed

ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceConfig.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,7 @@ public DataSourceConfig validateOnHeartbeat(boolean validateOnHeartbeat) {
719719
@Override
720720
public DataSourceBuilder useLambdaCheck(boolean useLambda) {
721721
this.useLambdaCheck = useLambda;
722+
this.validateOnHeartbeat = false;
722723
return this;
723724
}
724725

@@ -896,4 +897,12 @@ public Properties connectionProperties() {
896897
}
897898
return connectionProps;
898899
}
900+
901+
public long validateStaleMillis() {
902+
if (validateOnHeartbeat) {
903+
return 0L;
904+
} else {
905+
return (maxInactiveTimeSecs + trimPoolFreqSecs) * 1_000L;
906+
}
907+
}
899908
}

ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ final class ConnectionPool implements DataSourcePool {
5151
private final boolean readOnly;
5252
private final boolean failOnStart;
5353
private final int maxInactiveMillis;
54+
private final long validateStaleMillis;
5455
/**
5556
* Max age a connection is allowed in millis.
5657
* A value of 0 means no limit (no trimming based on max age).
@@ -118,6 +119,7 @@ final class ConnectionPool implements DataSourcePool {
118119
this.heartbeatSql = params.getHeartbeatSql();
119120
this.validateOnHeartbeat = params.isValidateOnHeartbeat();
120121
this.trimPoolFreqMillis = 1000L * params.getTrimPoolFreqSecs();
122+
this.validateStaleMillis = params.validateStaleMillis();
121123
this.applicationName = params.getApplicationName();
122124
this.clientInfo = params.getClientInfo();
123125
this.queue = new PooledConnectionQueue(this);
@@ -504,6 +506,14 @@ long maxAgeMillis() {
504506
return maxAgeMillis;
505507
}
506508

509+
/**
510+
* When obtaining a connection that has been idle for longer than maxInactiveMillis
511+
* perform a validation test on the connection before giving it to the application.
512+
*/
513+
long validateStaleMillis() {
514+
return validateStaleMillis;
515+
}
516+
507517
private boolean testConnection(Connection conn) throws SQLException {
508518
if (heartbeatSql == null) {
509519
return conn.isValid(heartbeatTimeoutSeconds);
@@ -529,7 +539,7 @@ boolean validateConnection(PooledConnection conn) {
529539
try {
530540
return testConnection(conn);
531541
} catch (Exception e) {
532-
Log.warn("Heartbeat test failed on connection:{0} message: {1}", conn.name(), e.getMessage());
542+
Log.warn("Validation test failed on connection:{0} message: {1}", conn.name(), e.getMessage());
533543
return false;
534544
}
535545
}

ebean-datasource/src/main/java/io/ebean/datasource/pool/PooledConnectionQueue.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ final class PooledConnectionQueue {
6262
*/
6363
private long lastResetTime;
6464
private boolean doingShutdown;
65+
private final long validateStaleMillis;
6566

6667
PooledConnectionQueue(ConnectionPool pool) {
6768
this.pool = pool;
@@ -72,6 +73,7 @@ final class PooledConnectionQueue {
7273
this.waitTimeoutMillis = pool.waitTimeoutMillis();
7374
this.leakTimeMinutes = pool.leakTimeMinutes();
7475
this.maxAgeMillis = pool.maxAgeMillis();
76+
this.validateStaleMillis = pool.validateStaleMillis();
7577
this.busyList = new BusyConnectionBuffer(maxSize, 20);
7678
this.freeList = new FreeConnectionBuffer();
7779
this.lock = new ReentrantLock(false);
@@ -184,11 +186,32 @@ void returnPooledConnection(PooledConnection c, boolean forceClose) {
184186
}
185187

186188
private PooledConnection extractFromFreeList() {
187-
PooledConnection c = freeList.remove();
189+
if (freeList.isEmpty()) {
190+
return null;
191+
}
192+
final PooledConnection c = freeList.remove();
193+
if (validateStaleMillis > 0 && staleEviction(c)) {
194+
c.closeConnectionFully(false);
195+
return null;
196+
}
188197
registerBusyConnection(c);
189198
return c;
190199
}
191200

201+
private boolean staleEviction(PooledConnection c) {
202+
if (!stale(c)) {
203+
return false;
204+
}
205+
if (Log.isLoggable(DEBUG)) {
206+
Log.debug("stale connection validation millis:{0}", (System.currentTimeMillis() - c.lastUsedTime()));
207+
}
208+
return !pool.validateConnection(c);
209+
}
210+
211+
private boolean stale(PooledConnection c) {
212+
return c.lastUsedTime() < System.currentTimeMillis() - validateStaleMillis;
213+
}
214+
192215
PooledConnection obtainConnection() throws SQLException {
193216
try {
194217
PooledConnection pc = _obtainConnection();
@@ -224,9 +247,9 @@ private PooledConnection _obtainConnection() throws InterruptedException, SQLExc
224247
hitCount++;
225248
// are other threads already waiting? (they get priority)
226249
if (waitingThreads == 0) {
227-
if (!freeList.isEmpty()) {
228-
// we have a free connection to return
229-
return extractFromFreeList();
250+
PooledConnection freeConnection = extractFromFreeList();
251+
if (freeConnection != null) {
252+
return freeConnection;
230253
}
231254
if (busyList.size() < maxSize) {
232255
// grow the connection pool

ebean-datasource/src/test/java/io/ebean/datasource/test/FactoryTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,31 @@ void readOnly() throws Exception {
5353
pool.shutdown();
5454
}
5555

56+
@Test
57+
void staleValidate() throws Exception {
58+
DataSourcePool pool = DataSourcePool.builder()
59+
.name("staleValidate")
60+
.url("jdbc:h2:mem:heartbeatTrimOnly")
61+
.username("sa")
62+
.password("")
63+
.readOnly(true)
64+
.autoCommit(true)
65+
.heartbeatFreqSecs(1)
66+
.trimPoolFreqSecs(1)
67+
.maxInactiveTimeSecs(1)
68+
.validateOnHeartbeat(false)
69+
// .useLambdaCheck(true)
70+
.build();
71+
72+
73+
Thread.sleep(3000);
74+
Connection connection1 = pool.getConnection();
75+
connection1.close();
76+
System.out.println("done");
77+
Thread.sleep(2000);
78+
pool.shutdown();
79+
}
80+
5681
// @Disabled
5782
@Test
5883
void heartbeatTrimOnly() throws Exception {

0 commit comments

Comments
 (0)