| /* |
| * Copyright (C) 2013 Square, Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License 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.squareup.okhttp; |
| |
| import com.google.mockwebserver.MockWebServer; |
| import com.squareup.okhttp.internal.RecordingHostnameVerifier; |
| import com.squareup.okhttp.internal.SslContextBuilder; |
| import com.squareup.okhttp.internal.Util; |
| import com.squareup.okhttp.internal.mockspdyserver.MockSpdyServer; |
| import java.io.IOException; |
| import java.net.InetAddress; |
| import java.net.InetSocketAddress; |
| import java.net.Proxy; |
| import java.net.UnknownHostException; |
| import java.security.GeneralSecurityException; |
| import java.util.Arrays; |
| import javax.net.ssl.SSLContext; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertSame; |
| import static org.junit.Assert.assertTrue; |
| |
| public final class ConnectionPoolTest { |
| private static final int KEEP_ALIVE_DURATION_MS = 500; |
| private static final SSLContext sslContext; |
| |
| static { |
| try { |
| sslContext = new SslContextBuilder(InetAddress.getLocalHost().getHostName()).build(); |
| } catch (GeneralSecurityException e) { |
| throw new RuntimeException(e); |
| } catch (UnknownHostException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private final MockSpdyServer spdyServer = new MockSpdyServer(sslContext.getSocketFactory()); |
| private InetSocketAddress spdySocketAddress; |
| private Address spdyAddress; |
| |
| private final MockWebServer httpServer = new MockWebServer(); |
| private Address httpAddress; |
| private InetSocketAddress httpSocketAddress; |
| |
| private Connection httpA; |
| private Connection httpB; |
| private Connection httpC; |
| private Connection httpD; |
| private Connection httpE; |
| private Connection spdyA; |
| private Connection spdyB; |
| |
| @Before public void setUp() throws Exception { |
| httpServer.play(); |
| httpAddress = new Address(httpServer.getHostName(), httpServer.getPort(), null, null, null); |
| httpSocketAddress = new InetSocketAddress(InetAddress.getByName(httpServer.getHostName()), |
| httpServer.getPort()); |
| |
| spdyServer.play(); |
| spdyAddress = |
| new Address(spdyServer.getHostName(), spdyServer.getPort(), sslContext.getSocketFactory(), |
| new RecordingHostnameVerifier(), null); |
| spdySocketAddress = new InetSocketAddress(InetAddress.getByName(spdyServer.getHostName()), |
| spdyServer.getPort()); |
| |
| httpA = new Connection(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true); |
| httpA.connect(100, 100, null); |
| httpB = new Connection(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true); |
| httpB.connect(100, 100, null); |
| httpC = new Connection(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true); |
| httpC.connect(100, 100, null); |
| httpD = new Connection(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true); |
| httpD.connect(100, 100, null); |
| httpE = new Connection(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true); |
| httpE.connect(100, 100, null); |
| spdyA = new Connection(spdyAddress, Proxy.NO_PROXY, spdySocketAddress, true); |
| spdyA.connect(100, 100, null); |
| spdyB = new Connection(spdyAddress, Proxy.NO_PROXY, spdySocketAddress, true); |
| spdyB.connect(100, 100, null); |
| } |
| |
| @After public void tearDown() throws Exception { |
| httpServer.shutdown(); |
| spdyServer.shutdown(); |
| |
| Util.closeQuietly(httpA); |
| Util.closeQuietly(httpB); |
| Util.closeQuietly(httpC); |
| Util.closeQuietly(httpD); |
| Util.closeQuietly(httpE); |
| Util.closeQuietly(spdyA); |
| Util.closeQuietly(spdyB); |
| } |
| |
| @Test public void poolSingleHttpConnection() throws IOException { |
| ConnectionPool pool = new ConnectionPool(1, KEEP_ALIVE_DURATION_MS); |
| Connection connection = pool.get(httpAddress); |
| assertNull(connection); |
| |
| connection = new Connection(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true); |
| connection.connect(100, 100, null); |
| assertEquals(0, pool.getConnectionCount()); |
| pool.recycle(connection); |
| assertEquals(1, pool.getConnectionCount()); |
| assertEquals(1, pool.getHttpConnectionCount()); |
| assertEquals(0, pool.getSpdyConnectionCount()); |
| |
| Connection recycledConnection = pool.get(httpAddress); |
| assertEquals(connection, recycledConnection); |
| assertTrue(recycledConnection.isAlive()); |
| |
| recycledConnection = pool.get(httpAddress); |
| assertNull(recycledConnection); |
| } |
| |
| @Test public void poolPrefersMostRecentlyRecycled() throws Exception { |
| ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); |
| pool.recycle(httpA); |
| pool.recycle(httpB); |
| pool.recycle(httpC); |
| assertPooled(pool, httpC, httpB); |
| } |
| |
| @Test public void getSpdyConnection() throws Exception { |
| ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); |
| pool.maybeShare(spdyA); |
| assertSame(spdyA, pool.get(spdyAddress)); |
| assertPooled(pool, spdyA); |
| } |
| |
| @Test public void getHttpConnection() throws Exception { |
| ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); |
| pool.recycle(httpA); |
| assertSame(httpA, pool.get(httpAddress)); |
| assertPooled(pool); |
| } |
| |
| @Test public void idleConnectionNotReturned() throws Exception { |
| ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); |
| pool.recycle(httpA); |
| Thread.sleep(KEEP_ALIVE_DURATION_MS * 2); |
| assertNull(pool.get(httpAddress)); |
| assertPooled(pool); |
| } |
| |
| @Test public void maxIdleConnectionLimitIsEnforced() throws Exception { |
| ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); |
| pool.recycle(httpA); |
| pool.recycle(httpB); |
| pool.recycle(httpC); |
| pool.recycle(httpD); |
| assertPooled(pool, httpD, httpC); |
| } |
| |
| @Test public void expiredConnectionsAreEvicted() throws Exception { |
| ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); |
| pool.recycle(httpA); |
| pool.recycle(httpB); |
| Thread.sleep(2 * KEEP_ALIVE_DURATION_MS); |
| pool.get(spdyAddress); // Force the cleanup callable to run. |
| assertPooled(pool); |
| } |
| |
| @Test public void nonAliveConnectionNotReturned() throws Exception { |
| ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); |
| pool.recycle(httpA); |
| httpA.close(); |
| assertNull(pool.get(httpAddress)); |
| assertPooled(pool); |
| } |
| |
| @Test public void differentAddressConnectionNotReturned() throws Exception { |
| ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); |
| pool.recycle(httpA); |
| assertNull(pool.get(spdyAddress)); |
| assertPooled(pool, httpA); |
| } |
| |
| @Test public void gettingSpdyConnectionPromotesItToFrontOfQueue() throws Exception { |
| ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); |
| pool.maybeShare(spdyA); |
| pool.recycle(httpA); |
| assertPooled(pool, httpA, spdyA); |
| assertSame(spdyA, pool.get(spdyAddress)); |
| assertPooled(pool, spdyA, httpA); |
| } |
| |
| @Test public void gettingConnectionReturnsOldestFirst() throws Exception { |
| ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); |
| pool.recycle(httpA); |
| pool.recycle(httpB); |
| assertSame(httpA, pool.get(httpAddress)); |
| } |
| |
| @Test public void recyclingNonAliveConnectionClosesThatConnection() throws Exception { |
| ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); |
| httpA.getSocket().shutdownInput(); |
| pool.recycle(httpA); // Should close httpA. |
| assertTrue(httpA.getSocket().isClosed()); |
| } |
| |
| @Test public void shareHttpConnectionDoesNothing() throws Exception { |
| ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); |
| pool.maybeShare(httpA); |
| assertPooled(pool); |
| } |
| |
| @Test public void recycleSpdyConnectionDoesNothing() throws Exception { |
| ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); |
| pool.recycle(spdyA); |
| assertPooled(pool); |
| } |
| |
| @Test public void validateIdleSpdyConnectionTimeout() throws Exception { |
| ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); |
| pool.maybeShare(spdyA); |
| Thread.sleep((int) (KEEP_ALIVE_DURATION_MS * 0.7)); |
| assertNull(pool.get(httpAddress)); |
| assertPooled(pool, spdyA); // Connection should still be in the pool. |
| Thread.sleep((int) (KEEP_ALIVE_DURATION_MS * 0.4)); |
| assertNull(pool.get(httpAddress)); |
| assertPooled(pool); |
| } |
| |
| @Test public void validateIdleHttpConnectionTimeout() throws Exception { |
| ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); |
| pool.recycle(httpA); |
| Thread.sleep((int) (KEEP_ALIVE_DURATION_MS * 0.7)); |
| assertNull(pool.get(spdyAddress)); |
| assertPooled(pool, httpA); // Connection should still be in the pool. |
| Thread.sleep((int) (KEEP_ALIVE_DURATION_MS * 0.4)); |
| assertNull(pool.get(spdyAddress)); |
| assertPooled(pool); |
| } |
| |
| @Test public void maxConnections() throws IOException, InterruptedException { |
| ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); |
| |
| // Pool should be empty. |
| assertEquals(0, pool.getConnectionCount()); |
| |
| // http A should be added to the pool. |
| pool.recycle(httpA); |
| assertEquals(1, pool.getConnectionCount()); |
| assertEquals(1, pool.getHttpConnectionCount()); |
| assertEquals(0, pool.getSpdyConnectionCount()); |
| |
| // http B should be added to the pool. |
| pool.recycle(httpB); |
| assertEquals(2, pool.getConnectionCount()); |
| assertEquals(2, pool.getHttpConnectionCount()); |
| assertEquals(0, pool.getSpdyConnectionCount()); |
| |
| // http C should be added and http A should be removed. |
| pool.recycle(httpC); |
| Thread.sleep(50); |
| assertEquals(2, pool.getConnectionCount()); |
| assertEquals(2, pool.getHttpConnectionCount()); |
| assertEquals(0, pool.getSpdyConnectionCount()); |
| |
| // spdy A should be added and http B should be removed. |
| pool.maybeShare(spdyA); |
| Thread.sleep(50); |
| assertEquals(2, pool.getConnectionCount()); |
| assertEquals(1, pool.getHttpConnectionCount()); |
| assertEquals(1, pool.getSpdyConnectionCount()); |
| |
| // http C should be removed from the pool. |
| Connection recycledHttpConnection = pool.get(httpAddress); |
| assertNotNull(recycledHttpConnection); |
| assertTrue(recycledHttpConnection.isAlive()); |
| assertEquals(1, pool.getConnectionCount()); |
| assertEquals(0, pool.getHttpConnectionCount()); |
| assertEquals(1, pool.getSpdyConnectionCount()); |
| |
| // spdy A will be returned and kept in the pool. |
| Connection sharedSpdyConnection = pool.get(spdyAddress); |
| assertNotNull(sharedSpdyConnection); |
| assertEquals(spdyA, sharedSpdyConnection); |
| assertEquals(1, pool.getConnectionCount()); |
| assertEquals(0, pool.getHttpConnectionCount()); |
| assertEquals(1, pool.getSpdyConnectionCount()); |
| |
| // Nothing should change. |
| pool.recycle(httpC); |
| Thread.sleep(50); |
| assertEquals(2, pool.getConnectionCount()); |
| assertEquals(1, pool.getHttpConnectionCount()); |
| assertEquals(1, pool.getSpdyConnectionCount()); |
| |
| // Nothing should change. |
| pool.maybeShare(spdyB); |
| Thread.sleep(50); |
| assertEquals(2, pool.getConnectionCount()); |
| assertEquals(1, pool.getHttpConnectionCount()); |
| assertEquals(1, pool.getSpdyConnectionCount()); |
| |
| // An http connection should be removed from the pool. |
| recycledHttpConnection = pool.get(httpAddress); |
| assertNotNull(recycledHttpConnection); |
| assertTrue(recycledHttpConnection.isAlive()); |
| assertEquals(1, pool.getConnectionCount()); |
| assertEquals(0, pool.getHttpConnectionCount()); |
| assertEquals(1, pool.getSpdyConnectionCount()); |
| |
| // Shouldn't change numbers because spdyConnections A and B user the same server address. |
| pool.maybeShare(spdyB); |
| Thread.sleep(50); |
| assertEquals(1, pool.getConnectionCount()); |
| assertEquals(0, pool.getHttpConnectionCount()); |
| assertEquals(1, pool.getSpdyConnectionCount()); |
| |
| // spdy A will be returned and kept in the pool. Pool shouldn't change. |
| sharedSpdyConnection = pool.get(spdyAddress); |
| assertEquals(spdyA, sharedSpdyConnection); |
| assertNotNull(sharedSpdyConnection); |
| assertEquals(1, pool.getConnectionCount()); |
| assertEquals(0, pool.getHttpConnectionCount()); |
| assertEquals(1, pool.getSpdyConnectionCount()); |
| |
| // http D should be added to the pool. |
| pool.recycle(httpD); |
| Thread.sleep(50); |
| assertEquals(2, pool.getConnectionCount()); |
| assertEquals(1, pool.getHttpConnectionCount()); |
| assertEquals(1, pool.getSpdyConnectionCount()); |
| |
| // http E should be added to the pool. spdy A should be removed from the pool. |
| pool.recycle(httpE); |
| Thread.sleep(50); |
| assertEquals(2, pool.getConnectionCount()); |
| assertEquals(2, pool.getHttpConnectionCount()); |
| assertEquals(0, pool.getSpdyConnectionCount()); |
| } |
| |
| @Test public void connectionCleanup() throws IOException, InterruptedException { |
| ConnectionPool pool = new ConnectionPool(10, KEEP_ALIVE_DURATION_MS); |
| |
| // Add 3 connections to the pool. |
| pool.recycle(httpA); |
| pool.recycle(httpB); |
| pool.maybeShare(spdyA); |
| assertEquals(3, pool.getConnectionCount()); |
| assertEquals(2, pool.getHttpConnectionCount()); |
| assertEquals(1, pool.getSpdyConnectionCount()); |
| |
| // Kill http A. |
| Util.closeQuietly(httpA); |
| |
| // Force pool to run a clean up. |
| assertNotNull(pool.get(spdyAddress)); |
| Thread.sleep(50); |
| |
| assertEquals(2, pool.getConnectionCount()); |
| assertEquals(1, pool.getHttpConnectionCount()); |
| assertEquals(1, pool.getSpdyConnectionCount()); |
| |
| Thread.sleep(KEEP_ALIVE_DURATION_MS); |
| // Force pool to run a clean up. |
| assertNull(pool.get(httpAddress)); |
| assertNull(pool.get(spdyAddress)); |
| |
| Thread.sleep(50); |
| |
| assertEquals(0, pool.getConnectionCount()); |
| assertEquals(0, pool.getHttpConnectionCount()); |
| assertEquals(0, pool.getSpdyConnectionCount()); |
| } |
| |
| private void assertPooled(ConnectionPool pool, Connection... connections) throws Exception { |
| assertEquals(Arrays.asList(connections), pool.getConnections()); |
| } |
| } |