You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2016/03/09 20:29:14 UTC
[4/4] cayenne git commit: Cayenne 4.0 connection pool is occasionally
running out of connections
Cayenne 4.0 connection pool is occasionally running out of connections
* reproduced in a unit test and fixed
Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/b0631deb
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/b0631deb
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/b0631deb
Branch: refs/heads/master
Commit: b0631deb251f036840d1ca3aee6d4ae50f2441bf
Parents: 0a9974b
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Wed Mar 9 07:49:10 2016 -0800
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Wed Mar 9 13:28:54 2016 -0600
----------------------------------------------------------------------
.../datasource/ManagedPoolingDataSource.java | 4 +
.../datasource/UnmanagedPoolingDataSource.java | 19 ++-
.../datasource/ManagedPoolingDataSourceIT.java | 170 +++++++++++++------
docs/doc/src/main/resources/RELEASE-NOTES.txt | 1 +
4 files changed, 135 insertions(+), 59 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cayenne/blob/b0631deb/cayenne-server/src/main/java/org/apache/cayenne/datasource/ManagedPoolingDataSource.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/datasource/ManagedPoolingDataSource.java b/cayenne-server/src/main/java/org/apache/cayenne/datasource/ManagedPoolingDataSource.java
index 1075e3d..367a6e0 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/datasource/ManagedPoolingDataSource.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/datasource/ManagedPoolingDataSource.java
@@ -62,6 +62,10 @@ public class ManagedPoolingDataSource implements PoolingDataSource, ScopeEventLi
int availableSize() {
return dataSourceManager.getDataSource().availableSize();
}
+
+ int canExpandSize() {
+ return dataSourceManager.getDataSource().canExpandSize();
+ }
/**
* Calls {@link #shutdown()} to drain the underlying pool, close open
http://git-wip-us.apache.org/repos/asf/cayenne/blob/b0631deb/cayenne-server/src/main/java/org/apache/cayenne/datasource/UnmanagedPoolingDataSource.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/datasource/UnmanagedPoolingDataSource.java b/cayenne-server/src/main/java/org/apache/cayenne/datasource/UnmanagedPoolingDataSource.java
index 560348c..2d0e6e2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/datasource/UnmanagedPoolingDataSource.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/datasource/UnmanagedPoolingDataSource.java
@@ -157,6 +157,10 @@ public class UnmanagedPoolingDataSource implements PoolingDataSource {
return available.size();
}
+ int canExpandSize() {
+ return poolCap.availablePermits();
+ }
+
@Override
public void close() {
@@ -264,7 +268,13 @@ public class UnmanagedPoolingDataSource implements PoolingDataSource {
return null;
}
- PoolAwareConnection c = createWrapped();
+ PoolAwareConnection c;
+ try {
+ c = createWrapped();
+ } catch (SQLException e) {
+ poolCap.release();
+ throw e;
+ }
pool.put(c, 1);
@@ -335,8 +345,11 @@ public class UnmanagedPoolingDataSource implements PoolingDataSource {
return resetState(c);
}
- throw new ConnectionUnavailableException(
- "Can't obtain connection. Request to pool timed out. Total pool size: " + pool.size());
+ int poolSize = poolSize();
+ int canGrow = poolCap.availablePermits();
+
+ throw new ConnectionUnavailableException("Can't obtain connection. Request to pool timed out. Total pool size: "
+ + poolSize + ", can expand by: " + canGrow);
}
@Override
http://git-wip-us.apache.org/repos/asf/cayenne/blob/b0631deb/cayenne-server/src/test/java/org/apache/cayenne/datasource/ManagedPoolingDataSourceIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/datasource/ManagedPoolingDataSourceIT.java b/cayenne-server/src/test/java/org/apache/cayenne/datasource/ManagedPoolingDataSourceIT.java
index 1580ce5..04a4540 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/datasource/ManagedPoolingDataSourceIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/datasource/ManagedPoolingDataSourceIT.java
@@ -1,7 +1,6 @@
package org.apache.cayenne.datasource;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -10,14 +9,18 @@ import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
import javax.sql.DataSource;
import org.apache.cayenne.unit.di.server.CayenneProjects;
import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -28,42 +31,28 @@ import org.mockito.stubbing.OngoingStubbing;
@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
public class ManagedPoolingDataSourceIT {
+ private static final Log LOGGER = LogFactory.getLog(ManagedPoolingDataSourceIT.class);
+
private int poolSize;
- private Map<ExpiringConnection, Object> connections;
+ private OnOffDataSourceManager dataSourceManager;
private UnmanagedPoolingDataSource unmanagedPool;
private ManagedPoolingDataSource managedPool;
@Before
public void before() throws SQLException {
- this.poolSize = 3;
- this.connections = new ConcurrentHashMap<>();
-
- DataSource mockDataSource = mock(DataSource.class);
- when(mockDataSource.getConnection()).thenAnswer(new Answer<Connection>() {
-
- @Override
- public Connection answer(InvocationOnMock invocation) throws Throwable {
- return createMockConnection();
- }
- });
+ this.poolSize = 4;
+ this.dataSourceManager = new OnOffDataSourceManager();
PoolingDataSourceParameters parameters = new PoolingDataSourceParameters();
parameters.setMaxConnections(poolSize);
- parameters.setMinConnections(poolSize - 1);
- parameters.setMaxQueueWaitTime(500);
+ parameters.setMinConnections(poolSize / 2);
+ parameters.setMaxQueueWaitTime(1000);
parameters.setValidationQuery("SELECT 1");
- this.unmanagedPool = new UnmanagedPoolingDataSource(mockDataSource, parameters);
-
+ this.unmanagedPool = new UnmanagedPoolingDataSource(dataSourceManager.mockDataSource, parameters);
this.managedPool = new ManagedPoolingDataSource(unmanagedPool, 10000);
}
- private Connection createMockConnection() throws SQLException {
- ExpiringConnection connectionWrapper = new ExpiringConnection();
- connections.put(connectionWrapper, 1);
- return connectionWrapper.mockConnection;
- }
-
@After
public void after() {
if (managedPool != null) {
@@ -71,53 +60,122 @@ public class ManagedPoolingDataSourceIT {
}
}
- private void expireConnections() throws SQLException {
- Iterator<ExpiringConnection> it = connections.keySet().iterator();
- while (it.hasNext()) {
- ExpiringConnection c = it.next();
- it.remove();
- c.expire();
+ private Collection<PoolTask> createTasks(int size) {
+ Collection<PoolTask> tasks = new ArrayList<>();
+
+ for (int i = 0; i < size; i++) {
+ tasks.add(new PoolTask());
}
+ return tasks;
}
@Test
- public void testAbruptReset() throws SQLException {
+ public void testGetConnection_OnBackendShutdown() throws SQLException, InterruptedException {
+
+ // note that this assertion can only work reliably when the pool is inactive...
+ assertEquals(poolSize, managedPool.poolSize() + managedPool.canExpandSize());
+
+ Collection<PoolTask> tasks = createTasks(4);
+ ExecutorService executor = Executors.newFixedThreadPool(4);
+
+ for (int j = 0; j < 10; j++) {
+ for (PoolTask task : tasks) {
+ executor.submit(task);
+ }
+ }
+
+ dataSourceManager.off();
+ Thread.sleep(500);
+
+ for (int j = 0; j < 10; j++) {
+ for (PoolTask task : tasks) {
+ executor.submit(task);
+ }
+ }
- assertTrue(managedPool.poolSize() > 0);
- assertTrue(managedPool.availableSize() > 0);
+ Thread.sleep(100);
- // make sure conn
- expireConnections();
+ dataSourceManager.on();
- // CAY-2067 ... this should work on an invalid pool
- assertNotNull(managedPool.getConnection());
+ for (int j = 0; j < 10; j++) {
+ for (PoolTask task : tasks) {
+ executor.submit(task);
+ }
+ }
+
+ executor.shutdown();
+ executor.awaitTermination(2, TimeUnit.SECONDS);
+
+ // note that this assertion can only work reliably when the pool is inactive...
+ assertEquals(poolSize, managedPool.poolSize() + managedPool.canExpandSize());
}
- static class ExpiringConnection {
+ class PoolTask implements Runnable {
- private Connection mockConnection;
- private OngoingStubbing<Statement> createStatementMock;
+ @Override
+ public void run() {
- ExpiringConnection() throws SQLException {
- this.mockConnection = mock(Connection.class);
- this.createStatementMock = when(mockConnection.createStatement());
+ try (Connection c = managedPool.getConnection();) {
+ try (Statement s = c.createStatement()) {
+ try {
+ Thread.sleep(40);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+ } catch (SQLException e) {
+ if (OnOffDataSourceManager.NO_CONNECTIONS_MESSAGE.equals(e.getMessage())) {
+ LOGGER.info("db down...");
+ } else {
+ LOGGER.warn("error getting connection", e);
+ }
+ }
+ }
+ }
- createStatementMock.thenAnswer(new Answer<Statement>() {
- @Override
- public Statement answer(InvocationOnMock invocation) throws Throwable {
+ static class OnOffDataSourceManager {
+
+ static final String NO_CONNECTIONS_MESSAGE = "no connections at the moment";
- ResultSet mockRs = mock(ResultSet.class);
- when(mockRs.next()).thenReturn(true, false, false, false);
+ private DataSource mockDataSource;
+ private OngoingStubbing<Connection> createConnectionMock;
+
+ OnOffDataSourceManager() throws SQLException {
+ this.mockDataSource = mock(DataSource.class);
+ this.createConnectionMock = when(mockDataSource.getConnection());
+ on();
+ }
- Statement mockStatement = mock(Statement.class);
- when(mockStatement.executeQuery(anyString())).thenReturn(mockRs);
- return mockStatement;
+ void off() throws SQLException {
+ createConnectionMock.thenAnswer(new Answer<Connection>() {
+ @Override
+ public Connection answer(InvocationOnMock invocation) throws Throwable {
+ throw new SQLException(NO_CONNECTIONS_MESSAGE);
}
});
}
- void expire() throws SQLException {
- createStatementMock.thenThrow(new SQLException("Expired"));
+ void on() throws SQLException {
+ createConnectionMock.thenAnswer(new Answer<Connection>() {
+ @Override
+ public Connection answer(InvocationOnMock invocation) throws Throwable {
+ Connection c = mock(Connection.class);
+ when(c.createStatement()).thenAnswer(new Answer<Statement>() {
+ @Override
+ public Statement answer(InvocationOnMock invocation) throws Throwable {
+
+ ResultSet mockRs = mock(ResultSet.class);
+ when(mockRs.next()).thenReturn(true, false, false, false);
+
+ Statement mockStatement = mock(Statement.class);
+ when(mockStatement.executeQuery(anyString())).thenReturn(mockRs);
+ return mockStatement;
+ }
+ });
+
+ return c;
+ }
+ });
}
}
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/b0631deb/docs/doc/src/main/resources/RELEASE-NOTES.txt
----------------------------------------------------------------------
diff --git a/docs/doc/src/main/resources/RELEASE-NOTES.txt b/docs/doc/src/main/resources/RELEASE-NOTES.txt
index c595659..3721d85 100644
--- a/docs/doc/src/main/resources/RELEASE-NOTES.txt
+++ b/docs/doc/src/main/resources/RELEASE-NOTES.txt
@@ -24,6 +24,7 @@ Bug Fixes:
CAY-2064 Issue with BeanAccessor for classes with complex inheritance
CAY-2066 Fixes for inner enums handling in ExtendedTypeMap
+CAY-2067 Cayenne 4.0 connection pool is occasionally running out of connections
----------------------------------
Release: 4.0.M3