ConnectionPoolTest.java revision 3c938a3f6b61ce5e2dba0d039b03fe73b89fd26c
1/* 2 * Copyright (C) 2013 Square, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package com.squareup.okhttp; 17 18import com.squareup.okhttp.internal.RecordingHostnameVerifier; 19import com.squareup.okhttp.internal.SslContextBuilder; 20import com.squareup.okhttp.internal.Util; 21import com.squareup.okhttp.internal.http.HttpAuthenticator; 22import com.squareup.okhttp.mockwebserver.MockWebServer; 23import java.io.IOException; 24import java.net.InetAddress; 25import java.net.InetSocketAddress; 26import java.net.Proxy; 27import java.util.Arrays; 28import javax.net.ssl.SSLContext; 29import org.junit.After; 30import org.junit.Before; 31import org.junit.Test; 32 33import static org.junit.Assert.assertEquals; 34import static org.junit.Assert.assertNotNull; 35import static org.junit.Assert.assertNull; 36import static org.junit.Assert.assertSame; 37import static org.junit.Assert.assertTrue; 38import static org.junit.Assert.fail; 39 40public final class ConnectionPoolTest { 41 private static final int KEEP_ALIVE_DURATION_MS = 5000; 42 private static final SSLContext sslContext = SslContextBuilder.localhost(); 43 44 private final MockWebServer spdyServer = new MockWebServer(); 45 private InetSocketAddress spdySocketAddress; 46 private Address spdyAddress; 47 48 private final MockWebServer httpServer = new MockWebServer(); 49 private Address httpAddress; 50 private InetSocketAddress httpSocketAddress; 51 52 private Connection httpA; 53 private Connection httpB; 54 private Connection httpC; 55 private Connection httpD; 56 private Connection httpE; 57 private Connection spdyA; 58 59 @Before public void setUp() throws Exception { 60 spdyServer.useHttps(sslContext.getSocketFactory(), false); 61 62 httpServer.play(); 63 httpAddress = new Address(httpServer.getHostName(), httpServer.getPort(), null, null, 64 HttpAuthenticator.SYSTEM_DEFAULT, null, Protocol.SPDY3_AND_HTTP11); 65 httpSocketAddress = new InetSocketAddress(InetAddress.getByName(httpServer.getHostName()), 66 httpServer.getPort()); 67 68 spdyServer.play(); 69 spdyAddress = new Address(spdyServer.getHostName(), spdyServer.getPort(), 70 sslContext.getSocketFactory(), new RecordingHostnameVerifier(), 71 HttpAuthenticator.SYSTEM_DEFAULT, null,Protocol.SPDY3_AND_HTTP11); 72 spdySocketAddress = new InetSocketAddress(InetAddress.getByName(spdyServer.getHostName()), 73 spdyServer.getPort()); 74 75 Route httpRoute = new Route(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true); 76 Route spdyRoute = new Route(spdyAddress, Proxy.NO_PROXY, spdySocketAddress, true); 77 httpA = new Connection(null, httpRoute); 78 httpA.connect(200, 200, null); 79 httpB = new Connection(null, httpRoute); 80 httpB.connect(200, 200, null); 81 httpC = new Connection(null, httpRoute); 82 httpC.connect(200, 200, null); 83 httpD = new Connection(null, httpRoute); 84 httpD.connect(200, 200, null); 85 httpE = new Connection(null, httpRoute); 86 httpE.connect(200, 200, null); 87 spdyA = new Connection(null, spdyRoute); 88 spdyA.connect(20000, 20000, null); 89 } 90 91 @After public void tearDown() throws Exception { 92 httpServer.shutdown(); 93 spdyServer.shutdown(); 94 95 Util.closeQuietly(httpA); 96 Util.closeQuietly(httpB); 97 Util.closeQuietly(httpC); 98 Util.closeQuietly(httpD); 99 Util.closeQuietly(httpE); 100 Util.closeQuietly(spdyA); 101 } 102 103 @Test public void poolSingleHttpConnection() throws IOException { 104 ConnectionPool pool = new ConnectionPool(1, KEEP_ALIVE_DURATION_MS); 105 Connection connection = pool.get(httpAddress); 106 assertNull(connection); 107 108 connection = new Connection( 109 null, new Route(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true)); 110 connection.connect(200, 200, null); 111 assertEquals(0, pool.getConnectionCount()); 112 pool.recycle(connection); 113 assertEquals(1, pool.getConnectionCount()); 114 assertEquals(1, pool.getHttpConnectionCount()); 115 assertEquals(0, pool.getSpdyConnectionCount()); 116 117 Connection recycledConnection = pool.get(httpAddress); 118 assertEquals(connection, recycledConnection); 119 assertTrue(recycledConnection.isAlive()); 120 121 recycledConnection = pool.get(httpAddress); 122 assertNull(recycledConnection); 123 } 124 125 @Test public void poolPrefersMostRecentlyRecycled() throws Exception { 126 ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); 127 pool.recycle(httpA); 128 pool.recycle(httpB); 129 pool.recycle(httpC); 130 assertPooled(pool, httpC, httpB); 131 } 132 133 @Test public void getSpdyConnection() throws Exception { 134 ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); 135 pool.share(spdyA); 136 assertSame(spdyA, pool.get(spdyAddress)); 137 assertPooled(pool, spdyA); 138 } 139 140 @Test public void getHttpConnection() throws Exception { 141 ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); 142 pool.recycle(httpA); 143 assertSame(httpA, pool.get(httpAddress)); 144 assertPooled(pool); 145 } 146 147 @Test public void idleConnectionNotReturned() throws Exception { 148 ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); 149 pool.recycle(httpA); 150 Thread.sleep(KEEP_ALIVE_DURATION_MS * 2); 151 assertNull(pool.get(httpAddress)); 152 assertPooled(pool); 153 } 154 155 @Test public void maxIdleConnectionLimitIsEnforced() throws Exception { 156 ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); 157 pool.recycle(httpA); 158 pool.recycle(httpB); 159 pool.recycle(httpC); 160 pool.recycle(httpD); 161 assertPooled(pool, httpD, httpC); 162 } 163 164 @Test public void expiredConnectionsAreEvicted() throws Exception { 165 ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); 166 pool.recycle(httpA); 167 pool.recycle(httpB); 168 Thread.sleep(2 * KEEP_ALIVE_DURATION_MS); 169 pool.get(spdyAddress); // Force the cleanup callable to run. 170 assertPooled(pool); 171 } 172 173 @Test public void nonAliveConnectionNotReturned() throws Exception { 174 ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); 175 pool.recycle(httpA); 176 httpA.close(); 177 assertNull(pool.get(httpAddress)); 178 assertPooled(pool); 179 } 180 181 @Test public void differentAddressConnectionNotReturned() throws Exception { 182 ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); 183 pool.recycle(httpA); 184 assertNull(pool.get(spdyAddress)); 185 assertPooled(pool, httpA); 186 } 187 188 @Test public void gettingSpdyConnectionPromotesItToFrontOfQueue() throws Exception { 189 ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); 190 pool.share(spdyA); 191 pool.recycle(httpA); 192 assertPooled(pool, httpA, spdyA); 193 assertSame(spdyA, pool.get(spdyAddress)); 194 assertPooled(pool, spdyA, httpA); 195 } 196 197 @Test public void gettingConnectionReturnsOldestFirst() throws Exception { 198 ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); 199 pool.recycle(httpA); 200 pool.recycle(httpB); 201 assertSame(httpA, pool.get(httpAddress)); 202 } 203 204 @Test public void recyclingNonAliveConnectionClosesThatConnection() throws Exception { 205 ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); 206 httpA.getSocket().shutdownInput(); 207 pool.recycle(httpA); // Should close httpA. 208 assertTrue(httpA.getSocket().isClosed()); 209 } 210 211 @Test public void shareHttpConnectionFails() throws Exception { 212 ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); 213 try { 214 pool.share(httpA); 215 fail(); 216 } catch (IllegalArgumentException expected) { 217 } 218 assertPooled(pool); 219 } 220 221 @Test public void recycleSpdyConnectionDoesNothing() throws Exception { 222 ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); 223 pool.recycle(spdyA); 224 assertPooled(pool); 225 } 226 227 @Test public void validateIdleSpdyConnectionTimeout() throws Exception { 228 ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); 229 pool.share(spdyA); 230 Thread.sleep((int) (KEEP_ALIVE_DURATION_MS * 0.7)); 231 assertNull(pool.get(httpAddress)); 232 assertPooled(pool, spdyA); // Connection should still be in the pool. 233 Thread.sleep((int) (KEEP_ALIVE_DURATION_MS * 0.4)); 234 assertNull(pool.get(httpAddress)); 235 assertPooled(pool); 236 } 237 238 @Test public void validateIdleHttpConnectionTimeout() throws Exception { 239 ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); 240 pool.recycle(httpA); 241 Thread.sleep((int) (KEEP_ALIVE_DURATION_MS * 0.7)); 242 assertNull(pool.get(spdyAddress)); 243 assertPooled(pool, httpA); // Connection should still be in the pool. 244 Thread.sleep((int) (KEEP_ALIVE_DURATION_MS * 0.4)); 245 assertNull(pool.get(spdyAddress)); 246 assertPooled(pool); 247 } 248 249 @Test public void maxConnections() throws IOException, InterruptedException { 250 ConnectionPool pool = new ConnectionPool(2, KEEP_ALIVE_DURATION_MS); 251 252 // Pool should be empty. 253 assertEquals(0, pool.getConnectionCount()); 254 255 // http A should be added to the pool. 256 pool.recycle(httpA); 257 assertEquals(1, pool.getConnectionCount()); 258 assertEquals(1, pool.getHttpConnectionCount()); 259 assertEquals(0, pool.getSpdyConnectionCount()); 260 261 // http B should be added to the pool. 262 pool.recycle(httpB); 263 assertEquals(2, pool.getConnectionCount()); 264 assertEquals(2, pool.getHttpConnectionCount()); 265 assertEquals(0, pool.getSpdyConnectionCount()); 266 267 // http C should be added and http A should be removed. 268 pool.recycle(httpC); 269 Thread.sleep(50); 270 assertEquals(2, pool.getConnectionCount()); 271 assertEquals(2, pool.getHttpConnectionCount()); 272 assertEquals(0, pool.getSpdyConnectionCount()); 273 274 // spdy A should be added and http B should be removed. 275 pool.share(spdyA); 276 Thread.sleep(50); 277 assertEquals(2, pool.getConnectionCount()); 278 assertEquals(1, pool.getHttpConnectionCount()); 279 assertEquals(1, pool.getSpdyConnectionCount()); 280 281 // http C should be removed from the pool. 282 Connection recycledHttpConnection = pool.get(httpAddress); 283 assertNotNull(recycledHttpConnection); 284 assertTrue(recycledHttpConnection.isAlive()); 285 assertEquals(1, pool.getConnectionCount()); 286 assertEquals(0, pool.getHttpConnectionCount()); 287 assertEquals(1, pool.getSpdyConnectionCount()); 288 289 // spdy A will be returned and kept in the pool. 290 Connection sharedSpdyConnection = pool.get(spdyAddress); 291 assertNotNull(sharedSpdyConnection); 292 assertEquals(spdyA, sharedSpdyConnection); 293 assertEquals(1, pool.getConnectionCount()); 294 assertEquals(0, pool.getHttpConnectionCount()); 295 assertEquals(1, pool.getSpdyConnectionCount()); 296 297 // Nothing should change. 298 pool.recycle(httpC); 299 Thread.sleep(50); 300 assertEquals(2, pool.getConnectionCount()); 301 assertEquals(1, pool.getHttpConnectionCount()); 302 assertEquals(1, pool.getSpdyConnectionCount()); 303 304 // An http connection should be removed from the pool. 305 recycledHttpConnection = pool.get(httpAddress); 306 assertNotNull(recycledHttpConnection); 307 assertTrue(recycledHttpConnection.isAlive()); 308 assertEquals(1, pool.getConnectionCount()); 309 assertEquals(0, pool.getHttpConnectionCount()); 310 assertEquals(1, pool.getSpdyConnectionCount()); 311 312 // spdy A will be returned and kept in the pool. Pool shouldn't change. 313 sharedSpdyConnection = pool.get(spdyAddress); 314 assertEquals(spdyA, sharedSpdyConnection); 315 assertNotNull(sharedSpdyConnection); 316 assertEquals(1, pool.getConnectionCount()); 317 assertEquals(0, pool.getHttpConnectionCount()); 318 assertEquals(1, pool.getSpdyConnectionCount()); 319 320 // http D should be added to the pool. 321 pool.recycle(httpD); 322 Thread.sleep(50); 323 assertEquals(2, pool.getConnectionCount()); 324 assertEquals(1, pool.getHttpConnectionCount()); 325 assertEquals(1, pool.getSpdyConnectionCount()); 326 327 // http E should be added to the pool. spdy A should be removed from the pool. 328 pool.recycle(httpE); 329 Thread.sleep(50); 330 assertEquals(2, pool.getConnectionCount()); 331 assertEquals(2, pool.getHttpConnectionCount()); 332 assertEquals(0, pool.getSpdyConnectionCount()); 333 } 334 335 @Test public void connectionCleanup() throws IOException, InterruptedException { 336 ConnectionPool pool = new ConnectionPool(10, KEEP_ALIVE_DURATION_MS); 337 338 // Add 3 connections to the pool. 339 pool.recycle(httpA); 340 pool.recycle(httpB); 341 pool.share(spdyA); 342 assertEquals(3, pool.getConnectionCount()); 343 assertEquals(2, pool.getHttpConnectionCount()); 344 assertEquals(1, pool.getSpdyConnectionCount()); 345 346 // Kill http A. 347 Util.closeQuietly(httpA); 348 349 // Force pool to run a clean up. 350 assertNotNull(pool.get(spdyAddress)); 351 Thread.sleep(50); 352 353 assertEquals(2, pool.getConnectionCount()); 354 assertEquals(1, pool.getHttpConnectionCount()); 355 assertEquals(1, pool.getSpdyConnectionCount()); 356 357 Thread.sleep(KEEP_ALIVE_DURATION_MS); 358 // Force pool to run a clean up. 359 assertNull(pool.get(httpAddress)); 360 assertNull(pool.get(spdyAddress)); 361 362 Thread.sleep(50); 363 364 assertEquals(0, pool.getConnectionCount()); 365 assertEquals(0, pool.getHttpConnectionCount()); 366 assertEquals(0, pool.getSpdyConnectionCount()); 367 } 368 369 @Test public void evictAllConnections() { 370 ConnectionPool pool = new ConnectionPool(10, KEEP_ALIVE_DURATION_MS); 371 pool.recycle(httpA); 372 Util.closeQuietly(httpA); // Include a closed connection in the pool. 373 pool.recycle(httpB); 374 pool.share(spdyA); 375 int connectionCount = pool.getConnectionCount(); 376 assertTrue(connectionCount == 2 || connectionCount == 3); 377 378 pool.evictAll(); 379 assertEquals(0, pool.getConnectionCount()); 380 } 381 382 private void assertPooled(ConnectionPool pool, Connection... connections) throws Exception { 383 assertEquals(Arrays.asList(connections), pool.getConnections()); 384 } 385} 386