// ================================================================================================= // Copyright 2011 Twitter, Inc. // ------------------------------------------------------------------------------------------------- // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this work except in compliance with the License. // You may obtain a copy of the License in the LICENSE file, or at: // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ================================================================================================= package com.twitter.common.net.pool; import com.twitter.common.quantity.Amount; import com.twitter.common.quantity.Time; import com.twitter.common.stats.Stats; import org.easymock.Capture; import org.easymock.EasyMock; import org.easymock.IMocksControl; import org.junit.Before; import org.junit.Test; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.ReentrantLock; import static org.easymock.EasyMock.*; import static org.junit.Assert.*; /** * @author John Sirois */ public class ConnectionPoolTest { private IMocksControl control; private ConnectionFactory<Connection<String, Integer>> connectionFactory; private ReentrantLock poolLock; @Before public void setUp() throws Exception { control = EasyMock.createControl(); @SuppressWarnings("unchecked") ConnectionFactory<Connection<String, Integer>> connectionFactory = control.createMock(ConnectionFactory.class); this.connectionFactory = connectionFactory; poolLock = new ReentrantLock(); } @Test(expected = IllegalArgumentException.class) public void testReleaseUnmanaged() { @SuppressWarnings("unchecked") Connection<String, Integer> connection = control.createMock(Connection.class); Executor executor = createMockExecutor(); control.replay(); try { createConnectionPool(executor).release(connection); } finally { control.verify(); } } @Test(expected = IllegalArgumentException.class) public void testReleaseUnmanagedIdentity() throws Exception { class TestConnection implements Connection<String, Integer> { @Override public String get() { return "test"; } @Override public boolean isValid() { return true; } @Override public void close() { // noop } @Override public Integer getEndpoint() { return 1; } @Override public boolean equals(Object obj) { return obj instanceof TestConnection; } } Executor executor = createMockExecutor(); TestConnection connection = new TestConnection(); expect(connectionFactory.create(eq(ObjectPool.NO_TIMEOUT))).andReturn(connection); control.replay(); ConnectionPool<Connection<String, Integer>> connectionPool = createConnectionPool(executor); assertSame(connection, connectionPool.get()); TestConnection equalConnection = new TestConnection(); assertEquals(equalConnection, connection); try { connectionPool.release(equalConnection); } finally { control.verify(); } } @Test(expected = ResourceExhaustedException.class) public void testExhaustedNull() throws Exception { Executor executor = createMockExecutor(); expect(connectionFactory.create(eq(ObjectPool.NO_TIMEOUT))).andReturn(null); control.replay(); try { createConnectionPool(executor).get(); } finally { control.verify(); } } @Test(expected = TimeoutException.class) public void testExhaustedWillNot() throws Exception { Executor executor = createMockExecutor(); @SuppressWarnings("unchecked") Connection<String, Integer> connection = control.createMock(Connection.class); expect(connectionFactory.create(eq(ObjectPool.NO_TIMEOUT))).andReturn(connection); expect(connectionFactory.mightCreate()).andReturn(false); control.replay(); ConnectionPool<Connection<String, Integer>> connectionPool = createConnectionPool(executor); assertSame(connection, connectionPool.get()); try { connectionPool.get(Amount.of(1L, Time.NANOSECONDS)); } finally { control.verify(); } } @Test public void testCloseDisallowsGets() throws Exception { Executor executor = createMockExecutor(); control.replay(); ConnectionPool<Connection<String, Integer>> connectionPool = createConnectionPool(executor); connectionPool.close(); try { connectionPool.get(); fail(); } catch (IllegalStateException e) { // expected } try { connectionPool.get(Amount.of(1L, Time.MILLISECONDS)); fail(); } catch (IllegalStateException e) { // expected } control.verify(); } @Test public void testCloseCloses() throws Exception { Executor executor = Executors.newSingleThreadExecutor(); @SuppressWarnings("unchecked") Connection<String, Integer> connection = control.createMock(Connection.class); expect(connectionFactory.create(eq(ObjectPool.NO_TIMEOUT))).andReturn(connection); @SuppressWarnings("unchecked") Connection<String, Integer> connection2 = control.createMock(Connection.class); expect(connectionFactory.create(eq(ObjectPool.NO_TIMEOUT))).andReturn(connection2); expect(connectionFactory.mightCreate()).andReturn(true); connectionFactory.destroy(connection2); expect(connection2.isValid()).andReturn(true); control.replay(); ConnectionPool<Connection<String, Integer>> connectionPool = createConnectionPool(executor); // This 1st connection is leased out of the pool at close-time and so should not be touched Connection<String, Integer> leasedDuringClose = connectionPool.get(); // this 2nd connection is available when close is called so it should be destroyed connectionPool.release(connectionPool.get()); connectionPool.close(); control.verify(); control.reset(); connectionFactory.destroy(connection); control.replay(); // After a close, releases should destroy connections connectionPool.release(leasedDuringClose); control.verify(); } @Test public void testCreating() throws Exception { Amount<Long, Time> timeout = Amount.of(1L, Time.SECONDS); Executor executor = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>()); expect(connectionFactory.mightCreate()).andReturn(true); Capture<Amount<Long, Time>> timeout1 = new Capture<Amount<Long, Time>>(); @SuppressWarnings("unchecked") Connection<String, Integer> connection1 = control.createMock(Connection.class); expect(connectionFactory.create(capture(timeout1))).andReturn(connection1); Capture<Amount<Long, Time>> timeout2 = new Capture<Amount<Long, Time>>(); @SuppressWarnings("unchecked") Connection<String, Integer> connection2 = control.createMock(Connection.class); expect(connectionFactory.create(capture(timeout2))).andReturn(connection2); control.replay(); ConnectionPool<Connection<String, Integer>> connectionPool = createConnectionPool(executor); assertSame(connection1, connectionPool.get(timeout)); assertTrue(timeout1.hasCaptured()); Long timeout1Millis = timeout1.getValue().as(Time.MILLISECONDS); assertTrue(timeout1Millis > 0 && timeout1Millis <= timeout.as(Time.MILLISECONDS)); assertSame(connection2, connectionPool.get(timeout)); assertTrue(timeout2.hasCaptured()); Long timeout2Millis = timeout1.getValue().as(Time.MILLISECONDS); assertTrue(timeout2Millis > 0 && timeout2Millis <= timeout.as(Time.MILLISECONDS)); control.verify(); } private Executor createMockExecutor() { return control.createMock(Executor.class); } private ConnectionPool<Connection<String, Integer>> createConnectionPool(Executor executor) { return new ConnectionPool<Connection<String, Integer>>(executor, poolLock, connectionFactory, Stats.STATS_PROVIDER); } }