Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
Tamas Cservenak authored November 22, 2012
94  nexus/nexus-proxy/src/main/java/org/sonatype/nexus/apachehttpclient/Hc4ProviderImpl.java
@@ -68,7 +68,7 @@
68 68
 
69 69
 /**
70 70
  * Default implementation of {@link Hc4Provider}.
71  
- * 
  71
+ *
72 72
  * @author cstamas
73 73
  * @since 2.2
74 74
  */
@@ -78,6 +78,7 @@
78 78
     extends AbstractLoggingComponent
79 79
     implements Hc4Provider
80 80
 {
  81
+
81 82
     /**
82 83
      * Key for customizing connection pool maximum size. Value should be integer equal to 0 or greater. Pool size of 0
83 84
      * 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 @@
103 104
     private static final int CONNECTION_POOL_SIZE_DEFAULT = 20;
104 105
 
105 106
     /**
106  
-     * Key for customizing connection pool keep-alive. In other words, how long open connections (sockets) are kept in
107  
-     * pool before evicted and closed. Value is milliseconds.
  107
+     * Key for customizing connection pool idle time. In other words, how long open connections (sockets) are kept in
  108
+     * pool idle (unused) before they get evicted and closed. Value is milliseconds.
108 109
      */
109  
-    private static final String CONNECTION_POOL_KEEPALIVE_KEY = "nexus.apacheHttpClient4x.connectionPoolKeepalive";
  110
+    private static final String CONNECTION_POOL_IDLE_TIME_KEY = "nexus.apacheHttpClient4x.connectionPoolIdleTime";
110 111
 
111 112
     /**
112  
-     * Default pool keep-alive: 1 minute.
  113
+     * Default pool idle time: 1 minute.
113 114
      */
114  
-    private static final long CONNECTION_POOL_KEEPALIVE_DEFAULT = TimeUnit.MINUTES.toMillis( 1 );
  115
+    private static final long CONNECTION_POOL_IDLE_TIME_DEFAULT = TimeUnit.MINUTES.toMillis( 1 );
115 116
 
116 117
     /**
117 118
      * Key for customizing connection pool timeout. In other words, how long should a HTTP request execution be blocked
@@ -120,9 +121,20 @@
120 121
     private static final String CONNECTION_POOL_TIMEOUT_KEY = "nexus.apacheHttpClient4x.connectionPoolTimeout";
121 122
 
122 123
     /**
123  
-     * Default pool timeout: equals to {@link #CONNECTION_POOL_KEEPALIVE_DEFAULT}.
  124
+     * Default pool timeout: 1 minute.
  125
+     */
  126
+    private static final long CONNECTION_POOL_TIMEOUT_DEFAULT = TimeUnit.MINUTES.toMillis( 1 );
  127
+
  128
+    /**
  129
+     * Key for customizing default (and max) keep alive duration when remote server does not state anything,
  130
+     * or states some unreal high value. Value is milliseconds.
  131
+     */
  132
+    private static final String KEEP_ALIVE_MAX_DURATION_KEY = "nexus.apacheHttpClient4x.keepAliveMaxDuration";
  133
+
  134
+    /**
  135
+     * Default keep alive max duration: 1 minute.
124 136
      */
125  
-    private static final long CONNECTION_POOL_TIMEOUT_DEFAULT = CONNECTION_POOL_KEEPALIVE_DEFAULT;
  137
+    private static final long KEEP_ALIVE_MAX_DURATION_DEFAULT = TimeUnit.MINUTES.toMillis( 1 );
126 138
 
127 139
     // ==
128 140
 
@@ -158,36 +170,38 @@
158 170
 
159 171
     /**
160 172
      * Constructor.
161  
-     * 
  173
+     *
162 174
      * @param applicationConfiguration the Nexus {@link ApplicationConfiguration}.
163  
-     * @param userAgentBuilder UA builder component.
164  
-     * @param eventBus the event multicaster
165  
-     * @param jmxInstaller installer to expose pool information over JMX.
  175
+     * @param userAgentBuilder         UA builder component.
  176
+     * @param eventBus                 the event multicaster
  177
+     * @param jmxInstaller             installer to expose pool information over JMX.
166 178
      */
167 179
     @Inject
168 180
     public Hc4ProviderImpl( final ApplicationConfiguration applicationConfiguration,
169  
-                            final UserAgentBuilder userAgentBuilder,
170  
-                            final EventBus eventBus,
171  
-                            final PoolingClientConnectionManagerMBeanInstaller jmxInstaller )
  181
+        final UserAgentBuilder userAgentBuilder,
  182
+        final EventBus eventBus,
  183
+        final PoolingClientConnectionManagerMBeanInstaller jmxInstaller )
172 184
     {
173 185
         this.applicationConfiguration = Preconditions.checkNotNull( applicationConfiguration );
174 186
         this.userAgentBuilder = Preconditions.checkNotNull( userAgentBuilder );
175 187
         this.jmxInstaller = Preconditions.checkNotNull( jmxInstaller );
176 188
         this.sharedConnectionManager = createClientConnectionManager();
177  
-        this.evictingThread = new EvictingThread( sharedConnectionManager, getConnectionPoolKeepalive() );
  189
+        this.evictingThread = new EvictingThread( sharedConnectionManager, getConnectionPoolIdleTime() );
178 190
         this.evictingThread.start();
179 191
         this.eventBus = Preconditions.checkNotNull( eventBus );
180 192
         this.eventBus.register( this );
181 193
         this.jmxInstaller.register( sharedConnectionManager );
182  
-        getLogger().info( "{} started up (keep-alive {} millis), listening for shutdown.", getClass().getSimpleName(),
183  
-            getConnectionPoolKeepalive() );
  194
+        getLogger().info(
  195
+            "{} started up (connectionPoolMaxSize {}, connectionPoolSize {}, connectionPoolIdleTime {} ms, connectionPoolTimeout {} ms, keepAliveMaxDuration {} ms)",
  196
+            getClass().getSimpleName(), getConnectionPoolMaxSize(), getConnectionPoolSize(),
  197
+            getConnectionPoolIdleTime(), getConnectionPoolTimeout(), getKeepAliveMaxDuration() );
184 198
     }
