Index: httpclient/src/test/java/org/apache/http/impl/client/TestStatefulConnManagement.java =================================================================== --- httpclient/src/test/java/org/apache/http/impl/client/TestStatefulConnManagement.java (revision 950911) +++ httpclient/src/test/java/org/apache/http/impl/client/TestStatefulConnManagement.java (working copy) @@ -198,4 +198,81 @@ } + @Test + public void testStuff() throws Exception { + + int maxConn = 2; + + int port = this.localServer.getServiceAddress().getPort(); + this.localServer.register("*", new SimpleService()); + + // We build a client with 2 max active // connections, and 2 max per route. + ThreadSafeClientConnManager connMngr = new ThreadSafeClientConnManager(supportedSchemes); + connMngr.setMaxTotalConnections(maxConn); + connMngr.setDefaultMaxPerRoute(maxConn); + + DefaultHttpClient client = new DefaultHttpClient(connMngr); + + client.setUserTokenHandler(new UserTokenHandler() { + + public Object getUserToken(final HttpContext context) { + return context.getAttribute("user"); + } + + }); + + // Bottom of the pool : a *keep alive* connection to Route 1. + HttpContext context1 = new BasicHttpContext(); + context1.setAttribute("user", "stuff"); + HttpResponse response1 = client.execute( + new HttpHost("localhost", port), new HttpGet("/"), context1); + HttpEntity entity1 = response1.getEntity(); + if (entity1 != null) { + entity1.consumeContent(); + } + + // The ConnPoolByRoute now has 1 free connection, out of 2 max + // The ConnPoolByRoute has one RouteSpcfcPool, that has one free connection + // for [localhost][stuff] + + Thread.sleep(1000); + + // Send a very simple HTTP get (it MUST be simple, no auth, no proxy, no 302, no 401, ...) + // Send it to another route. Must be a keepalive. + HttpContext context2 = new BasicHttpContext(); + HttpResponse response2 = client.execute( + new HttpHost("127.0.0.1", port), new HttpGet("/"), context2); + HttpEntity entity2 = response2.getEntity(); + if (entity2 != null) { + entity2.consumeContent(); + } + // ConnPoolByRoute now has 2 free connexions, out of its 2 max. + // The [localhost][stuff] RouteSpcfcPool is the same as earlier + // And there is a [127.0.0.1][null] pool with 1 free connection + + Thread.sleep(1000); + + // This will put the ConnPoolByRoute in a state ready to blow : request to ROUTE 1 + // [localhost][stuff] will not get reused because this call is [localhost][null] + // So the ConnPoolByRoute will need to kill one connection (it is maxed out globally). + // The killed conn is the oldest, which means the first HTTPGet we posted. + // The killing will remove the RouteSpecificPool from the ConnPoolByRoute's inner routeToPool map + // But it will still use this removed RouteSpecificPool to create the new connection + HttpContext context3 = new BasicHttpContext(); + HttpResponse response3 = client.execute( + new HttpHost("localhost", port), new HttpGet("/"), context3); + + // Up til now, all seems good, but the stage is set for the breakage. + // And now we release the connection. + // ConnPoolRoute will try to find the RouteSpecificPool that created the connection, but it has been removed. + // So it creates a new one + // And tells it a new connection has been freed. + // And the RouteSpecificPool does not like an entry it did not create to get freed. + HttpEntity entity3 = response3.getEntity(); + if (entity3 != null) { + entity3.consumeContent(); + } + // This is never reached. IllegalStateException has been thrown. + } + }