ConnectionPoolTest.java revision f3bcd0c7f0741b277b5d58f294df6201dafd45e8
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.SocketFactory; 29import javax.net.ssl.SSLContext; 30import org.junit.After; 31import org.junit.Before; 32import org.junit.Test; 33 34import static org.junit.Assert.assertEquals; 35import static org.junit.Assert.assertFalse; 36import static org.junit.Assert.assertNotNull; 37import static org.junit.Assert.assertNull; 38import static org.junit.Assert.assertSame; 39import static org.junit.Assert.assertTrue; 40import static org.junit.Assert.fail; 41 42public final class ConnectionPoolTest { 43 private static final int KEEP_ALIVE_DURATION_MS = 5000; 44 private static final SSLContext sslContext = SslContextBuilder.localhost(); 45 46 private MockWebServer spdyServer; 47 private InetSocketAddress spdySocketAddress; 48 private Address spdyAddress; 49 50 private MockWebServer httpServer; 51 private Address httpAddress; 52 private InetSocketAddress httpSocketAddress; 53 54 private ConnectionPool pool; 55 private Connection httpA; 56 private Connection httpB; 57 private Connection httpC; 58 private Connection httpD; 59 private Connection httpE; 60 private Connection spdyA; 61 62 private Object owner; 63 64 @Before public void setUp() throws Exception { 65 setUp(2); 66 } 67 68 private void setUp(int poolSize) throws Exception { 69 SocketFactory socketFactory = SocketFactory.getDefault(); 70 71 spdyServer = new MockWebServer(); 72 httpServer = new MockWebServer(); 73 spdyServer.useHttps(sslContext.getSocketFactory(), false); 74 75 httpServer.play(); 76 httpAddress = new Address(httpServer.getHostName(), httpServer.getPort(), socketFactory, null, 77 null, HttpAuthenticator.SYSTEM_DEFAULT, null, Protocol.SPDY3_AND_HTTP11); 78 httpSocketAddress = new InetSocketAddress(InetAddress.getByName(httpServer.getHostName()), 79 httpServer.getPort()); 80 81 spdyServer.play(); 82 spdyAddress = new Address(spdyServer.getHostName(), spdyServer.getPort(), socketFactory, 83 sslContext.getSocketFactory(), new RecordingHostnameVerifier(), 84 HttpAuthenticator.SYSTEM_DEFAULT, null,Protocol.SPDY3_AND_HTTP11); 85 spdySocketAddress = new InetSocketAddress(InetAddress.getByName(spdyServer.getHostName()), 86 spdyServer.getPort()); 87 88 Route httpRoute = new Route(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true); 89 Route spdyRoute = new Route(spdyAddress, Proxy.NO_PROXY, spdySocketAddress, true); 90 pool = new ConnectionPool(poolSize, KEEP_ALIVE_DURATION_MS); 91 httpA = new Connection(pool, httpRoute); 92 httpA.connect(200, 200, null); 93 httpB = new Connection(pool, httpRoute); 94 httpB.connect(200, 200, null); 95 httpC = new Connection(pool, httpRoute); 96 httpC.connect(200, 200, null); 97 httpD = new Connection(pool, httpRoute); 98 httpD.connect(200, 200, null); 99 httpE = new Connection(pool, httpRoute); 100 httpE.connect(200, 200, null); 101 spdyA = new Connection(pool, spdyRoute); 102 spdyA.connect(20000, 20000, null); 103 104 owner = new Object(); 105 httpA.setOwner(owner); 106 httpB.setOwner(owner); 107 httpC.setOwner(owner); 108 httpD.setOwner(owner); 109 httpE.setOwner(owner); 110 } 111 112 @After public void tearDown() throws Exception { 113 httpServer.shutdown(); 114 spdyServer.shutdown(); 115 116 Util.closeQuietly(httpA); 117 Util.closeQuietly(httpB); 118 Util.closeQuietly(httpC); 119 Util.closeQuietly(httpD); 120 Util.closeQuietly(httpE); 121 Util.closeQuietly(spdyA); 122 } 123 124 private void resetWithPoolSize(int poolSize) throws Exception { 125 tearDown(); 126 setUp(poolSize); 127 } 128 129 @Test public void poolSingleHttpConnection() throws Exception { 130 resetWithPoolSize(1); 131 Connection connection = pool.get(httpAddress); 132 assertNull(connection); 133 134 connection = new Connection( 135 pool, new Route(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true)); 136 connection.connect(200, 200, null); 137 connection.setOwner(owner); 138 assertEquals(0, pool.getConnectionCount()); 139 140 pool.recycle(connection); 141 assertNull(connection.getOwner()); 142 assertEquals(1, pool.getConnectionCount()); 143 assertEquals(1, pool.getHttpConnectionCount()); 144 assertEquals(0, pool.getSpdyConnectionCount()); 145 146 Connection recycledConnection = pool.get(httpAddress); 147 assertNull(connection.getOwner()); 148 assertEquals(connection, recycledConnection); 149 assertTrue(recycledConnection.isAlive()); 150 151 recycledConnection = pool.get(httpAddress); 152 assertNull(recycledConnection); 153 } 154 155 @Test public void poolPrefersMostRecentlyRecycled() throws Exception { 156 pool.recycle(httpA); 157 pool.recycle(httpB); 158 pool.recycle(httpC); 159 assertPooled(pool, httpC, httpB); 160 } 161 162 @Test public void getSpdyConnection() throws Exception { 163 pool.share(spdyA); 164 assertSame(spdyA, pool.get(spdyAddress)); 165 assertPooled(pool, spdyA); 166 } 167 168 @Test public void getHttpConnection() throws Exception { 169 pool.recycle(httpA); 170 assertSame(httpA, pool.get(httpAddress)); 171 assertPooled(pool); 172 } 173 174 @Test public void idleConnectionNotReturned() throws Exception { 175 pool.recycle(httpA); 176 Thread.sleep(KEEP_ALIVE_DURATION_MS * 2); 177 assertNull(pool.get(httpAddress)); 178 assertPooled(pool); 179 } 180 181 @Test public void maxIdleConnectionLimitIsEnforced() throws Exception { 182 pool.recycle(httpA); 183 pool.recycle(httpB); 184 pool.recycle(httpC); 185 pool.recycle(httpD); 186 assertPooled(pool, httpD, httpC); 187 } 188 189 @Test public void expiredConnectionsAreEvicted() throws Exception { 190 pool.recycle(httpA); 191 pool.recycle(httpB); 192 Thread.sleep(2 * KEEP_ALIVE_DURATION_MS); 193 pool.get(spdyAddress); // Force the cleanup callable to run. 194 assertPooled(pool); 195 } 196 197 @Test public void nonAliveConnectionNotReturned() throws Exception { 198 pool.recycle(httpA); 199 httpA.close(); 200 assertNull(pool.get(httpAddress)); 201 assertPooled(pool); 202 } 203 204 @Test public void differentAddressConnectionNotReturned() throws Exception { 205 pool.recycle(httpA); 206 assertNull(pool.get(spdyAddress)); 207 assertPooled(pool, httpA); 208 } 209 210 @Test public void gettingSpdyConnectionPromotesItToFrontOfQueue() throws Exception { 211 pool.share(spdyA); 212 pool.recycle(httpA); 213 assertPooled(pool, httpA, spdyA); 214 assertSame(spdyA, pool.get(spdyAddress)); 215 assertPooled(pool, spdyA, httpA); 216 } 217 218 @Test public void gettingConnectionReturnsOldestFirst() throws Exception { 219 pool.recycle(httpA); 220 pool.recycle(httpB); 221 assertSame(httpA, pool.get(httpAddress)); 222 } 223 224 @Test public void recyclingNonAliveConnectionClosesThatConnection() throws Exception { 225 httpA.getSocket().shutdownInput(); 226 pool.recycle(httpA); // Should close httpA. 227 assertTrue(httpA.getSocket().isClosed()); 228 } 229 230 @Test public void shareHttpConnectionFails() throws Exception { 231 try { 232 pool.share(httpA); 233 fail(); 234 } catch (IllegalArgumentException expected) { 235 } 236 assertPooled(pool); 237 } 238 239 @Test public void recycleSpdyConnectionDoesNothing() throws Exception { 240 pool.recycle(spdyA); 241 assertPooled(pool); 242 } 243 244 @Test public void validateIdleSpdyConnectionTimeout() throws Exception { 245 pool.share(spdyA); 246 Thread.sleep((int) (KEEP_ALIVE_DURATION_MS * 0.7)); 247 assertNull(pool.get(httpAddress)); 248 assertPooled(pool, spdyA); // Connection should still be in the pool. 249 Thread.sleep((int) (KEEP_ALIVE_DURATION_MS * 0.4)); 250 assertNull(pool.get(httpAddress)); 251 assertPooled(pool); 252 } 253 254 @Test public void validateIdleHttpConnectionTimeout() throws Exception { 255 pool.recycle(httpA); 256 Thread.sleep((int) (KEEP_ALIVE_DURATION_MS * 0.7)); 257 assertNull(pool.get(spdyAddress)); 258 assertPooled(pool, httpA); // Connection should still be in the pool. 259 Thread.sleep((int) (KEEP_ALIVE_DURATION_MS * 0.4)); 260 assertNull(pool.get(spdyAddress)); 261 assertPooled(pool); 262 } 263 264 @Test public void maxConnections() throws IOException, InterruptedException { 265 // Pool should be empty. 266 assertEquals(0, pool.getConnectionCount()); 267 268 // http A should be added to the pool. 269 pool.recycle(httpA); 270 assertEquals(1, pool.getConnectionCount()); 271 assertEquals(1, pool.getHttpConnectionCount()); 272 assertEquals(0, pool.getSpdyConnectionCount()); 273 274 // http B should be added to the pool. 275 pool.recycle(httpB); 276 assertEquals(2, pool.getConnectionCount()); 277 assertEquals(2, pool.getHttpConnectionCount()); 278 assertEquals(0, pool.getSpdyConnectionCount()); 279 280 // http C should be added and http A should be removed. 281 pool.recycle(httpC); 282 Thread.sleep(50); 283 assertEquals(2, pool.getConnectionCount()); 284 assertEquals(2, pool.getHttpConnectionCount()); 285 assertEquals(0, pool.getSpdyConnectionCount()); 286 287 // spdy A should be added and http B should be removed. 288 pool.share(spdyA); 289 Thread.sleep(50); 290 assertEquals(2, pool.getConnectionCount()); 291 assertEquals(1, pool.getHttpConnectionCount()); 292 assertEquals(1, pool.getSpdyConnectionCount()); 293 294 // http C should be removed from the pool. 295 Connection recycledHttpConnection = pool.get(httpAddress); 296 recycledHttpConnection.setOwner(owner); 297 assertNotNull(recycledHttpConnection); 298 assertTrue(recycledHttpConnection.isAlive()); 299 assertEquals(1, pool.getConnectionCount()); 300 assertEquals(0, pool.getHttpConnectionCount()); 301 assertEquals(1, pool.getSpdyConnectionCount()); 302 303 // spdy A will be returned and kept in the pool. 304 Connection sharedSpdyConnection = pool.get(spdyAddress); 305 assertNotNull(sharedSpdyConnection); 306 assertEquals(spdyA, sharedSpdyConnection); 307 assertEquals(1, pool.getConnectionCount()); 308 assertEquals(0, pool.getHttpConnectionCount()); 309 assertEquals(1, pool.getSpdyConnectionCount()); 310 311 // Nothing should change. 312 pool.recycle(httpC); 313 Thread.sleep(50); 314 assertEquals(2, pool.getConnectionCount()); 315 assertEquals(1, pool.getHttpConnectionCount()); 316 assertEquals(1, pool.getSpdyConnectionCount()); 317 318 // An http connection should be removed from the pool. 319 recycledHttpConnection = pool.get(httpAddress); 320 assertNotNull(recycledHttpConnection); 321 assertTrue(recycledHttpConnection.isAlive()); 322 assertEquals(1, pool.getConnectionCount()); 323 assertEquals(0, pool.getHttpConnectionCount()); 324 assertEquals(1, pool.getSpdyConnectionCount()); 325 326 // spdy A will be returned and kept in the pool. Pool shouldn't change. 327 sharedSpdyConnection = pool.get(spdyAddress); 328 assertEquals(spdyA, sharedSpdyConnection); 329 assertNotNull(sharedSpdyConnection); 330 assertEquals(1, pool.getConnectionCount()); 331 assertEquals(0, pool.getHttpConnectionCount()); 332 assertEquals(1, pool.getSpdyConnectionCount()); 333 334 // http D should be added to the pool. 335 pool.recycle(httpD); 336 Thread.sleep(50); 337 assertEquals(2, pool.getConnectionCount()); 338 assertEquals(1, pool.getHttpConnectionCount()); 339 assertEquals(1, pool.getSpdyConnectionCount()); 340 341 // http E should be added to the pool. spdy A should be removed from the pool. 342 pool.recycle(httpE); 343 Thread.sleep(50); 344 assertEquals(2, pool.getConnectionCount()); 345 assertEquals(2, pool.getHttpConnectionCount()); 346 assertEquals(0, pool.getSpdyConnectionCount()); 347 } 348 349 @Test public void connectionCleanup() throws IOException, InterruptedException { 350 ConnectionPool pool = new ConnectionPool(10, KEEP_ALIVE_DURATION_MS); 351 352 // Add 3 connections to the pool. 353 pool.recycle(httpA); 354 pool.recycle(httpB); 355 pool.share(spdyA); 356 assertEquals(3, pool.getConnectionCount()); 357 assertEquals(2, pool.getHttpConnectionCount()); 358 assertEquals(1, pool.getSpdyConnectionCount()); 359 360 // Kill http A. 361 Util.closeQuietly(httpA); 362 363 // Force pool to run a clean up. 364 assertNotNull(pool.get(spdyAddress)); 365 Thread.sleep(50); 366 367 assertEquals(2, pool.getConnectionCount()); 368 assertEquals(1, pool.getHttpConnectionCount()); 369 assertEquals(1, pool.getSpdyConnectionCount()); 370 371 Thread.sleep(KEEP_ALIVE_DURATION_MS); 372 // Force pool to run a clean up. 373 assertNull(pool.get(httpAddress)); 374 assertNull(pool.get(spdyAddress)); 375 376 Thread.sleep(50); 377 378 assertEquals(0, pool.getConnectionCount()); 379 assertEquals(0, pool.getHttpConnectionCount()); 380 assertEquals(0, pool.getSpdyConnectionCount()); 381 } 382 383 // Tests to demonstrate Android bug http://b/18369687 and the solution to it. 384 @Test public void connectionCleanup_draining() throws IOException, InterruptedException { 385 ConnectionPool pool = new ConnectionPool(10, KEEP_ALIVE_DURATION_MS); 386 387 // Add 3 connections to the pool. 388 pool.recycle(httpA); 389 pool.recycle(httpB); 390 pool.share(spdyA); 391 assertEquals(3, pool.getConnectionCount()); 392 assertEquals(2, pool.getHttpConnectionCount()); 393 assertEquals(1, pool.getSpdyConnectionCount()); 394 395 // With no method calls made to the pool it will not clean up any connections. 396 Thread.sleep(KEEP_ALIVE_DURATION_MS * 5); 397 assertEquals(3, pool.getConnectionCount()); 398 assertEquals(2, pool.getHttpConnectionCount()); 399 assertEquals(1, pool.getSpdyConnectionCount()); 400 401 // Change the pool into a mode that will clean up connections. 402 pool.enterDrainMode(); 403 404 // Give the drain thread a chance to run. 405 for (int i = 0; i < 5; i++) { 406 Thread.sleep(KEEP_ALIVE_DURATION_MS); 407 if (pool.isDrained()) { 408 break; 409 } 410 } 411 412 // All connections should have drained. 413 assertEquals(0, pool.getConnectionCount()); 414 } 415 416 @Test public void evictAllConnections() throws Exception { 417 resetWithPoolSize(10); 418 pool.recycle(httpA); 419 Util.closeQuietly(httpA); // Include a closed connection in the pool. 420 pool.recycle(httpB); 421 pool.share(spdyA); 422 int connectionCount = pool.getConnectionCount(); 423 assertTrue(connectionCount == 2 || connectionCount == 3); 424 425 pool.evictAll(); 426 assertEquals(0, pool.getConnectionCount()); 427 } 428 429 @Test public void closeIfOwnedBy() throws Exception { 430 httpA.closeIfOwnedBy(owner); 431 assertFalse(httpA.isAlive()); 432 assertFalse(httpA.clearOwner()); 433 } 434 435 @Test public void closeIfOwnedByDoesNothingIfNotOwner() throws Exception { 436 httpA.closeIfOwnedBy(new Object()); 437 assertTrue(httpA.isAlive()); 438 assertTrue(httpA.clearOwner()); 439 } 440 441 @Test public void closeIfOwnedByFailsForSpdyConnections() throws Exception { 442 try { 443 spdyA.closeIfOwnedBy(owner); 444 fail(); 445 } catch (IllegalStateException expected) { 446 } 447 } 448 449 private void assertPooled(ConnectionPool pool, Connection... connections) throws Exception { 450 assertEquals(Arrays.asList(connections), pool.getConnections()); 451 } 452} 453