package org.infinispan.client.hotrod; import static org.infinispan.client.hotrod.test.HotRodClientTestingUtil.killRemoteCacheManager; import static org.infinispan.client.hotrod.test.HotRodClientTestingUtil.killServers; import static org.infinispan.server.hotrod.test.HotRodTestingUtil.hotRodCacheConfiguration; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertTrue; import java.net.InetSocketAddress; import java.util.concurrent.ExecutionException; import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.pool.impl.GenericKeyedObjectPool; import org.infinispan.Cache; import org.infinispan.client.hotrod.configuration.ExhaustedAction; import org.infinispan.client.hotrod.impl.transport.tcp.TcpTransportFactory; import org.infinispan.client.hotrod.test.HotRodClientTestingUtil; import org.infinispan.client.hotrod.test.InternalRemoteCacheManager; import org.infinispan.commands.VisitableCommand; import org.infinispan.context.InvocationContext; import org.infinispan.interceptors.base.CommandInterceptor; import org.infinispan.server.hotrod.HotRodServer; import org.infinispan.test.MultipleCacheManagersTest; import org.infinispan.test.fwk.TestCacheManagerFactory; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; /** * @author Mircea.Markus@jboss.com * @since 4.1 */ @Test(testName = "client.hotrod.ClientConnectionPoolingTest", groups="functional") public class ClientConnectionPoolingTest extends MultipleCacheManagersTest { private static final Log log = LogFactory.getLog(ClientConnectionPoolingTest.class); Cache<String, String> c1; Cache<String, String> c2; private HotRodServer hotRodServer1; private HotRodServer hotRodServer2; RemoteCache<String, String> remoteCache; private RemoteCacheManager remoteCacheManager; private GenericKeyedObjectPool<?, ?> connectionPool; private InetSocketAddress hrServ1Addr; private InetSocketAddress hrServ2Addr; private WorkerThread workerThread1; private WorkerThread workerThread2; private WorkerThread workerThread3; private WorkerThread workerThread4; private WorkerThread workerThread5; private WorkerThread workerThread6; @Override protected void createCacheManagers() throws Throwable { // The caches are not configured to form a cluster // so the client will have to use round-robin for balancing. // This means requests will alternate between server 1 and server 2. c1 = TestCacheManagerFactory.createCacheManager(hotRodCacheConfiguration()).getCache(); c2 = TestCacheManagerFactory.createCacheManager(hotRodCacheConfiguration()).getCache(); registerCacheManager(c1.getCacheManager(), c2.getCacheManager()); hotRodServer1 = HotRodClientTestingUtil.startHotRodServer(c1.getCacheManager()); hotRodServer2 = HotRodClientTestingUtil.startHotRodServer(c2.getCacheManager()); String servers = HotRodClientTestingUtil.getServersString(hotRodServer1, hotRodServer2); org.infinispan.client.hotrod.configuration.ConfigurationBuilder clientBuilder = new org.infinispan.client.hotrod.configuration.ConfigurationBuilder(); clientBuilder .connectionPool() .maxActive(2) .maxTotal(8) .maxIdle(6) .exhaustedAction(ExhaustedAction.WAIT) .testOnBorrow(false) .testOnReturn(false) .timeBetweenEvictionRuns(-2) .minEvictableIdleTime(7) .testWhileIdle(true) .minIdle(-5) .lifo(true) .addServers(servers); remoteCacheManager = new InternalRemoteCacheManager(clientBuilder.build()); remoteCache = remoteCacheManager.getCache(); TcpTransportFactory tcpConnectionFactory = (TcpTransportFactory) ((InternalRemoteCacheManager) remoteCacheManager).getTransportFactory(); connectionPool = tcpConnectionFactory.getConnectionPool(); workerThread1 = new WorkerThread(remoteCache); workerThread2 = new WorkerThread(remoteCache); workerThread3 = new WorkerThread(remoteCache); workerThread4 = new WorkerThread(remoteCache); workerThread5 = new WorkerThread(remoteCache); workerThread6 = new WorkerThread(remoteCache); hrServ1Addr = new InetSocketAddress("localhost", hotRodServer1.getPort()); hrServ2Addr = new InetSocketAddress("localhost", hotRodServer2.getPort()); } @AfterMethod public void tearDown() throws ExecutionException, InterruptedException { killServers(hotRodServer1, hotRodServer2); workerThread1.stop(); workerThread2.stop(); workerThread3.stop(); workerThread4.stop(); workerThread5.stop(); workerThread6.stop(); workerThread1.awaitTermination(); workerThread2.awaitTermination(); workerThread3.awaitTermination(); workerThread4.awaitTermination(); workerThread5.awaitTermination(); workerThread6.awaitTermination(); killRemoteCacheManager(remoteCacheManager); } @Test public void testPropsCorrectlySet() { assertEquals(2, connectionPool.getMaxActive()); assertEquals(8, connectionPool.getMaxTotal()); assertEquals(6, connectionPool.getMaxIdle()); assertEquals(1, connectionPool.getWhenExhaustedAction()); assertFalse(connectionPool.getTestOnBorrow()); assertFalse(connectionPool.getTestOnReturn()); assertEquals(-2, connectionPool.getTimeBetweenEvictionRunsMillis()); assertEquals(7, connectionPool.getMinEvictableIdleTimeMillis()); assertTrue(connectionPool.getTestWhileIdle()); assertEquals(-5, connectionPool.getMinIdle()); assertTrue(connectionPool.getLifo()); } public void testMaxActiveReached() throws Exception { workerThread1.put("k1", "v1"); workerThread1.put("k2", "v2"); // verify that each cache got a request assertEquals(1, c1.size()); assertEquals(1, c2.size()); assertEquals("v1", remoteCache.get("k1")); assertEquals(1, c1.size()); assertEquals("v2", remoteCache.get("k2")); assertEquals(1, c2.size()); // there should be no active connections to any server assertEquals(0, connectionPool.getNumActive(hrServ1Addr)); assertEquals(0, connectionPool.getNumActive(hrServ2Addr)); assertEquals(1, connectionPool.getNumIdle(hrServ1Addr)); assertEquals(1, connectionPool.getNumIdle(hrServ2Addr)); // install an interceptor that will block all requests on the server until the allow() call DelayTransportInterceptor dt1 = new DelayTransportInterceptor(true); DelayTransportInterceptor dt2 = new DelayTransportInterceptor(true); c1.getAdvancedCache().addInterceptor(dt1, 0); c2.getAdvancedCache().addInterceptor(dt2, 0); log.info("Cache operations blocked"); try { // start one operation on each server, using the existing connections workerThread1.putAsync("k3", "v3"); workerThread2.putAsync("k4", "v4"); log.info("Async calls for k3 and k4 is done."); eventually(() -> 1 == connectionPool.getNumActive(hrServ1Addr) && 1 == connectionPool.getNumActive(hrServ2Addr) && 0 == connectionPool.getNumIdle(hrServ1Addr) && 0 == connectionPool.getNumIdle(hrServ2Addr)); // another operation for each server, creating new connections workerThread3.putAsync("k5", "v5"); workerThread4.putAsync("k6", "v6"); eventually(() -> 2 == connectionPool.getNumActive(hrServ1Addr) && 2 == connectionPool.getNumActive(hrServ2Addr) && 0 == connectionPool.getNumIdle(hrServ1Addr) && 0 == connectionPool.getNumIdle(hrServ2Addr)); // we've reached the connection pool limit, the new operations will block // until a connection is released workerThread5.putAsync("k7", "v7"); workerThread6.putAsync("k8", "v8"); Thread.sleep(2000); //sleep a bit longer to make sure the async threads do their job assertEquals(2, connectionPool.getNumActive(hrServ1Addr)); assertEquals(2, connectionPool.getNumActive(hrServ2Addr)); assertEquals(0, connectionPool.getNumIdle(hrServ1Addr)); assertEquals(0, connectionPool.getNumIdle(hrServ2Addr)); } catch (Exception e) { log.error(e); } finally { //now allow dt1.allow(); dt2.allow(); } // give the servers some time to process the operations eventually(() -> connectionPool.getNumActive() == 0, 1000); assertExistKeyValue("k3", "v3"); assertExistKeyValue("k4", "v4"); assertExistKeyValue("k5", "v5"); assertExistKeyValue("k6", "v6"); assertExistKeyValue("k7", "v7"); assertExistKeyValue("k8", "v8"); // all the connections have been released to the pool, but haven't been closed assertEquals(0, connectionPool.getNumActive(hrServ1Addr)); assertEquals(0, connectionPool.getNumActive(hrServ2Addr)); assertEquals(2, connectionPool.getNumIdle(hrServ1Addr)); assertEquals(2, connectionPool.getNumIdle(hrServ2Addr)); } private void assertExistKeyValue(String key, String value) throws InterruptedException { boolean exists = false; for (int i = 0; i < 10; i++) { exists = value.equals(remoteCache.get(key)) || value.equals(remoteCache.get(key)); if (exists) break; Thread.sleep(1000); } assertEquals("key value not found: (" + key + ", " + value + ")", true, exists); } public static class DelayTransportInterceptor extends CommandInterceptor { private final ReentrantLock lock = new ReentrantLock(); public DelayTransportInterceptor(boolean lock) { if (lock) block(); } @Override protected Object handleDefault(InvocationContext ctx, VisitableCommand command) throws Throwable { log.trace("Acquiring lock. " + lockInfo()); lock.lock(); try { return super.handleDefault(ctx, command); } finally { log.trace("Done operation, releasing lock" + lockInfo()); lock.unlock(); } } private String lockInfo() { return " Is locked? " + lock.isLocked() + ". Lock held by me? " + lock.isHeldByCurrentThread(); } public void block() { log.trace("block. " + lockInfo()); lock.lock(); } public void allow() { log.trace("allow." + lockInfo()); lock.unlock(); } } }