Index: src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java =================================================================== --- src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java (revision 1825273) +++ src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java (working copy) @@ -483,8 +483,6 @@ private volatile HttpUriRequest currentRequest; // Accessed from multiple threads - private volatile boolean resetSSLContext; - protected HTTPHC4Impl(HTTPSamplerBase testElement) { super(testElement); } @@ -539,6 +537,7 @@ log.debug("Start : sample {} method {} followingRedirect {} depth {}", url, method, areFollowingRedirect, frameDepth); } + JMeterVariables jMeterVariables = JMeterContextService.getContext().getVariables(); HTTPSampleResult res = createSampleResult(url, method); @@ -549,7 +548,7 @@ clientContext.setAttribute(CONTEXT_ATTRIBUTE_AUTH_MANAGER, getAuthManager()); try { - httpClient = setupClient(url, clientContext); + httpClient = setupClient(jMeterVariables, url, clientContext); URI uri = url.toURI(); if (method.equals(HTTPConstants.POST)) { httpRequest = new HttpPost(uri); @@ -589,7 +588,7 @@ return res; } - setupClientContextBeforeSample(localContext); + setupClientContextBeforeSample(jMeterVariables, localContext); res.sampleStart(); @@ -609,7 +608,7 @@ // Needs to be done after execute to pick up all the headers final HttpRequest request = (HttpRequest) localContext.getAttribute(HttpCoreContext.HTTP_REQUEST); - extractClientContextAfterSample(localContext); + extractClientContextAfterSample(jMeterVariables, localContext); // We've finished with the request, so we can add the LocalAddress to it for display if (localAddress != null) { request.addHeader(HEADER_LOCAL_ADDRESS, localAddress.toString()); @@ -718,14 +717,14 @@ /** * Store in JMeter Variables the UserToken so that the SSL context is reused * See Bug 57804 + * @param jMeterVariables {@link JMeterVariables} * @param localContext {@link HttpContext} */ - private void extractClientContextAfterSample(HttpContext localContext) { + private void extractClientContextAfterSample(JMeterVariables jMeterVariables, HttpContext localContext) { Object userToken = localContext.getAttribute(HttpClientContext.USER_TOKEN); if(userToken != null) { log.debug("Extracted from HttpContext user token:{} storing it as JMeter variable:{}", userToken, JMETER_VARIABLE_USER_TOKEN); // During recording JMeterContextService.getContext().getVariables() is null - JMeterVariables jMeterVariables = JMeterContextService.getContext().getVariables(); if (jMeterVariables != null) { jMeterVariables.putObject(JMETER_VARIABLE_USER_TOKEN, userToken); } @@ -735,12 +734,12 @@ /** * Configure the UserToken so that the SSL context is reused * See Bug 57804 + * @param jMeterVariables {@link JMeterVariables} * @param localContext {@link HttpContext} */ - private void setupClientContextBeforeSample(HttpContext localContext) { + private void setupClientContextBeforeSample(JMeterVariables jMeterVariables, HttpContext localContext) { Object userToken = null; // During recording JMeterContextService.getContext().getVariables() is null - JMeterVariables jMeterVariables = JMeterContextService.getContext().getVariables(); if(jMeterVariables != null) { userToken = jMeterVariables.getObject(JMETER_VARIABLE_USER_TOKEN); } @@ -934,7 +933,7 @@ } } - private CloseableHttpClient setupClient(URL url, HttpClientContext clientContext) throws GeneralSecurityException { + private CloseableHttpClient setupClient(JMeterVariables jMeterVariables, URL url, HttpClientContext clientContext) throws GeneralSecurityException { Map> mapHttpClientPerHttpClientKey = HTTPCLIENTS_CACHE_PER_THREAD_AND_HTTPCLIENTKEY.get(); @@ -972,7 +971,7 @@ httpClient = pair != null ? pair.getLeft() : null; } - resetSSLStateIfNeeded(url, clientContext, pair); + resetStateIfNeeded(jMeterVariables, clientContext, mapHttpClientPerHttpClientKey); if (httpClient == null) { // One-time init for this client DnsResolver resolver = this.testElement.getDNSResolver(); @@ -1080,21 +1079,36 @@ *
  • Close current Idle or Expired connections that hold SSL State
  • *
  • Remove HttpClientContext.USER_TOKEN from {@link HttpClientContext}
  • * - * @param url {@link URL} + * @param jMeterVariables {@link JMeterVariables} * @param clientContext {@link HttpClientContext} - * @param pair {@link Pair} holding {@link CloseableHttpClient} and {@link PoolingHttpClientConnectionManager} + * @param mapHttpClientPerHttpClientKey Map of {@link Pair} holding {@link CloseableHttpClient} and {@link PoolingHttpClientConnectionManager} */ - private void resetSSLStateIfNeeded(URL url, HttpClientContext clientContext, - Pair pair) { - if (resetSSLContext && HTTPConstants.PROTOCOL_HTTPS.equalsIgnoreCase(url.getProtocol())) { - if(pair != null) { - PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = pair.getRight(); - poolingHttpClientConnectionManager.closeExpiredConnections(); - poolingHttpClientConnectionManager.closeIdleConnections(1L, TimeUnit.MICROSECONDS); - } + private void resetStateIfNeeded(JMeterVariables jMeterVariables, + HttpClientContext clientContext, + Map> mapHttpClientPerHttpClientKey) { + if (resetSSLContext.get()) { + closeCurrentConnections(mapHttpClientPerHttpClientKey); clientContext.removeAttribute(HttpClientContext.USER_TOKEN); ((JsseSSLManager) SSLManager.getInstance()).resetContext(); - resetSSLContext = false; + resetSSLContext.set(false); + } + if (closeConnections.get()) { + closeCurrentConnections(mapHttpClientPerHttpClientKey); + clientContext.removeAttribute(HttpClientContext.USER_TOKEN); + closeConnections.set(false); + } + } + + /** + * @param mapHttpClientPerHttpClientKey + */ + private void closeCurrentConnections( + Map> mapHttpClientPerHttpClientKey) { + for (Pair pair : + mapHttpClientPerHttpClientKey.values()) { + PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = pair.getRight(); + poolingHttpClientConnectionManager.closeExpiredConnections(); + poolingHttpClientConnectionManager.closeIdleConnections(1L, TimeUnit.MICROSECONDS); } } @@ -1700,8 +1714,11 @@ @Override protected void notifyFirstSampleAfterLoopRestart() { - log.debug("notifyFirstSampleAfterLoopRestart"); - resetSSLContext = !USE_CACHED_SSL_CONTEXT; + log.debug("notifyFirstSampleAfterLoopRestart called " + + "with config(reuse.http.connections={}, https.use.cached.ssl.context={})", + REUSE_HTTP_CONNECTIONS, USE_CACHED_SSL_CONTEXT); + closeConnections.set(!REUSE_HTTP_CONNECTIONS); + resetSSLContext.set(!USE_CACHED_SSL_CONTEXT); } @Override Index: src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHCAbstractImpl.java =================================================================== --- src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHCAbstractImpl.java (revision 1825265) +++ src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPHCAbstractImpl.java (working copy) @@ -78,10 +78,28 @@ // -1 means not defined protected static final int SO_TIMEOUT = JMeterUtils.getPropDefault("httpclient.timeout", -1); + // Control reuse of HTTP connections in subsequent iterations + protected static final boolean REUSE_HTTP_CONNECTIONS = + JMeterUtils.getPropDefault("reuse.http.connections", false);//$NON-NLS-1$ + // Control reuse of cached SSL Context in subsequent iterations protected static final boolean USE_CACHED_SSL_CONTEXT = JMeterUtils.getPropDefault("https.use.cached.ssl.context", true);//$NON-NLS-1$ + /** + * Whether SSL State/Context should be reset + * Shared state for any HC based implementation, because SSL contexts are the same + */ + protected static final ThreadLocal resetSSLContext = + ThreadLocal.withInitial(() -> Boolean.FALSE); + + /** + * Whether connections should be closed + * Shared state for any HC based implementation, + */ + protected static final ThreadLocal closeConnections = + ThreadLocal.withInitial(() -> Boolean.FALSE); + static { if(!StringUtils.isEmpty(JMeterUtils.getProperty("httpclient.timeout"))) { //$NON-NLS-1$ log.warn("You're using property 'httpclient.timeout' that will soon be deprecated for HttpClient3.1, you should either set " Index: src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java =================================================================== --- src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java (revision 1825265) +++ src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java (working copy) @@ -36,8 +36,6 @@ private transient HTTPAbstractImpl impl; - private transient volatile boolean notifyFirstSampleAfterLoopRestart; - public HTTPSamplerProxy(){ super(); } @@ -66,11 +64,6 @@ return errorResult(ex, new HTTPSampleResult()); } } - // see https://bz.apache.org/bugzilla/show_bug.cgi?id=51380 - if(notifyFirstSampleAfterLoopRestart) { - impl.notifyFirstSampleAfterLoopRestart(); - notifyFirstSampleAfterLoopRestart = false; - } return impl.sample(u, method, areFollowingRedirect, depth); } @@ -97,6 +90,8 @@ */ @Override public void testIterationStart(LoopIterationEvent event) { - notifyFirstSampleAfterLoopRestart = true; + if (impl != null) { + impl.notifyFirstSampleAfterLoopRestart(); + } } }