Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
java-version: ${{ matrix.java_version }}
distribution: 'zulu'
- name: Maven cache
uses: actions/cache@v2
uses: actions/cache@v4
env:
cache-name: maven-cache
with:
Expand Down
2 changes: 1 addition & 1 deletion ebean-datasource-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-datasource-parent</artifactId>
<groupId>io.ebean</groupId>
<version>9.3-SNAPSHOT</version>
<version>9.3-FOC4-SNAPSHOT</version>
</parent>

<artifactId>ebean-datasource-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,27 @@ default DataSourceBuilder initDatabaseForPlatform(String platform) {
*/
DataSourceBuilder loadSettings(Properties properties, String poolName);


/**
* When enabled, the datasource enforces a clean close. This means, if you close a possible dirty
* connection, that was not committed or rolled back, an exception is thrown.
* <p>
* When disabled, the situation is logged as warning.
* <p>
* This option has no effect on readonly or autocommit connections.
* <p>
* Note: It is recommended to enable this option in tests/test systems to find possible
* programming errors. See https://github.com/ebean-orm/ebean-datasource/issues/116 for details.
*/
DataSourceBuilder enforceCleanClose(boolean enforceCleanClose);

/**
* When <code>true</code>, an exception is thrown when a dirty connection is closed.
* <p>
* See {@link #enforceCleanClose(boolean)}.
*/
boolean enforceCleanClose();

/**
* The settings of the DataSourceBuilder. Provides getters/accessors
* to read the configured properties of this DataSourceBuilder.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public class DataSourceConfig implements DataSourceBuilder.Settings {
private String applicationName;
private boolean shutdownOnJvmExit;
private boolean validateOnHeartbeat = !System.getenv().containsKey("LAMBDA_TASK_ROOT");
private boolean enforceCleanClose;

@Override
public Settings settings() {
Expand Down Expand Up @@ -144,6 +145,7 @@ public DataSourceConfig copy() {
copy.initSql = initSql;
copy.alert = alert;
copy.listener = listener;
copy.enforceCleanClose = enforceCleanClose;
return copy;
}

Expand Down Expand Up @@ -798,6 +800,8 @@ private void loadSettings(ConfigPropertiesHelper properties) {
offline = properties.getBoolean("offline", offline);
shutdownOnJvmExit = properties.getBoolean("shutdownOnJvmExit", shutdownOnJvmExit);
validateOnHeartbeat = properties.getBoolean("validateOnHeartbeat", validateOnHeartbeat);
enforceCleanClose = properties.getBoolean("enforceCleanClose", enforceCleanClose);


String isoLevel = properties.get("isolationLevel", _isolationLevel(isolationLevel));
this.isolationLevel = _isolationLevel(isoLevel);
Expand Down Expand Up @@ -846,6 +850,17 @@ Map<String, String> parseCustom(String customProperties) {
return propertyMap;
}

@Override
public DataSourceBuilder enforceCleanClose(boolean enforceCleanClose) {
this.enforceCleanClose = enforceCleanClose;
return this;
}

@Override
public boolean enforceCleanClose() {
return enforceCleanClose;
}

/**
* Return the isolation level description from the associated Connection int value.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ public interface DataSourcePoolListener {
/**
* Called after a connection has been retrieved from the connection pool
*/
void onAfterBorrowConnection(Connection connection);
default void onAfterBorrowConnection(Connection connection) {}

/**
* Called before a connection will be put back to the connection pool
*/
void onBeforeReturnConnection(Connection connection);
default void onBeforeReturnConnection(Connection connection) {}


}
11 changes: 9 additions & 2 deletions ebean-datasource/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>io.ebean</groupId>
<artifactId>ebean-datasource-parent</artifactId>
<version>9.3-SNAPSHOT</version>
<version>9.3-FOC4-SNAPSHOT</version>
</parent>

<artifactId>ebean-datasource</artifactId>
Expand All @@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-datasource-api</artifactId>
<version>9.3-SNAPSHOT</version>
<version>9.3-FOC4-SNAPSHOT</version>
</dependency>

<dependency>
Expand Down Expand Up @@ -76,6 +76,13 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.ibm.db2</groupId>
<artifactId>jcc</artifactId>
<version>11.5.9.0</version>
<scope>test</scope>
</dependency>

</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.ebean.datasource.pool;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* A buffer especially designed for Busy PooledConnections.
Expand Down Expand Up @@ -82,9 +84,10 @@ boolean remove(PooledConnection pc) {
}

/**
* Close connections that should be considered leaked.
* Remove connections that should be considered leaked.
*/
void closeBusyConnections(long leakTimeMinutes) {
List<PooledConnection> removeBusyConnections(long leakTimeMinutes) {
List<PooledConnection> busyConnections = null;
long olderThanTime = System.currentTimeMillis() - (leakTimeMinutes * 60000);
Log.debug("Closing busy connections using leakTimeMinutes {0}", leakTimeMinutes);
for (int i = 0; i < slots.length; i++) {
Expand All @@ -98,20 +101,14 @@ void closeBusyConnections(long leakTimeMinutes) {
} else {
slots[i] = null;
--size;
closeBusyConnection(pc);
if (busyConnections == null) {
busyConnections = new ArrayList<>();
}
busyConnections.add(pc);
}
}
}
}

