Support for connection TTL setting on a per-route basis

This commit is contained in:
Oleg Kalnichevski 2021-10-23 15:57:00 +02:00
parent fff097615b
commit a02455acb3
6 changed files with 154 additions and 58 deletions

View File

@ -49,22 +49,25 @@ public class ConnectionConfig implements Cloneable {
private final Timeout connectTimeout;
private final Timeout socketTimeout;
private final TimeValue validateAfterInactivity;
private final TimeValue timeToLive;
/**
* Intended for CDI compatibility
*/
protected ConnectionConfig() {
this(DEFAULT_CONNECT_TIMEOUT, null, null);
this(DEFAULT_CONNECT_TIMEOUT, null, null, null);
}
ConnectionConfig(
final Timeout connectTimeout,
final Timeout socketTimeout,
final TimeValue validateAfterInactivity) {
final TimeValue validateAfterInactivity,
final TimeValue timeToLive) {
super();
this.connectTimeout = connectTimeout;
this.socketTimeout = socketTimeout;
this.validateAfterInactivity = validateAfterInactivity;
this.timeToLive = timeToLive;
}
/**
@ -88,6 +91,13 @@ public TimeValue getValidateAfterInactivity() {
return validateAfterInactivity;
}
/**
* @see Builder#setTimeToLive(TimeValue) (TimeValue)
*/
public TimeValue getTimeToLive() {
return timeToLive;
}
@Override
protected ConnectionConfig clone() throws CloneNotSupportedException {
return (ConnectionConfig) super.clone();
@ -100,6 +110,7 @@ public String toString() {
builder.append(", connectTimeout=").append(connectTimeout);
builder.append(", socketTimeout=").append(socketTimeout);
builder.append(", validateAfterInactivity=").append(validateAfterInactivity);
builder.append(", timeToLive=").append(timeToLive);
builder.append("]");
return builder.toString();
}
@ -112,7 +123,8 @@ public static ConnectionConfig.Builder copy(final ConnectionConfig config) {
return new Builder()
.setConnectTimeout(config.getConnectTimeout())
.setSocketTimeout(config.getSocketTimeout())
.setValidateAfterInactivity(config.getValidateAfterInactivity());
.setValidateAfterInactivity(config.getValidateAfterInactivity())
.setTimeToLive(config.getTimeToLive());
}
public static class Builder {
@ -120,6 +132,7 @@ public static class Builder {
private Timeout socketTimeout;
private Timeout connectTimeout;
private TimeValue validateAfterInactivity;
private TimeValue timeToLive;
Builder() {
super();
@ -190,11 +203,31 @@ public Builder setValidateAfterInactivity(final long validateAfterInactivity, fi
return this;
}
/**
* Defines the total span of time connections can be kept alive or execute requests.
* <p>
* Default: {@code null} (undefined)
* </p>
*/
public Builder setTimeToLive(final TimeValue timeToLive) {
this.timeToLive = timeToLive;
return this;
}
/**
* @see #setTimeToLive(TimeValue)
*/
public Builder setTimeToLive(final long timeToLive, final TimeUnit timeUnit) {
this.timeToLive = TimeValue.of(timeToLive, timeUnit);
return this;
}
public ConnectionConfig build() {
return new ConnectionConfig(
connectTimeout != null ? connectTimeout : DEFAULT_CONNECT_TIMEOUT,
socketTimeout,
validateAfterInactivity);
validateAfterInactivity,
timeToLive);
}
}

View File

@ -67,6 +67,7 @@
import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.Asserts;
import org.apache.hc.core5.util.Deadline;
import org.apache.hc.core5.util.LangUtils;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
@ -105,6 +106,7 @@ public class BasicHttpClientConnectionManager implements HttpClientConnectionMan
private ManagedHttpClientConnection conn;
private HttpRoute route;
private Object state;
private long created;
private long updated;
private long expiry;
private boolean leased;
@ -114,8 +116,6 @@ public class BasicHttpClientConnectionManager implements HttpClientConnectionMan
private final AtomicBoolean closed;
private volatile TimeValue validateAfterInactivity;
private static Registry<ConnectionSocketFactory> getDefaultRegistry() {
return RegistryBuilder.<ConnectionSocketFactory>create()
.register(URIScheme.HTTP.id, PlainConnectionSocketFactory.getSocketFactory())
@ -147,7 +147,6 @@ public BasicHttpClientConnectionManager(
this.connectionConfig = ConnectionConfig.DEFAULT;
this.tlsConfig = TlsConfig.DEFAULT;
this.closed = new AtomicBoolean(false);
this.validateAfterInactivity = TimeValue.ofSeconds(2L);
}
public BasicHttpClientConnectionManager(
@ -207,6 +206,13 @@ public synchronized void setConnectionConfig(final ConnectionConfig connectionCo
this.connectionConfig = connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT;
}
/**
* @since 5.2
*/
public synchronized TlsConfig getTlsConfig() {
return tlsConfig;
}
/**
* @since 5.2
*/
@ -260,21 +266,34 @@ private void checkExpiry() {
}
private void validate() {
final TimeValue validateAfterInactivitySnapshot = validateAfterInactivity;
if (this.conn != null
&& TimeValue.isNonNegative(validateAfterInactivitySnapshot)
&& updated + validateAfterInactivitySnapshot.toMilliseconds() <= System.currentTimeMillis()) {
boolean stale;
try {
stale = conn.isStale();
} catch (final IOException ignore) {
stale = true;
}
if (stale) {
if (LOG.isDebugEnabled()) {
LOG.debug("{} connection {} is stale", id, ConnPoolSupport.getId(conn));
if (this.conn != null) {
final TimeValue timeToLive = connectionConfig.getTimeToLive();
if (TimeValue.isNonNegative(timeToLive)) {
final Deadline deadline = Deadline.calculate(created, timeToLive);
if (deadline.isExpired()) {
closeConnection(CloseMode.GRACEFUL);
}
}
}
if (this.conn != null) {
final TimeValue timeValue = connectionConfig.getValidateAfterInactivity() != null ?
connectionConfig.getValidateAfterInactivity() : TimeValue.ofSeconds(2);
if (TimeValue.isNonNegative(timeValue)) {
final Deadline deadline = Deadline.calculate(updated, timeValue);
if (deadline.isExpired()) {
boolean stale;
try {
stale = conn.isStale();
} catch (final IOException ignore) {
stale = true;
}
if (stale) {
if (LOG.isDebugEnabled()) {
LOG.debug("{} connection {} is stale", id, ConnPoolSupport.getId(conn));
}
closeConnection(CloseMode.GRACEFUL);
}
}
closeConnection(CloseMode.GRACEFUL);
}
}
}
@ -294,6 +313,7 @@ synchronized ManagedHttpClientConnection getConnection(final HttpRoute route, fi
validate();
if (this.conn == null) {
this.conn = this.connFactory.createConnection(null);
this.created = System.currentTimeMillis();
} else {
this.conn.activate();
}
@ -436,9 +456,12 @@ public synchronized void closeIdle(final TimeValue idleTime) {
* @see #setValidateAfterInactivity(TimeValue)
*
* @since 5.1
*
* @deprecated Use {@link #getConnectionConfig()}
*/
@Deprecated
public TimeValue getValidateAfterInactivity() {
return validateAfterInactivity;
return connectionConfig.getValidateAfterInactivity();
}
/**
@ -448,9 +471,14 @@ public TimeValue getValidateAfterInactivity() {
* detect connections that have become stale (half-closed) while kept inactive in the pool.
*
* @since 5.1
*
* @deprecated Use {@link #setConnectionConfig(ConnectionConfig)}
*/
@Deprecated
public void setValidateAfterInactivity(final TimeValue validateAfterInactivity) {
this.validateAfterInactivity = validateAfterInactivity;
this.connectionConfig = ConnectionConfig.custom()
.setValidateAfterInactivity(validateAfterInactivity)
.build();
}
class InternalConnectionEndpoint extends ConnectionEndpoint {

View File

@ -76,6 +76,7 @@
import org.apache.hc.core5.pool.StrictConnPool;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.Asserts;
import org.apache.hc.core5.util.Deadline;
import org.apache.hc.core5.util.Identifiable;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
@ -303,23 +304,34 @@ public synchronized ConnectionEndpoint get(
LOG.debug("{} endpoint leased {}", id, ConnPoolSupport.formatStats(route, state, pool));
}
final ConnectionConfig connectionConfig = resolveConnectionConfig(route);
final TimeValue timeValue = resolveValidateAfterInactivity(connectionConfig);
try {
if (TimeValue.isNonNegative(timeValue)) {
final ManagedHttpClientConnection conn = poolEntry.getConnection();
if (conn != null
&& poolEntry.getUpdated() + timeValue.toMilliseconds() <= System.currentTimeMillis()) {
boolean stale;
try {
stale = conn.isStale();
} catch (final IOException ignore) {
stale = true;
if (poolEntry.hasConnection()) {
final TimeValue timeToLive = connectionConfig.getTimeToLive();
if (TimeValue.isNonNegative(timeToLive)) {
final Deadline deadline = Deadline.calculate(poolEntry.getCreated(), timeToLive);
if (deadline.isExpired()) {
poolEntry.discardConnection(CloseMode.GRACEFUL);
}
if (stale) {
if (LOG.isDebugEnabled()) {
LOG.debug("{} connection {} is stale", id, ConnPoolSupport.getId(conn));
}
}
if (poolEntry.hasConnection()) {
final TimeValue timeValue = resolveValidateAfterInactivity(connectionConfig);
if (TimeValue.isNonNegative(timeValue)) {
final Deadline deadline = Deadline.calculate(poolEntry.getUpdated(), timeValue);
if (deadline.isExpired()) {
final ManagedHttpClientConnection conn = poolEntry.getConnection();
boolean stale;
try {
stale = conn.isStale();
} catch (final IOException ignore) {
stale = true;
}
if (stale) {
if (LOG.isDebugEnabled()) {
LOG.debug("{} connection {} is stale", id, ConnPoolSupport.getId(conn));
}
poolEntry.discardConnection(CloseMode.IMMEDIATE);
}
poolEntry.discardConnection(CloseMode.IMMEDIATE);
}
}
}

View File

@ -90,8 +90,6 @@ public class PoolingHttpClientConnectionManagerBuilder {
private int maxConnTotal;
private int maxConnPerRoute;
private TimeValue timeToLive;
public static PoolingHttpClientConnectionManagerBuilder create() {
return new PoolingHttpClientConnectionManagerBuilder();
}
@ -229,9 +227,14 @@ public final PoolingHttpClientConnectionManagerBuilder setTlsConfigResolver(
/**
* Sets maximum time to live for persistent connections
*
* @deprecated Use {@link #setDefaultConnectionConfig(ConnectionConfig)}.
*/
@Deprecated
public final PoolingHttpClientConnectionManagerBuilder setConnectionTimeToLive(final TimeValue timeToLive) {
this.timeToLive = timeToLive;
setDefaultConnectionConfig(ConnectionConfig.custom()
.setTimeToLive(timeToLive)
.build());
return this;
}
@ -269,7 +272,7 @@ public PoolingHttpClientConnectionManager build() {
.build(),
poolConcurrencyPolicy,
poolReusePolicy,
timeToLive != null ? timeToLive : TimeValue.NEG_ONE_MILLISECOND,
null,
schemePortResolver,
dnsResolver,
connectionFactory);

View File

@ -88,6 +88,7 @@
import org.apache.hc.core5.reactor.ssl.TlsDetails;
import org.apache.hc.core5.util.Args;
import org.apache.hc.core5.util.Asserts;
import org.apache.hc.core5.util.Deadline;
import org.apache.hc.core5.util.Identifiable;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
@ -261,25 +262,40 @@ public Future<AsyncConnectionEndpoint> lease(
@Override
public void completed(final PoolEntry<HttpRoute, ManagedAsyncClientConnection> poolEntry) {
final ManagedAsyncClientConnection connection = poolEntry.getConnection();
final TimeValue timeValue = connectionConfig != null ? connectionConfig.getValidateAfterInactivity() : null;
if (TimeValue.isNonNegative(timeValue) && connection != null &&
poolEntry.getUpdated() + timeValue.toMilliseconds() <= System.currentTimeMillis()) {
final ProtocolVersion protocolVersion = connection.getProtocolVersion();
if (protocolVersion != null && protocolVersion.greaterEquals(HttpVersion.HTTP_2_0)) {
connection.submitCommand(new PingCommand(new BasicPingHandler(result -> {
if (result == null || !result) {
if (poolEntry.hasConnection()) {
final TimeValue timeToLive = connectionConfig.getTimeToLive();
if (TimeValue.isNonNegative(timeToLive)) {
final Deadline deadline = Deadline.calculate(poolEntry.getCreated(), timeToLive);
if (deadline.isExpired()) {
poolEntry.discardConnection(CloseMode.GRACEFUL);
}
}
}
if (poolEntry.hasConnection()) {
final ManagedAsyncClientConnection connection = poolEntry.getConnection();
final TimeValue timeValue = connectionConfig.getValidateAfterInactivity();
if (connection.isOpen() && TimeValue.isNonNegative(timeValue)) {
final Deadline deadline = Deadline.calculate(poolEntry.getUpdated(), timeValue);
if (deadline.isExpired()) {
final ProtocolVersion protocolVersion = connection.getProtocolVersion();
if (protocolVersion != null && protocolVersion.greaterEquals(HttpVersion.HTTP_2_0)) {
connection.submitCommand(new PingCommand(new BasicPingHandler(result -> {
if (result == null || !result) {
if (LOG.isDebugEnabled()) {
LOG.debug("{} connection {} is stale", id, ConnPoolSupport.getId(connection));
}
poolEntry.discardConnection(CloseMode.GRACEFUL);
}
leaseCompleted(poolEntry);
})), Command.Priority.IMMEDIATE);
return;
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("{} connection {} is stale", id, ConnPoolSupport.getId(connection));
LOG.debug("{} connection {} is closed", id, ConnPoolSupport.getId(connection));
}
poolEntry.discardConnection(CloseMode.IMMEDIATE);
}
})), Command.Priority.IMMEDIATE);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("{} connection {} is closed", id, ConnPoolSupport.getId(connection));
}
poolEntry.discardConnection(CloseMode.IMMEDIATE);
}
}
leaseCompleted(poolEntry);

View File

@ -87,7 +87,6 @@ public class PoolingAsyncClientConnectionManagerBuilder {
private Resolver<HttpRoute, SocketConfig> socketConfigResolver;
private Resolver<HttpRoute, ConnectionConfig> connectionConfigResolver;
private Resolver<HttpHost, TlsConfig> tlsConfigResolver;
private TimeValue timeToLive;
public static PoolingAsyncClientConnectionManagerBuilder create() {
return new PoolingAsyncClientConnectionManagerBuilder();
@ -198,9 +197,14 @@ public final PoolingAsyncClientConnectionManagerBuilder setTlsConfigResolver(
/**
* Sets maximum time to live for persistent connections
*
* @deprecated Use {@link #setDefaultConnectionConfig(ConnectionConfig)}
*/
@Deprecated
public final PoolingAsyncClientConnectionManagerBuilder setConnectionTimeToLive(final TimeValue timeToLive) {
this.timeToLive = timeToLive;
setDefaultConnectionConfig(ConnectionConfig.custom()
.setTimeToLive(timeToLive)
.build());
return this;
}
@ -252,7 +256,7 @@ public PoolingAsyncClientConnectionManager build() {
.build(),
poolConcurrencyPolicy,
poolReusePolicy,
timeToLive,
null,
schemePortResolver,
dnsResolver);
poolingmgr.setConnectionConfigResolver(connectionConfigResolver);