185 199
 
186 200
     // configuration
187 201
 
188 202
     /**
189 203
      * Returns the pool max size.
190  
-     * 
  204
+     *
191 205
      * @return pool max size
192 206
      */
193 207
     protected int getConnectionPoolMaxSize()
@@ -197,7 +211,7 @@ protected int getConnectionPoolMaxSize()
197 211
 
198 212
     /**
199 213
      * Returns the pool size per route.
200  
-     * 
  214
+     *
201 215
      * @return pool per route size
202 216
      */
203 217
     protected int getConnectionPoolSize()
@@ -206,18 +220,18 @@ protected int getConnectionPoolSize()
206 220
     }
207 221
 
208 222
     /**
209  
-     * Returns the keep alive (idle open) time in milliseconds.
210  
-     * 
211  
-     * @return keep alive in milliseconds.
  223
+     * Returns the connection pool idle (idle as unused but pooled) time in milliseconds.
  224
+     *
  225
+     * @return idle time in milliseconds.
212 226
      */
213  
-    protected long getConnectionPoolKeepalive()
  227
+    protected long getConnectionPoolIdleTime()
214 228
     {
215  
-        return SystemPropertiesHelper.getLong( CONNECTION_POOL_KEEPALIVE_KEY, CONNECTION_POOL_KEEPALIVE_DEFAULT );
  229
+        return SystemPropertiesHelper.getLong( CONNECTION_POOL_IDLE_TIME_KEY, CONNECTION_POOL_IDLE_TIME_DEFAULT );
216 230
     }
217 231
 
218 232
     /**
219 233
      * Returns the pool timeout in milliseconds.
220  
-     * 
  234
+     *
221 235
      * @return pool timeout in milliseconds.
222 236
      */
