Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

NEXUS-5368: Stale connections in pool

Explicitly controlling the duration for how long
is connection kept alive (it's total lifespan).

Some servers announce their "wish" for how long to
keep connection alive with non standard "Keep-Alive"
response header, and HC4 obeys that header. But some
servers does not do this (like RSO) and are silently
dropping the connection, while HC4 in this case
sets expiration time of the connection to "indefinite"
(forever).

With this change, we take over explicit control over
keep alive duration, and we prevent HC4 to pool connections
forever.
  • Loading branch information...
commit b38af77f842aff0d98175fe63b607d1ba59a1ba3 1 parent f716acb
@cstamas cstamas authored
View
94 nexus/nexus-proxy/src/main/java/org/sonatype/nexus/apachehttpclient/Hc4ProviderImpl.java
@@ -68,7 +68,7 @@
/**
* Default implementation of {@link Hc4Provider}.
- *
+ *
* @author cstamas
* @since 2.2
*/
@@ -78,6 +78,7 @@
extends AbstractLoggingComponent
implements Hc4Provider
{
+
/**
* Key for customizing connection pool maximum size. Value should be integer equal to 0 or greater. Pool size of 0
* will actually prevent use of pool. Any positive number means the actual size of the pool to be created. This is a
@@ -103,15 +104,15 @@
private static final int CONNECTION_POOL_SIZE_DEFAULT = 20;
/**
- * Key for customizing connection pool keep-alive. In other words, how long open connections (sockets) are kept in
- * pool before evicted and closed. Value is milliseconds.
+ * Key for customizing connection pool idle time. In other words, how long open connections (sockets) are kept in
+ * pool idle (unused) before they get evicted and closed. Value is milliseconds.
*/
- private static final String CONNECTION_POOL_KEEPALIVE_KEY = "nexus.apacheHttpClient4x.connectionPoolKeepalive";
+ private static final String CONNECTION_POOL_IDLE_TIME_KEY = "nexus.apacheHttpClient4x.connectionPoolIdleTime";
/**
- * Default pool keep-alive: 1 minute.
+ * Default pool idle time: 1 minute.
*/
- private static final long CONNECTION_POOL_KEEPALIVE_DEFAULT = TimeUnit.MINUTES.toMillis( 1 );
+ private static final long CONNECTION_POOL_IDLE_TIME_DEFAULT = TimeUnit.MINUTES.toMillis( 1 );
/**
* Key for customizing connection pool timeout. In other words, how long should a HTTP request execution be blocked
@@ -120,9 +121,20 @@
private static final String CONNECTION_POOL_TIMEOUT_KEY = "nexus.apacheHttpClient4x.connectionPoolTimeout";
/**
- * Default pool timeout: equals to {@link #CONNECTION_POOL_KEEPALIVE_DEFAULT}.
+ * Default pool timeout: 1 minute.
+ */
+ private static final long CONNECTION_POOL_TIMEOUT_DEFAULT = TimeUnit.MINUTES.toMillis( 1 );
+
+ /**
+ * Key for customizing default (and max) keep alive duration when remote server does not state anything,
+ * or states some unreal high value. Value is milliseconds.
+ */
+ private static final String KEEP_ALIVE_MAX_DURATION_KEY = "nexus.apacheHttpClient4x.keepAliveMaxDuration";
+
+ /**
+ * Default keep alive max duration: 1 minute.
*/
- private static final long CONNECTION_POOL_TIMEOUT_DEFAULT = CONNECTION_POOL_KEEPALIVE_DEFAULT;
+ private static final long KEEP_ALIVE_MAX_DURATION_DEFAULT = TimeUnit.MINUTES.toMillis( 1 );
// ==
@@ -158,36 +170,38 @@
/**
* Constructor.
- *
+ *
* @param applicationConfiguration the Nexus {@link ApplicationConfiguration}.
- * @param userAgentBuilder UA builder component.
- * @param eventBus the event multicaster
- * @param jmxInstaller installer to expose pool information over JMX.
+ * @param userAgentBuilder UA builder component.
+ * @param eventBus the event multicaster
+ * @param jmxInstaller installer to expose pool information over JMX.
*/
@Inject
public Hc4ProviderImpl( final ApplicationConfiguration applicationConfiguration,
- final UserAgentBuilder userAgentBuilder,
- final EventBus eventBus,
- final PoolingClientConnectionManagerMBeanInstaller jmxInstaller )
+ final UserAgentBuilder userAgentBuilder,
+ final EventBus eventBus,
+ final PoolingClientConnectionManagerMBeanInstaller jmxInstaller )
{
this.applicationConfiguration = Preconditions.checkNotNull( applicationConfiguration );
this.userAgentBuilder = Preconditions.checkNotNull( userAgentBuilder );
this.jmxInstaller = Preconditions.checkNotNull( jmxInstaller );
this.sharedConnectionManager = createClientConnectionManager();
- this.evictingThread = new EvictingThread( sharedConnectionManager, getConnectionPoolKeepalive() );
+ this.evictingThread = new EvictingThread( sharedConnectionManager, getConnectionPoolIdleTime() );
this.evictingThread.start();
this.eventBus = Preconditions.checkNotNull( eventBus );
this.eventBus.register( this );
this.jmxInstaller.register( sharedConnectionManager );
- getLogger().info( "{} started up (keep-alive {} millis), listening for shutdown.", getClass().getSimpleName(),
- getConnectionPoolKeepalive() );
+ getLogger().info(
+ "{} started up (connectionPoolMaxSize {}, connectionPoolSize {}, connectionPoolIdleTime {} ms, connectionPoolTimeout {} ms, keepAliveMaxDuration {} ms)",
+ getClass().getSimpleName(), getConnectionPoolMaxSize(), getConnectionPoolSize(),
+ getConnectionPoolIdleTime(), getConnectionPoolTimeout(), getKeepAliveMaxDuration() );
}
// configuration
/**
* Returns the pool max size.
- *
+ *
* @return pool max size
*/
protected int getConnectionPoolMaxSize()
@@ -197,7 +211,7 @@ protected int getConnectionPoolMaxSize()
/**
* Returns the pool size per route.
- *
+ *
* @return pool per route size
*/
protected int getConnectionPoolSize()
@@ -206,18 +220,18 @@ protected int getConnectionPoolSize()
}
/**
- * Returns the keep alive (idle open) time in milliseconds.
- *
- * @return keep alive in milliseconds.
+ * Returns the connection pool idle (idle as unused but pooled) time in milliseconds.
+ *
+ * @return idle time in milliseconds.
*/
- protected long getConnectionPoolKeepalive()
+ protected long getConnectionPoolIdleTime()
{
- return SystemPropertiesHelper.getLong( CONNECTION_POOL_KEEPALIVE_KEY, CONNECTION_POOL_KEEPALIVE_DEFAULT );
+ return SystemPropertiesHelper.getLong( CONNECTION_POOL_IDLE_TIME_KEY, CONNECTION_POOL_IDLE_TIME_DEFAULT );
}
/**
* Returns the pool timeout in milliseconds.
- *
+ *
* @return pool timeout in milliseconds.
*/
protected long getConnectionPoolTimeout()
@@ -226,8 +240,18 @@ protected long getConnectionPoolTimeout()
}
/**
+ * Returns the maximum Keep-Alive duration in milliseconds.
+ *
+ * @return default Keep-Alive duration in milliseconds.
+ */
+ protected long getKeepAliveMaxDuration()
+ {
+ return SystemPropertiesHelper.getLong( KEEP_ALIVE_MAX_DURATION_KEY, KEEP_ALIVE_MAX_DURATION_DEFAULT );
+ }
+
+ /**
* Returns the connection timeout in milliseconds. The timeout until connection is established.
- *
+ *
* @param context
* @return the connection timeout in milliseconds.
*/
@@ -246,7 +270,7 @@ protected int getConnectionTimeout( final RemoteStorageContext context )
/**
* Returns the SO_SOCKET timeout in milliseconds. The timeout for waiting for data on established connection.
- *
+ *
* @param context
* @return the SO_SOCKET timeout in milliseconds.
*/
@@ -325,6 +349,7 @@ public DefaultHttpClient createHttpClient( final RemoteStorageContext context )
private static class DefaultHttpClientImpl
extends DefaultHttpClient
{
+
private DefaultHttpClientImpl( final ClientConnectionManager conman, final HttpParams params )
{
super( conman, params );
@@ -340,7 +365,7 @@ protected BasicHttpProcessor createHttpProcessor()
}
protected DefaultHttpClient createHttpClient( final RemoteStorageContext context,
- final ClientConnectionManager clientConnectionManager )
+ final ClientConnectionManager clientConnectionManager )
{
final DefaultHttpClient httpClient =
new DefaultHttpClientImpl( clientConnectionManager, createHttpParams( context ) );
@@ -348,9 +373,11 @@ protected DefaultHttpClient createHttpClient( final RemoteStorageContext context
configureProxy( httpClient, context.getRemoteProxySettings() );
// obey the given retries count and apply it to client.
final int retries =
- context.getRemoteConnectionSettings() != null ? context.getRemoteConnectionSettings().getRetrievalRetryCount()
+ context.getRemoteConnectionSettings() != null
+ ? context.getRemoteConnectionSettings().getRetrievalRetryCount()
: 0;
httpClient.setHttpRequestRetryHandler( new StandardHttpRequestRetryHandler( retries, false ) );
+ httpClient.setKeepAliveStrategy( new NexusConnectionKeepAliveStrategy( getKeepAliveMaxDuration() ) );
return httpClient;
}
@@ -388,7 +415,7 @@ protected PoolingClientConnectionManager createClientConnectionManager()
// ==
protected void configureAuthentication( final DefaultHttpClient httpClient, final RemoteAuthenticationSettings ras,
- final HttpHost proxyHost )
+ final HttpHost proxyHost )
{
if ( ras != null )
{
@@ -413,14 +440,15 @@ else if ( ras instanceof NtlmRemoteAuthenticationSettings )
authorisationPreference.add( 0, AuthPolicy.NTLM );
getLogger().info( "... {} authentication setup for NTLM domain '{}'", authScope, nras.getNtlmDomain() );
credentials =
- new NTCredentials( nras.getUsername(), nras.getPassword(), nras.getNtlmHost(), nras.getNtlmDomain() );
+ new NTCredentials( nras.getUsername(), nras.getPassword(), nras.getNtlmHost(),
+ nras.getNtlmDomain() );
}
else if ( ras instanceof UsernamePasswordRemoteAuthenticationSettings )
{
final UsernamePasswordRemoteAuthenticationSettings uras =
(UsernamePasswordRemoteAuthenticationSettings) ras;
getLogger().info( "... {} authentication setup for remote storage with username '{}'", authScope,
- uras.getUsername() );
+ uras.getUsername() );
credentials = new UsernamePasswordCredentials( uras.getUsername(), uras.getPassword() );
}
View
70 ...y/src/main/java/org/sonatype/nexus/apachehttpclient/NexusConnectionKeepAliveStrategy.java
@@ -0,0 +1,70 @@
+/**
+ * Sonatype Nexus (TM) Open Source Version
+ * Copyright (c) 2007-2012 Sonatype, Inc.
+ * All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions.
+ *
+ * This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0,
+ * which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html.
+ *
+ * Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks
+ * of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the
+ * Eclipse Foundation. All other trademarks are the property of their respective owners.
+ */
+package org.sonatype.nexus.apachehttpclient;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
+import org.apache.http.protocol.HttpContext;
+
+/**
+ * Nexus connection keep alive strategy, that differs from the HC4 default one only in one thing: when server does
+ * not state timeout, it never says "indefinite" (meaning pool it forever), but instead a finite amount of time.
+ *
+ * @author cstamas
+ * @since 2.3
+ */
+public class NexusConnectionKeepAliveStrategy
+ extends DefaultConnectionKeepAliveStrategy
+{
+
+ /**
+ * The max duration for how long to pool a connection in milliseconds. Used as default too, instead of
+ * "indefinite" case.
+ */
+ private final long maxKeepAliveDuration;
+
+ /**
+ * Constructor.
+ *
+ * @param maxKeepAliveDuration the max duration in millis for how long to pool the connection.
+ */
+ public NexusConnectionKeepAliveStrategy( final long maxKeepAliveDuration )
+ {
+ this.maxKeepAliveDuration = maxKeepAliveDuration;
+ }
+
+ /**
+ * Returns the duration of time which this connection can be safely kept
+ * idle. Nexus by default does not "believe" much to remote servers, and will never
+ * keep connection pooled "forever", nor will keep it pooled for unreasonable long time.
+ *
+ * @param response
+ * @param context
+ * @return the duration of time which this connection can be safely kept idle in pool.
+ */
+ public long getKeepAliveDuration( HttpResponse response, HttpContext context )
+ {
+ // ask super class
+ final long result = super.getKeepAliveDuration( response, context );
+ if ( result < 0 )
+ {
+ // if "indefinite", use default
+ return maxKeepAliveDuration;
+ }
+ else
+ {
+ // else "cap" it with max
+ return Math.min( result, maxKeepAliveDuration );
+ }
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.