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