223 237
     protected long getConnectionPoolTimeout()
@@ -226,8 +240,18 @@ protected long getConnectionPoolTimeout()
226 240
     }
227 241
 
228 242
     /**
  243
+     * Returns the maximum Keep-Alive duration in milliseconds.
  244
+     *
  245
+     * @return default Keep-Alive duration in milliseconds.
  246
+     */
  247
+    protected long getKeepAliveMaxDuration()
  248
+    {
  249
+        return SystemPropertiesHelper.getLong( KEEP_ALIVE_MAX_DURATION_KEY, KEEP_ALIVE_MAX_DURATION_DEFAULT );
  250
+    }
  251
+
  252
+    /**
229 253
      * Returns the connection timeout in milliseconds. The timeout until connection is established.
230  
-     * 
  254
+     *
231 255
      * @param context
232 256
      * @return the connection timeout in milliseconds.
233 257
      */
@@ -246,7 +270,7 @@ protected int getConnectionTimeout( final RemoteStorageContext context )
246 270
 
247 271
     /**
248 272
      * Returns the SO_SOCKET timeout in milliseconds. The timeout for waiting for data on established connection.
249  
-     * 
  273
+     *
250 274
      * @param context
251 275
      * @return the SO_SOCKET timeout in milliseconds.
252 276
      */
@@ -325,6 +349,7 @@ public DefaultHttpClient createHttpClient( final RemoteStorageContext context )
325 349
     private static class DefaultHttpClientImpl
326 350
         extends DefaultHttpClient
327 351
     {
  352
+
328 353
         private DefaultHttpClientImpl( final ClientConnectionManager conman, final HttpParams params )
329 354
         {
330 355
             super( conman, params );
@@ -340,7 +365,7 @@ protected BasicHttpProcessor createHttpProcessor()
340 365
     }
341 366
 
342 367
     protected DefaultHttpClient createHttpClient( final RemoteStorageContext context,
343  
-                                                  final ClientConnectionManager clientConnectionManager )
  368
+        final ClientConnectionManager clientConnectionManager )
344 369
     {
345 370
         final DefaultHttpClient httpClient =
346 371
             new DefaultHttpClientImpl( clientConnectionManager, createHttpParams( context ) );
@@ -348,9 +373,11 @@ protected DefaultHttpClient createHttpClient( final RemoteStorageContext context
348 373
         configureProxy( httpClient, context.getRemoteProxySettings() );
349 374
         // obey the given retries count and apply it to client.
350 375
         final int retries =
351  
-            context.getRemoteConnectionSettings() != null ? context.getRemoteConnectionSettings().getRetrievalRetryCount()
  376
+            context.getRemoteConnectionSettings() != null
  377
+                ? context.getRemoteConnectionSettings().getRetrievalRetryCount()
352 378
                 : 0;
353 379
         httpClient.setHttpRequestRetryHandler( new StandardHttpRequestRetryHandler( retries, false ) );
  380
+        httpClient.setKeepAliveStrategy( new NexusConnectionKeepAliveStrategy( getKeepAliveMaxDuration() ) );
354 381
         return httpClient;
355 382
     }
356 383
 
@@ -388,7 +415,7 @@ protected PoolingClientConnectionManager createClientConnectionManager()
388 415
     // ==
389 416
 
390 417
     protected void configureAuthentication( final DefaultHttpClient httpClient, final RemoteAuthenticationSettings ras,
391  
-                                            final HttpHost proxyHost )
  418
+        final HttpHost proxyHost )
392 419
     {
393 420
         if ( ras != null )
394 421
         {
@@ -413,14 +440,15 @@ else if ( ras instanceof NtlmRemoteAuthenticationSettings )
413 440
                 authorisationPreference.add( 0, AuthPolicy.NTLM );
414 441
                 getLogger().info( "... {} authentication setup for NTLM domain '{}'", authScope, nras.getNtlmDomain() );
415 442
                 credentials =
416  
-                    new NTCredentials( nras.getUsername(), nras.getPassword(), nras.getNtlmHost(), nras.getNtlmDomain() );
  443
+                    new NTCredentials( nras.getUsername(), nras.getPassword(), nras.getNtlmHost(),
  444
+                                       nras.getNtlmDomain() );
417 445
             }
418 446
             else if ( ras instanceof UsernamePasswordRemoteAuthenticationSettings )
419 447
             {
420 448
                 final UsernamePasswordRemoteAuthenticationSettings uras =
421 449
                     (UsernamePasswordRemoteAuthenticationSettings) ras;
422 450
                 getLogger().info( "... {} authentication setup for remote storage with username '{}'", authScope,
423  
-                    uras.getUsername() );
  451
+                                  uras.getUsername() );
424 452
                 credentials = new UsernamePasswordCredentials( uras.getUsername(), uras.getPassword() );
425 453
             }
