/* * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Florent Guillaume */ package org.nuxeo.ecm.core; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Before; import org.junit.Test; import org.nuxeo.ecm.core.api.ClientException; import org.nuxeo.ecm.core.api.CoreInstance; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.security.SecurityConstants; import org.nuxeo.ecm.core.storage.sql.DatabaseH2; import org.nuxeo.ecm.core.storage.sql.RepositoryDescriptor; import org.nuxeo.ecm.core.storage.sql.TXSQLRepositoryTestCase; import org.nuxeo.ecm.core.storage.sql.coremodel.SQLRepositoryService; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.jtajca.NuxeoContainer; import org.nuxeo.runtime.jtajca.NuxeoContainer.ConnectionManagerWrapper; import org.nuxeo.runtime.transaction.TransactionHelper; /** * Test JTAJCA pool behavior. */ public class TestJCAPoolBehavior extends TXSQLRepositoryTestCase { private static final Log log = LogFactory.getLog(TestJCAPoolBehavior.class); public static final int MIN_POOL_SIZE = 2; public static final int MAX_POOL_SIZE = 5; public static final int BLOCKING_TIMEOUT = 200; public volatile Exception threadException; private RepositoryDescriptor desc; private ConnectionManagerWrapper cm; @Override protected void deployRepositoryContrib() throws Exception { super.deployRepositoryContrib(); desc = Framework.getLocalService(SQLRepositoryService.class).getRepositoryDescriptor( database.repositoryName); desc.pool.setMinPoolSize(MIN_POOL_SIZE); desc.pool.setMaxPoolSize(MAX_POOL_SIZE); desc.pool.setBlockingTimeoutMillis(BLOCKING_TIMEOUT); } @Before public void lookupCM() { cm = (ConnectionManagerWrapper) NuxeoContainer.getConnectionManager(desc.pool.getName()); assertNotNull("no pooling", cm); } @Test public void testOpenAllConnections() throws Exception { if (!hasPoolingConfig()) { return; } threadException = null; // main thread already uses 1 session Thread[] threads = new Thread[MAX_POOL_SIZE - 1]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(new SessionHolder(2000)); threads[i].start(); } try { Thread.sleep(500); assertNull(threadException); } finally { // finish all threads for (int i = 0; i < threads.length; i++) { threads[i].join(); } } assertNull(threadException); } @Test public void testOpenMoreConnectionsThanMax() throws Exception { if (!hasPoolingConfig()) { return; } if (useSingleConnectionMode()) { // there's not actual pool in this mode return; } threadException = null; // main thread already uses 1 session Thread[] threads = new Thread[MAX_POOL_SIZE - 1]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(new SessionHolder(2000)); threads[i].start(); } Thread.sleep(500); assertNull(threadException); // all connections are used, but try yet another one Thread t = new Thread(new SessionHolder(2000)); t.start(); Thread.sleep(BLOCKING_TIMEOUT + 500); try { assertNotNull(threadException); } finally { // finish all threads for (int i = 0; i < threads.length; i++) { threads[i].join(); } } threadException = null; assertNull(threadException); // re-test full threads use testOpenAllConnections(); } /** Creates a session and holds it open for a while. */ public class SessionHolder implements Runnable { public final int sleepMillis; public SessionHolder(int sleepMillis) { this.sleepMillis = sleepMillis; } @Override public void run() { log.info("start of thread " + Thread.currentThread().getName()); try { TransactionHelper.startTransaction(); CoreSession s = null; try { s = openSessionAs(SecurityConstants.ADMINISTRATOR); Thread.sleep(sleepMillis); } finally { try { if (s != null) { closeSession(s); } } finally { TransactionHelper.commitOrRollbackTransaction(); } } } catch (Exception e) { if (threadException == null) { threadException = e; } } log.info("end of thread " + Thread.currentThread().getName()); } } /** * Check that for two different repositories we get the connections from two * different pools. If not, TransactionCachingInterceptor will return a * session from the first repository when asked for a new session for the * second repository. */ @Test public void testMultipleRepositoriesPerTransaction() throws Exception { // config for second repo available only for H2 if (!(database instanceof DatabaseH2)) { return; } DatabaseH2 db = (DatabaseH2) database; db.setUp2(); deployContrib("org.nuxeo.ecm.core.storage.sql.test", "OSGI-INF/test-pooling-h2-repo2-contrib.xml"); // open a second repository try (CoreSession session2 = CoreInstance.openCoreSession( database.repositoryName + "2", SecurityConstants.ADMINISTRATOR)) { doTestMultipleRepositoriesPerTransaction(session2); } } protected void doTestMultipleRepositoriesPerTransaction(CoreSession session2) throws Exception { assertEquals(database.repositoryName, session.getRepositoryName()); assertEquals(database.repositoryName + "2", session2.getRepositoryName()); assertTrue(TransactionHelper.isTransactionActive()); assertNotSame("Sessions from two different repos", session.getRootDocument().getId(), session2.getRootDocument().getId()); } @Test public void doesntLeakWithTx() throws ClientException { checkSessionLeak(); } @Test public void doesntLeakWithoutTx() throws ClientException { TransactionHelper.commitOrRollbackTransaction(); try { checkSessionLeak(); } finally { TransactionHelper.startTransaction(); } } protected void checkSessionLeak() throws ClientException { closeSession(); int count = threadAllocatedConnectionsCount(); try (CoreSession session = openSessionAs("jdoe")) { assertEquals(count + 1, threadAllocatedConnectionsCount()); } assertEquals(count, threadAllocatedConnectionsCount()); } @Test public void doesntReleaseBeforeCommit() throws ClientException { TransactionHelper.commitOrRollbackTransaction(); assertEquals(0, activeConnectionCount()); assertEquals(0, threadAllocatedConnectionsCount()); closeSession(); TransactionHelper.startTransaction(); try { try (CoreSession first = openSessionAs("jdoe")) { assertEquals(1, threadAllocatedConnectionsCount()); assertEquals(1, activeConnectionCount()); try (CoreSession second = openSessionAs("jdoe")) { assertEquals(2, threadAllocatedConnectionsCount()); assertEquals(1, activeConnectionCount()); TransactionHelper.commitOrRollbackTransaction(); assertEquals(0, threadAllocatedConnectionsCount()); assertEquals(0, activeConnectionCount()); } } assertEquals(0, threadAllocatedConnectionsCount()); } finally { TransactionHelper.commitOrRollbackTransaction(); } } protected int threadAllocatedConnectionsCount() { return cm.getCurrentThreadAllocations().size(); } protected int activeConnectionCount() { return cm.getPooling().getConnectionCount() - cm.getPooling().getIdleConnectionCount(); } }