private void closeBusyConnection(PooledConnection pc) {
try {
Log.warn("DataSource closing busy connection? {0}", pc.fullDescription());
System.out.println("CLOSING busy connection: " + pc.fullDescription());
pc.closeConnectionFully(false);
} catch (Exception ex) {
Log.error("Error when closing potentially leaked connection " + pc.description(), ex);
}
return busyConnections;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ final class ConnectionPool implements DataSourcePool {
private final boolean failOnStart;
private final int maxInactiveMillis;
private final long validateStaleMillis;
private final boolean enforceCleanClose;
/**
* Max age a connection is allowed in millis.
* A value of 0 means no limit (no trimming based on max age).
Expand Down Expand Up @@ -128,6 +129,7 @@ final class ConnectionPool implements DataSourcePool {
this.user = params.getUsername();
this.shutdownOnJvmExit = params.isShutdownOnJvmExit();
this.source = DriverDataSource.of(name, params);
this.enforceCleanClose = params.enforceCleanClose();
if (!params.isOffline()) {
init();
}
Expand Down Expand Up @@ -290,7 +292,7 @@ public SQLException dataSourceDownReason() {

private void notifyDataSourceIsDown(SQLException reason) {
if (dataSourceUp.get()) {
reset();
reset(false);
notifyDown(reason);
}
}
Expand All @@ -314,7 +316,7 @@ private void notifyDown(SQLException reason) {

private void notifyDataSourceIsUp() {
if (!dataSourceUp.get()) {
reset();
reset(true);
notifyUp();
}
}
Expand Down Expand Up @@ -447,6 +449,10 @@ private Connection createConnection() throws SQLException {
return initConnection(source.getConnection());
}

public int forceTrim(int trimCount) {
return queue.forceTrim(trimCount);
}

@Override
public void setMaxSize(int max) {
queue.setMaxSize(max);
Expand Down Expand Up @@ -516,6 +522,13 @@ boolean invalidConnection(PooledConnection conn) {
}
}

/**
* Fail hard in close() when there is uncommitted work. This is for debugging to find wrong code.
*/
boolean enforceCleanClose() {
return enforceCleanClose;
}

/**
* Called by the PooledConnection themselves, returning themselves to the
* pool when they have been finished with.
Expand All @@ -537,7 +550,7 @@ void returnConnectionForceClose(PooledConnection pooledConnection) {
}

void removeClosedConnection(PooledConnection pooledConnection) {
queue.returnPooledConnection(pooledConnection, true);
queue.returnPooledConnection(pooledConnection, true, false);
}

/**
Expand All @@ -548,17 +561,17 @@ private void returnTheConnection(PooledConnection pooledConnection, boolean forc
if (poolListener != null && !forceClose) {
poolListener.onBeforeReturnConnection(pooledConnection);
}
queue.returnPooledConnection(pooledConnection, forceClose);
queue.returnPooledConnection(pooledConnection, forceClose, true);
if (forceClose) {
// Got a bad connection so check the pool
testConnection();
}
}

void returnConnectionReset(PooledConnection pooledConnection) {
queue.returnPooledConnection(pooledConnection, true);
queue.returnPooledConnection(pooledConnection, true, false);
Log.warn("Resetting DataSource on read-only failure [{0}]", name);
reset();
reset(false);
}

/**
Expand Down Expand Up @@ -587,9 +600,9 @@ PooledConnection createConnectionForQueue(int connId) throws SQLException {
* <li>Busy connections are closed when they are returned to the pool.</li>
* </ul>
*/
private void reset() {
private void reset(boolean logErrors) {
heartbeatPoolExhaustedCount = 0;
queue.reset(leakTimeMinutes);
queue.reset(leakTimeMinutes, logErrors);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,22 @@ void closeAll(boolean logErrors) {

/**
* Trim any inactive connections that have not been used since usedSince.
* <p>
* The connections are returned to close.
*/
int trim(int minSize, long usedSince, long createdSince) {
int trimCount = 0;
List<PooledConnection> trim(int minSize, long usedSince, long createdSince, boolean forced) {
List<PooledConnection> trimmedConnections = null;
ListIterator<PooledConnection> iterator = freeBuffer.listIterator(minSize);
while (iterator.hasNext()) {
PooledConnection pooledConnection = iterator.next();
if (pooledConnection.shouldTrim(usedSince, createdSince)) {
if (pooledConnection.shouldTrim(usedSince, createdSince, forced)) {
iterator.remove();
pooledConnection.closeConnectionFully(true);
trimCount++;
if (trimmedConnections == null) {
trimmedConnections = new ArrayList<>();
}
trimmedConnections.add(pooledConnection);
}
}
return trimCount;
return trimmedConnections;
}
}
Loading