Skip to content

Commit 3fc0a22

Browse files
committed
Do not reset pool on first exhaustion
1 parent 1e6d86e commit 3fc0a22

File tree

6 files changed

+88
-13
lines changed

6 files changed

+88
-13
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.ebean.datasource;
2+
3+
import java.sql.SQLException;
4+
5+
/**
6+
* This exception is thrown, if the connection pool has reached maxSize.
7+
* @author Roland Praml, Foconis Analytics GmbH
8+
*/
9+
public class ConnectionPoolExhaustedException extends SQLException {
10+
public ConnectionPoolExhaustedException(String reason) {
11+
super(reason);
12+
}
13+
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,15 @@ default DataSourceBuilder heartbeatTimeoutSeconds(int heartbeatTimeoutSeconds) {
409409
@Deprecated(forRemoval = true)
410410
DataSourceBuilder setHeartbeatTimeoutSeconds(int heartbeatTimeoutSeconds);
411411

412+
/**
413+
* Sets the coun how often the heartbeat has to detect pool exhaustion in succession.
414+
* in succession before an error is raised and the pool will be reset.
415+
* <p>
416+
* By default, this value must be multiplied with the sum of heartbeatfreq + waitTimeoutMillis to
417+
* estimate the time, when the pool will be restarted, because all connections were leaked.
418+
*/
419+
DataSourceBuilder heartbeatMaxPoolExhaustedCount(int count);
420+
412421
/**
413422
* Set to true if a stack trace should be captured when obtaining a connection from the pool.
414423
* <p>
@@ -706,6 +715,7 @@ default DataSourceBuilder initDatabaseForPlatform(String platform) {
706715
* <p>
707716
* This is enabled by default. Generally we only want to turn this
708717
* off when using the pool with a Lambda function.
718+
*
709719
* @param validateOnHeartbeat Use false to disable heartbeat validation.
710720
*/
711721
DataSourceBuilder validateOnHeartbeat(boolean validateOnHeartbeat);
@@ -900,6 +910,11 @@ default String driverClassName() {
900910
*/
901911
int getHeartbeatTimeoutSeconds();
902912

913+
/**
914+
* Return the number, how often the heartbeat has to detect pool exhaustion in succession.
915+
*/
916+
int getHeartbeatMaxPoolExhaustedCount();
917+
903918
/**
904919
* Return true if a stack trace should be captured when obtaining a connection from the pool.
905920
* <p>

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public class DataSourceConfig implements DataSourceBuilder.Settings {
6262
private String heartbeatSql;
6363
private int heartbeatFreqSecs = 30;
6464
private int heartbeatTimeoutSeconds = 30;
65+
private int heartbeatMaxPoolExhaustedCount = 10;
6566
private boolean captureStackTrace;
6667
private int maxStackTraceSize = 5;
6768
private int leakTimeMinutes = 30;
@@ -179,10 +180,10 @@ public DataSourceConfig setDefaults(DataSourceBuilder builder) {
179180
if (minConnections == 2 && other.getMinConnections() < 2) {
180181
minConnections = other.getMinConnections();
181182
}
182-
if (!shutdownOnJvmExit && other.isShutdownOnJvmExit()){
183+
if (!shutdownOnJvmExit && other.isShutdownOnJvmExit()) {
183184
shutdownOnJvmExit = true;
184185
}
185-
if (validateOnHeartbeat && !other.isValidateOnHeartbeat()){
186+
if (validateOnHeartbeat && !other.isValidateOnHeartbeat()) {
186187
validateOnHeartbeat = false;
187188
}
188189
if (customProperties == null) {
@@ -466,6 +467,17 @@ public DataSourceConfig setHeartbeatTimeoutSeconds(int heartbeatTimeoutSeconds)
466467
return this;
467468
}
468469

470+
@Override
471+
public int getHeartbeatMaxPoolExhaustedCount() {
472+
return this.heartbeatMaxPoolExhaustedCount;
473+
}
474+
475+
@Override
476+
public DataSourceBuilder heartbeatMaxPoolExhaustedCount(int count) {
477+
this.heartbeatMaxPoolExhaustedCount = count;
478+
return this;
479+
}
480+
469481
@Override
470482
public boolean isCaptureStackTrace() {
471483
return captureStackTrace;

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ final class ConnectionPool implements DataSourcePool {
4646
private final String heartbeatSql;
4747
private final int heartbeatFreqSecs;
4848
private final int heartbeatTimeoutSeconds;
49+
private final int heartbeatMaxPoolExhaustedCount;
50+
4951
private final long trimPoolFreqMillis;
5052
private final int transactionIsolation;
5153
private final boolean autoCommit;
@@ -77,6 +79,7 @@ final class ConnectionPool implements DataSourcePool {
7779
private final int pstmtCacheSize;
7880
private final PooledConnectionQueue queue;
7981
private Timer heartBeatTimer;
82+
private int heartbeatPoolExhaustedCount;
8083
/**
8184
* Used to find and close() leaked connections. Leaked connections are
8285
* thought to be busy but have not been used for some time. Each time a
@@ -112,6 +115,7 @@ final class ConnectionPool implements DataSourcePool {
112115
this.waitTimeoutMillis = params.getWaitTimeoutMillis();
113116
this.heartbeatFreqSecs = params.getHeartbeatFreqSecs();
114117
this.heartbeatTimeoutSeconds = params.getHeartbeatTimeoutSeconds();
118+
this.heartbeatMaxPoolExhaustedCount = params.getHeartbeatMaxPoolExhaustedCount();
115119
this.heartbeatSql = params.getHeartbeatSql();
116120
this.validateOnHeartbeat = params.isValidateOnHeartbeat();
117121
this.trimPoolFreqMillis = 1000L * params.getTrimPoolFreqSecs();
@@ -367,11 +371,19 @@ private void testConnection() {
367371
try {
368372
// Get a connection from the pool and test it
369373
conn = getConnection();
374+
heartbeatPoolExhaustedCount = 0;
370375
if (testConnection(conn)) {
371376
notifyDataSourceIsUp();
372377
} else {
373378
notifyDataSourceIsDown(null);
374379
}
380+
} catch (ConnectionPoolExhaustedException be) {
381+
heartbeatPoolExhaustedCount++;
382+
if (heartbeatPoolExhaustedCount > heartbeatMaxPoolExhaustedCount) {
383+
notifyDataSourceIsDown(be);
384+
} else {
385+
Log.warn("Heartbeat: " + be.getMessage());
386+
}
375387
} catch (SQLException ex) {
376388
notifyDataSourceIsDown(ex);
377389
} finally {
@@ -573,6 +585,7 @@ PooledConnection createConnectionForQueue(int connId) throws SQLException {
573585
* </ul>
574586
*/
575587
private void reset() {
588+
heartbeatPoolExhaustedCount = 0;
576589
queue.reset(leakTimeMinutes);
577590
}
578591

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.ebean.datasource.pool;
22

3+
import io.ebean.datasource.ConnectionPoolExhaustedException;
34
import io.ebean.datasource.PoolStatus;
45
import io.ebean.datasource.pool.ConnectionPool.Status;
56

@@ -271,7 +272,7 @@ private PooledConnection _obtainConnectionWaitLoop() throws SQLException, Interr
271272
dumpBusyConnectionInformation();
272273
}
273274

274-
throw new SQLException(msg);
275+
throw new ConnectionPoolExhaustedException(msg);
275276
}
276277

277278
try {

ebean-datasource/src/test/java/io/ebean/datasource/pool/ConnectionPoolFullTest.java

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,18 @@
44
import io.ebean.datasource.DataSourceBuilder;
55
import io.ebean.datasource.DataSourcePool;
66
import org.junit.jupiter.api.Test;
7-
import org.slf4j.Logger;
8-
import org.slf4j.LoggerFactory;
97

108
import javax.sql.DataSource;
119
import java.sql.Connection;
1210
import java.sql.SQLException;
1311

1412
import static org.assertj.core.api.Assertions.assertThat;
1513

16-
public class ConnectionPoolFullTest implements DataSourceAlert, WaitFor {
17-
18-
private static final Logger log = LoggerFactory.getLogger(ConnectionPoolFullTest.class);
14+
public class ConnectionPoolFullTest implements DataSourceAlert {
1915

2016
private int up;
2117
private int down;
2218

23-
2419
@Test
2520
void testPoolFullWithHeartbeat() throws Exception {
2621

@@ -32,6 +27,7 @@ void testPoolFullWithHeartbeat() throws Exception {
3227
.minConnections(1)
3328
.maxConnections(1)
3429
.trimPoolFreqSecs(1)
30+
.heartbeatMaxPoolExhaustedCount(1)
3531
.alert(this)
3632
.failOnStart(false)
3733
.build();
@@ -40,17 +36,42 @@ void testPoolFullWithHeartbeat() throws Exception {
4036
assertThat(down).isEqualTo(0);
4137

4238
try {
39+
// block the thread for 2 secs. The heartbeat must not shutdown the pool
4340
try (Connection connection = pool.getConnection()) {
44-
// we do a rollback here
45-
System.out.println("So we wait");
41+
System.out.println("waiting 2s");
4642
Thread.sleep(2000);
4743
connection.rollback();
4844
}
45+
assertThat(up).isEqualTo(1);
46+
assertThat(down).isEqualTo(0);
47+
48+
// now block the thread longer, so that exhausted count will be reached
49+
try (Connection connection = pool.getConnection()) {
50+
System.out.println("waiting 4s");
51+
Thread.sleep(4000);
52+
connection.rollback();
53+
}
54+
// we expect, that the pool goes down.
55+
assertThat(up).isEqualTo(1);
56+
assertThat(down).isEqualTo(1);
57+
58+
System.out.println("waiting 2s for recovery");
59+
Thread.sleep(2000);
60+
assertThat(up).isEqualTo(2);
61+
assertThat(down).isEqualTo(1);
62+
63+
// pool should be OK again
64+
try (Connection connection = pool.getConnection()) {
65+
connection.rollback();
66+
}
67+
68+
assertThat(up).isEqualTo(2);
69+
assertThat(down).isEqualTo(1);
70+
71+
4972
} finally {
5073
pool.shutdown();
5174
}
52-
assertThat(up).isEqualTo(1);
53-
assertThat(down).isEqualTo(0);
5475

5576
}
5677

0 commit comments

Comments
 (0)