Fixes #1893 - Jetty HttpClient Connection TTL.

Implemented live timeout on RoundRobinConnectionPool.
This commit is contained in:
Simone Bordet 2017-10-19 17:47:20 +02:00
parent d2fc88839c
commit 0d3827d882
2 changed files with 159 additions and 7 deletions

View File

@ -21,16 +21,21 @@ package org.eclipse.jetty.client;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
@ManagedObject
public class RoundRobinConnectionPool extends AbstractConnectionPool
{
private final List<Entry> entries;
private int index;
private long liveTimeout;
public RoundRobinConnectionPool(Destination destination, int maxConnections, Callback requester)
{
@ -40,6 +45,17 @@ public class RoundRobinConnectionPool extends AbstractConnectionPool
entries.add(new Entry());
}
@ManagedAttribute("The timeout, in milliseconds, after which a live connection may be closed")
public long getLiveTimeout()
{
return liveTimeout;
}
public void setLiveTimeout(long liveTimeout)
{
this.liveTimeout = liveTimeout;
}
@Override
protected void onCreated(Connection connection)
{
@ -50,6 +66,7 @@ public class RoundRobinConnectionPool extends AbstractConnectionPool
if (entry.connection == null)
{
entry.connection = connection;
entry.createdTime = System.nanoTime();
break;
}
}
@ -110,7 +127,8 @@ public class RoundRobinConnectionPool extends AbstractConnectionPool
@Override
public boolean release(Connection connection)
{
boolean released = false;
boolean active = false;
boolean removed = false;
synchronized (this)
{
for (Entry entry : entries)
@ -118,21 +136,38 @@ public class RoundRobinConnectionPool extends AbstractConnectionPool
if (entry.connection == connection)
{
entry.active = false;
released = true;
active = true;
long timeout = getLiveTimeout();
if (timeout > 0)
{
long live = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - entry.createdTime);
if (live >= timeout)
{
entry.reset();
removed = true;
}
}
break;
}
}
}
if (released)
if (active)
{
released(connection);
if (removed)
{
removed(connection);
return false;
}
}
return idle(connection, isClosed());
}
@Override
public boolean remove(Connection connection)
{
boolean removed = false;
boolean active = false;
boolean removed = false;
synchronized (this)
{
for (Entry entry : entries)
@ -140,9 +175,7 @@ public class RoundRobinConnectionPool extends AbstractConnectionPool
if (entry.connection == connection)
{
active = entry.active;
entry.connection = null;
entry.active = false;
entry.used = 0;
entry.reset();
removed = true;
break;
}
@ -198,6 +231,15 @@ public class RoundRobinConnectionPool extends AbstractConnectionPool
private Connection connection;
private boolean active;
private long used;
private long createdTime;
private void reset()
{
connection = null;
active = false;
used = 0;
createdTime = 0;
}
@Override
public String toString()

View File

@ -0,0 +1,110 @@
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.client;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
public class RoundRobinConnectionPoolTest extends AbstractHttpClientServerTest
{
public RoundRobinConnectionPoolTest(SslContextFactory sslContextFactory)
{
super(sslContextFactory);
}
@Test
public void testLiveTimeout() throws Exception
{
List<Integer> remotePorts = new ArrayList<>();
start(new EmptyServerHandler()
{
@Override
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
remotePorts.add(request.getRemotePort());
if (target.equals("/wait"))
{
long time = Long.parseLong(request.getParameter("time"));
try
{
Thread.sleep(time);
}
catch (InterruptedException x)
{
throw new InterruptedIOException();
}
}
}
});
long liveTimeout = 1000;
client.getTransport().setConnectionPoolFactory(destination ->
{
RoundRobinConnectionPool pool = new RoundRobinConnectionPool(destination, 1, destination);
pool.setLiveTimeout(liveTimeout);
return pool;
});
// Make a request to create the initial connection.
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
// Wait a bit.
Thread.sleep(liveTimeout / 2);
// Live timeout will expire during this request.
response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/wait")
.param("time", String.valueOf(liveTimeout))
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
// Make another request to be sure another connection will be used.
response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
Assert.assertThat(remotePorts.size(), Matchers.equalTo(3));
Assert.assertThat(remotePorts.get(0), Matchers.equalTo(remotePorts.get(1)));
// Third request must be on a different connection.
Assert.assertThat(remotePorts.get(1), Matchers.not(Matchers.equalTo(remotePorts.get(2))));
}
}