426 454
 
70  nexus/nexus-proxy/src/main/java/org/sonatype/nexus/apachehttpclient/NexusConnectionKeepAliveStrategy.java
... ...
@@ -0,0 +1,70 @@
  1
+/**
  2
+ * Sonatype Nexus (TM) Open Source Version
  3
+ * Copyright (c) 2007-2012 Sonatype, Inc.
  4
+ * All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions.
  5
+ *
  6
+ * This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0,
  7
+ * which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html.
  8
+ *
  9
+ * Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks
  10
+ * of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the
  11
+ * Eclipse Foundation. All other trademarks are the property of their respective owners.
  12
+ */
  13
+package org.sonatype.nexus.apachehttpclient;
  14
+
  15
+import org.apache.http.HttpResponse;
  16
+import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
  17
+import org.apache.http.protocol.HttpContext;
  18
+
  19
+/**
  20
+ * Nexus connection keep alive strategy, that differs from the HC4 default one only in one thing: when server does
  21
+ * not state timeout, it never says "indefinite" (meaning pool it forever), but instead a finite amount of time.
  22
+ *
  23
+ * @author cstamas
  24
+ * @since 2.3
  25
+ */
  26
+public class NexusConnectionKeepAliveStrategy
  27
+    extends DefaultConnectionKeepAliveStrategy
  28
+{
  29
+
  30
+    /**
  31
+     * The max duration for how long to pool a connection in milliseconds. Used as default too, instead of
  32
+     * "indefinite" case.
  33
+     */
  34
+    private final long maxKeepAliveDuration;
  35
+
  36
+    /**
  37
+     * Constructor.
  38
+     *
  39
+     * @param maxKeepAliveDuration     the max duration in millis for how long to pool the connection.
  40
+     */
  41
+    public NexusConnectionKeepAliveStrategy( final long maxKeepAliveDuration )
  42
+    {
  43
+        this.maxKeepAliveDuration = maxKeepAliveDuration;
  44
+    }
  45
+
  46
+    /**
  47
+     * Returns the duration of time which this connection can be safely kept
  48
+     * idle. Nexus by default does not "believe" much to remote servers, and will never
  49
+     * keep connection pooled "forever", nor will keep it pooled for unreasonable long time.
  50
+     *
  51
+     * @param response
  52
+     * @param context
  53
+     * @return the duration of time which this connection can be safely kept idle in pool.
  54
+     */
  55
+    public long getKeepAliveDuration( HttpResponse response, HttpContext context )
  56
+    {
  57
+        // ask super class
  58
+        final long result = super.getKeepAliveDuration( response, context );
  59
+        if ( result < 0 )
  60
+        {
  61
+            // if "indefinite", use default
  62
+            return maxKeepAliveDuration;
  63
+        }
  64
+        else
  65
+        {
  66
+            // else "cap" it with max
  67
+            return Math.min( result, maxKeepAliveDuration );
  68
+        }
  69
+    }
  70
+}

0 notes on commit b38af77

Please sign in to comment.
Something went wrong with that request. Please try again.