You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2018/01/08 10:13:37 UTC

[2/2] httpcomponents-client git commit: Completed rewrite of re-validation code in the classic caching exec interceptor; added re-validation to the async caching exec interceptor

Completed rewrite of re-validation code in the classic caching exec interceptor; added re-validation to the async caching exec interceptor


Project: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/commit/16147b18
Tree: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/tree/16147b18
Diff: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/diff/16147b18

Branch: refs/heads/master
Commit: 16147b1852d03bb1e9b3b039cf5df0292c9a61d6
Parents: f16ac3e
Author: Oleg Kalnichevski <ol...@apache.org>
Authored: Sun Jan 7 14:51:02 2018 +0100
Committer: Oleg Kalnichevski <ol...@apache.org>
Committed: Mon Jan 8 11:13:17 2018 +0100

----------------------------------------------------------------------
 .../http/impl/cache/AsyncCachingExec.java       |  70 ++++++++--
 .../http/impl/cache/BasicHttpAsyncCache.java    |   9 ++
 .../client5/http/impl/cache/BasicHttpCache.java |   9 ++
 .../hc/client5/http/impl/cache/CacheConfig.java | 140 +++----------------
 .../http/impl/cache/CacheRevalidatorBase.java   |   9 +-
 .../hc/client5/http/impl/cache/CachingExec.java | 107 +++++++++-----
 .../cache/CachingHttp2AsyncClientBuilder.java   |  31 +++-
 .../cache/CachingHttpAsyncClientBuilder.java    |  31 +++-
 .../impl/cache/CachingHttpClientBuilder.java    |  44 ++++--
 .../cache/DefaultAsyncCacheRevalidator.java     | 112 ++++++++-------
 .../impl/cache/DefaultCacheRevalidator.java     |  59 +++-----
 .../client5/http/impl/cache/HttpAsyncCache.java |   2 +
 .../hc/client5/http/impl/cache/HttpCache.java   |   2 +
 .../schedule/ImmediateSchedulingStrategy.java   |   2 +-
 .../http/impl/cache/AbstractProtocolTest.java   |  12 +-
 .../http/impl/cache/TestCachingExec.java        |  14 +-
 .../http/impl/cache/TestCachingExecChain.java   |  30 ++--
 .../cache/TestCachingHttpClientBuilder.java     |  48 -------
 .../impl/cache/TestHttpCacheJiraNumber1147.java |   2 +-
 .../http/impl/cache/TestProtocolDeviations.java |   2 +-
 .../impl/cache/TestProtocolRequirements.java    |  10 +-
 .../http/impl/cache/TestRFC5861Compliance.java  |  67 ++++++---
 .../hc/client5/http/impl/ExecSupport.java       |   4 +
 .../apache/hc/client5/http/impl/Operations.java |  37 +++++
 .../async/InternalAbstractHttpAsyncClient.java  |   2 +-
 .../impl/async/MinimalHttp2AsyncClient.java     |   2 +-
 .../http/impl/async/MinimalHttpAsyncClient.java |   2 +-
 .../http/impl/classic/InternalHttpClient.java   |   2 +-
 28 files changed, 487 insertions(+), 374 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
index 6989bba..36620b7 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
@@ -32,6 +32,7 @@ import java.nio.ByteBuffer;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -47,8 +48,10 @@ import org.apache.hc.client5.http.cache.HttpAsyncCacheStorage;
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
 import org.apache.hc.client5.http.cache.ResourceFactory;
 import org.apache.hc.client5.http.cache.ResourceIOException;
+import org.apache.hc.client5.http.impl.ExecSupport;
 import org.apache.hc.client5.http.impl.RequestCopier;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.schedule.SchedulingStrategy;
 import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
@@ -87,21 +90,16 @@ import org.apache.hc.core5.util.ByteArrayBuffer;
 public class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler {
 
     private final HttpAsyncCache responseCache;
+    private final DefaultAsyncCacheRevalidator cacheRevalidator;
     private final ConditionalRequestBuilder<HttpRequest> conditionalRequestBuilder;
 
-    public AsyncCachingExec(final HttpAsyncCache cache, final CacheConfig config) {
+    AsyncCachingExec(final HttpAsyncCache cache, final DefaultAsyncCacheRevalidator cacheRevalidator, final CacheConfig config) {
         super(config);
         this.responseCache = Args.notNull(cache, "Response cache");
+        this.cacheRevalidator = cacheRevalidator;
         this.conditionalRequestBuilder = new ConditionalRequestBuilder<>(RequestCopier.INSTANCE);
     }
 
-    public AsyncCachingExec(
-            final ResourceFactory resourceFactory,
-            final HttpAsyncCacheStorage storage,
-            final CacheConfig config) {
-        this(new BasicHttpAsyncCache(resourceFactory, storage), config);
-    }
-
     AsyncCachingExec(
             final HttpAsyncCache responseCache,
             final CacheValidityPolicy validityPolicy,
@@ -109,16 +107,37 @@ public class AsyncCachingExec extends CachingExecBase implements AsyncExecChainH
             final CachedHttpResponseGenerator responseGenerator,
             final CacheableRequestPolicy cacheableRequestPolicy,
             final CachedResponseSuitabilityChecker suitabilityChecker,
-            final ConditionalRequestBuilder<HttpRequest> conditionalRequestBuilder,
             final ResponseProtocolCompliance responseCompliance,
             final RequestProtocolCompliance requestCompliance,
+            final DefaultAsyncCacheRevalidator cacheRevalidator,
+            final ConditionalRequestBuilder<HttpRequest> conditionalRequestBuilder,
             final CacheConfig config) {
         super(validityPolicy, responseCachingPolicy, responseGenerator, cacheableRequestPolicy,
                 suitabilityChecker, responseCompliance, requestCompliance, config);
         this.responseCache = responseCache;
+        this.cacheRevalidator = cacheRevalidator;
         this.conditionalRequestBuilder = conditionalRequestBuilder;
     }
 
+    public AsyncCachingExec(
+            final HttpAsyncCache cache,
+            final ScheduledExecutorService executorService,
+            final SchedulingStrategy schedulingStrategy,
+            final CacheConfig config) {
+        this(cache,
+                executorService != null ? new DefaultAsyncCacheRevalidator(executorService, schedulingStrategy) : null,
+                config);
+    }
+
+    public AsyncCachingExec(
+            final ResourceFactory resourceFactory,
+            final HttpAsyncCacheStorage storage,
+            final ScheduledExecutorService executorService,
+            final SchedulingStrategy schedulingStrategy,
+            final CacheConfig config) {
+        this(new BasicHttpAsyncCache(resourceFactory, storage), executorService, schedulingStrategy, config);
+    }
+
     private void triggerResponse(
             final SimpleHttpResponse cacheResponse,
             final AsyncExecChain.Scope scope,
@@ -609,7 +628,38 @@ public class AsyncCachingExec extends CachingExecBase implements AsyncExecChainH
             triggerResponse(cacheResponse, scope, asyncExecCallback);
         } else if (!(entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request))) {
             log.debug("Revalidating cache entry");
-            revalidateCacheEntry(target, request, entityProducer, scope, chain, asyncExecCallback, entry);
+            if (cacheRevalidator != null
+                    && !staleResponseNotAllowed(request, entry, now)
+                    && validityPolicy.mayReturnStaleWhileRevalidating(entry, now)) {
+                log.debug("Serving stale with asynchronous revalidation");
+                try {
+                    final SimpleHttpResponse cacheResponse = generateCachedResponse(request, context, entry, now);
+                    final String exchangeId = ExecSupport.getNextExchangeId();
+                    final AsyncExecChain.Scope fork = new AsyncExecChain.Scope(
+                            exchangeId,
+                            scope.route,
+                            scope.originalRequest,
+                            new ComplexFuture<>(null),
+                            HttpClientContext.create(),
+                            scope.execRuntime.fork());
+                    cacheRevalidator.revalidateCacheEntry(
+                            responseCache.generateKey(target, request, entry),
+                            asyncExecCallback,
+                            new DefaultAsyncCacheRevalidator.RevalidationCall() {
+
+                                @Override
+                                public void execute(final AsyncExecCallback asyncExecCallback) {
+                                    revalidateCacheEntry(target, request, entityProducer, fork, chain, asyncExecCallback, entry);
+                                }
+
+                            });
+                    triggerResponse(cacheResponse, scope, asyncExecCallback);
+                } catch (final ResourceIOException ex) {
+                    asyncExecCallback.failed(ex);
+                }
+            } else {
+                revalidateCacheEntry(target, request, entityProducer, scope, chain, asyncExecCallback, entry);
+            }
         } else {
             log.debug("Cache entry not usable; calling backend");
             callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java
index 945da46..7b38fa0 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpAsyncCache.java
@@ -85,6 +85,15 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
     }
 
     @Override
+    public String generateKey(final HttpHost host, final HttpRequest request, final HttpCacheEntry cacheEntry) {
+        if (cacheEntry == null) {
+            return cacheKeyGenerator.generateKey(host, request);
+        } else {
+            return cacheKeyGenerator.generateKey(host, request, cacheEntry);
+        }
+    }
+
+    @Override
     public Cancellable flushCacheEntriesFor(
             final HttpHost host, final HttpRequest request, final FutureCallback<Boolean> callback) {
         if (log.isDebugEnabled()) {

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java
index 2cfdcb4..248cbcc 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/BasicHttpCache.java
@@ -89,6 +89,15 @@ class BasicHttpCache implements HttpCache {
     }
 
     @Override
+    public String generateKey(final HttpHost host, final HttpRequest request, final HttpCacheEntry cacheEntry) {
+        if (cacheEntry == null) {
+            return cacheKeyGenerator.generateKey(host, request);
+        } else {
+            return cacheKeyGenerator.generateKey(host, request, cacheEntry);
+        }
+    }
+
+    @Override
     public void flushCacheEntriesFor(final HttpHost host, final HttpRequest request) {
         if (log.isDebugEnabled()) {
             log.debug("Flush cache entries: " + host + "; " + new RequestLine(request));

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheConfig.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheConfig.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheConfig.java
index d63e623..21c92fd 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheConfig.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheConfig.java
@@ -90,15 +90,10 @@ import org.apache.hc.core5.util.Args;
  * <p><b>Background validation</b>. The cache module supports the
  * {@code stale-while-revalidate} directive of
  * <a href="http://tools.ietf.org/html/rfc5861">RFC5861</a>, which allows
- * certain cache entry revalidations to happen in the background. You may
- * want to tweak the settings for the {@link
- * CacheConfig#getAsynchronousWorkersCore() minimum} and {@link
- * CacheConfig#getAsynchronousWorkersMax() maximum} number of background
- * worker threads, as well as the {@link
- * CacheConfig#getAsynchronousWorkerIdleLifetimeSecs() maximum time they
- * can be idle before being reclaimed}. You can also control the {@link
- * CacheConfig#getRevalidationQueueSize() size of the queue} used for
- * revalidations when there aren't enough workers to keep up with demand.</p>
+ * certain cache entry revalidations to happen in the background. Asynchronous
+ * validation is enabled by default but it could be disabled by setting the number
+ * of re-validation workers to {@code 0} with {@link CacheConfig#getAsynchronousWorkers()}
+ * parameter</p>
  */
 public class CacheConfig implements Cloneable {
 
@@ -142,21 +137,7 @@ public class CacheConfig implements Cloneable {
     /** Default number of worker threads to allow for background revalidations
      * resulting from the stale-while-revalidate directive.
      */
-    public static final int DEFAULT_ASYNCHRONOUS_WORKERS_MAX = 1;
-
-    /** Default minimum number of worker threads to allow for background
-     * revalidations resulting from the stale-while-revalidate directive.
-     */
-    public static final int DEFAULT_ASYNCHRONOUS_WORKERS_CORE = 1;
-
-    /** Default maximum idle lifetime for a background revalidation thread
-     * before it gets reclaimed.
-     */
-    public static final int DEFAULT_ASYNCHRONOUS_WORKER_IDLE_LIFETIME_SECS = 60;
-
-    /** Default maximum queue length for background revalidation requests.
-     */
-    public static final int DEFAULT_REVALIDATION_QUEUE_SIZE = 100;
+    public static final int DEFAULT_ASYNCHRONOUS_WORKERS = 1;
 
     public static final CacheConfig DEFAULT = new Builder().build();
 
@@ -170,10 +151,7 @@ public class CacheConfig implements Cloneable {
     private final long heuristicDefaultLifetime;
     private final boolean sharedCache;
     private final boolean freshnessCheckEnabled;
-    private final int asynchronousWorkersMax;
-    private final int asynchronousWorkersCore;
-    private final int asynchronousWorkerIdleLifetimeSecs;
-    private final int revalidationQueueSize;
+    private final int asynchronousWorkers;
     private final boolean neverCacheHTTP10ResponsesWithQuery;
 
     CacheConfig(
@@ -187,10 +165,7 @@ public class CacheConfig implements Cloneable {
             final long heuristicDefaultLifetime,
             final boolean sharedCache,
             final boolean freshnessCheckEnabled,
-            final int asynchronousWorkersMax,
-            final int asynchronousWorkersCore,
-            final int asynchronousWorkerIdleLifetimeSecs,
-            final int revalidationQueueSize,
+            final int asynchronousWorkers,
             final boolean neverCacheHTTP10ResponsesWithQuery) {
         super();
         this.maxObjectSize = maxObjectSize;
@@ -203,10 +178,7 @@ public class CacheConfig implements Cloneable {
         this.heuristicDefaultLifetime = heuristicDefaultLifetime;
         this.sharedCache = sharedCache;
         this.freshnessCheckEnabled = freshnessCheckEnabled;
-        this.asynchronousWorkersMax = asynchronousWorkersMax;
-        this.asynchronousWorkersCore = asynchronousWorkersCore;
-        this.asynchronousWorkerIdleLifetimeSecs = asynchronousWorkerIdleLifetimeSecs;
-        this.revalidationQueueSize = revalidationQueueSize;
+        this.asynchronousWorkers = asynchronousWorkers;
         this.neverCacheHTTP10ResponsesWithQuery = neverCacheHTTP10ResponsesWithQuery;
     }
 
@@ -306,33 +278,8 @@ public class CacheConfig implements Cloneable {
      * revalidations due to the {@code stale-while-revalidate} directive. A
      * value of 0 means background revalidations are disabled.
      */
-    public int getAsynchronousWorkersMax() {
-        return asynchronousWorkersMax;
-    }
-
-    /**
-     * Returns the minimum number of threads to keep alive for background
-     * revalidations due to the {@code stale-while-revalidate} directive.
-     */
-    public int getAsynchronousWorkersCore() {
-        return asynchronousWorkersCore;
-    }
-
-    /**
-     * Returns the current maximum idle lifetime in seconds for a
-     * background revalidation worker thread. If a worker thread is idle
-     * for this long, and there are more than the core number of worker
-     * threads alive, the worker will be reclaimed.
-     */
-    public int getAsynchronousWorkerIdleLifetimeSecs() {
-        return asynchronousWorkerIdleLifetimeSecs;
-    }
-
-    /**
-     * Returns the current maximum queue size for background revalidations.
-     */
-    public int getRevalidationQueueSize() {
-        return revalidationQueueSize;
+    public int getAsynchronousWorkers() {
+        return asynchronousWorkers;
     }
 
     @Override
@@ -354,10 +301,7 @@ public class CacheConfig implements Cloneable {
             .setHeuristicCoefficient(config.getHeuristicCoefficient())
             .setHeuristicDefaultLifetime(config.getHeuristicDefaultLifetime())
             .setSharedCache(config.isSharedCache())
-            .setAsynchronousWorkersMax(config.getAsynchronousWorkersMax())
-            .setAsynchronousWorkersCore(config.getAsynchronousWorkersCore())
-            .setAsynchronousWorkerIdleLifetimeSecs(config.getAsynchronousWorkerIdleLifetimeSecs())
-            .setRevalidationQueueSize(config.getRevalidationQueueSize())
+            .setAsynchronousWorkers(config.getAsynchronousWorkers())
             .setNeverCacheHTTP10ResponsesWithQueryString(config.isNeverCacheHTTP10ResponsesWithQuery());
     }
 
@@ -374,10 +318,7 @@ public class CacheConfig implements Cloneable {
         private long heuristicDefaultLifetime;
         private boolean sharedCache;
         private boolean freshnessCheckEnabled;
-        private int asynchronousWorkersMax;
-        private int asynchronousWorkersCore;
-        private int asynchronousWorkerIdleLifetimeSecs;
-        private int revalidationQueueSize;
+        private int asynchronousWorkers;
         private boolean neverCacheHTTP10ResponsesWithQuery;
 
         Builder() {
@@ -391,10 +332,7 @@ public class CacheConfig implements Cloneable {
             this.heuristicDefaultLifetime = DEFAULT_HEURISTIC_LIFETIME;
             this.sharedCache = true;
             this.freshnessCheckEnabled = true;
-            this.asynchronousWorkersMax = DEFAULT_ASYNCHRONOUS_WORKERS_MAX;
-            this.asynchronousWorkersCore = DEFAULT_ASYNCHRONOUS_WORKERS_CORE;
-            this.asynchronousWorkerIdleLifetimeSecs = DEFAULT_ASYNCHRONOUS_WORKER_IDLE_LIFETIME_SECS;
-            this.revalidationQueueSize = DEFAULT_REVALIDATION_QUEUE_SIZE;
+            this.asynchronousWorkers = DEFAULT_ASYNCHRONOUS_WORKERS;
         }
 
         /**
@@ -495,42 +433,11 @@ public class CacheConfig implements Cloneable {
         /**
          * Sets the maximum number of threads to allow for background
          * revalidations due to the {@code stale-while-revalidate} directive.
-         * @param asynchronousWorkersMax number of threads; a value of 0 disables background
+         * @param asynchronousWorkers number of threads; a value of 0 disables background
          * revalidations.
          */
-        public Builder setAsynchronousWorkersMax(final int asynchronousWorkersMax) {
-            this.asynchronousWorkersMax = asynchronousWorkersMax;
-            return this;
-        }
-
-        /**
-         * Sets the minimum number of threads to keep alive for background
-         * revalidations due to the {@code stale-while-revalidate} directive.
-         * @param asynchronousWorkersCore should be greater than zero and less than or equal
-         *   to {@code getAsynchronousWorkersMax()}
-         */
-        public Builder setAsynchronousWorkersCore(final int asynchronousWorkersCore) {
-            this.asynchronousWorkersCore = asynchronousWorkersCore;
-            return this;
-        }
-
-        /**
-         * Sets the current maximum idle lifetime in seconds for a
-         * background revalidation worker thread. If a worker thread is idle
-         * for this long, and there are more than the core number of worker
-         * threads alive, the worker will be reclaimed.
-         * @param asynchronousWorkerIdleLifetimeSecs idle lifetime in seconds
-         */
-        public Builder setAsynchronousWorkerIdleLifetimeSecs(final int asynchronousWorkerIdleLifetimeSecs) {
-            this.asynchronousWorkerIdleLifetimeSecs = asynchronousWorkerIdleLifetimeSecs;
-            return this;
-        }
-
-        /**
-         * Sets the current maximum queue size for background revalidations.
-         */
-        public Builder setRevalidationQueueSize(final int revalidationQueueSize) {
-            this.revalidationQueueSize = revalidationQueueSize;
+        public Builder setAsynchronousWorkers(final int asynchronousWorkers) {
+            this.asynchronousWorkers = asynchronousWorkers;
             return this;
         }
 
@@ -547,6 +454,11 @@ public class CacheConfig implements Cloneable {
             return this;
         }
 
+        public Builder setFreshnessCheckEnabled(final boolean freshnessCheckEnabled) {
+            this.freshnessCheckEnabled = freshnessCheckEnabled;
+            return this;
+        }
+
         public CacheConfig build() {
             return new CacheConfig(
                     maxObjectSize,
@@ -559,10 +471,7 @@ public class CacheConfig implements Cloneable {
                     heuristicDefaultLifetime,
                     sharedCache,
                     freshnessCheckEnabled,
-                    asynchronousWorkersMax,
-                    asynchronousWorkersCore,
-                    asynchronousWorkerIdleLifetimeSecs,
-                    revalidationQueueSize,
+                    asynchronousWorkers,
                     neverCacheHTTP10ResponsesWithQuery);
         }
 
@@ -581,10 +490,7 @@ public class CacheConfig implements Cloneable {
                 .append(", heuristicDefaultLifetime=").append(this.heuristicDefaultLifetime)
                 .append(", sharedCache=").append(this.sharedCache)
                 .append(", freshnessCheckEnabled=").append(this.freshnessCheckEnabled)
-                .append(", asynchronousWorkersMax=").append(this.asynchronousWorkersMax)
-                .append(", asynchronousWorkersCore=").append(this.asynchronousWorkersCore)
-                .append(", asynchronousWorkerIdleLifetimeSecs=").append(this.asynchronousWorkerIdleLifetimeSecs)
-                .append(", revalidationQueueSize=").append(this.revalidationQueueSize)
+                .append(", asynchronousWorkers=").append(this.asynchronousWorkers)
                 .append(", neverCacheHTTP10ResponsesWithQuery=").append(this.neverCacheHTTP10ResponsesWithQuery)
                 .append("]");
         return builder.toString();

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheRevalidatorBase.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheRevalidatorBase.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheRevalidatorBase.java
index 25c0ecf..be9d3af 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheRevalidatorBase.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheRevalidatorBase.java
@@ -33,6 +33,7 @@ import java.util.Iterator;
 import java.util.Set;
 import java.util.concurrent.Future;
 import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 
@@ -62,7 +63,7 @@ class CacheRevalidatorBase implements Closeable {
 
     }
 
-    public static ScheduledExecutor wrap(final ScheduledThreadPoolExecutor threadPoolExecutor) {
+    public static ScheduledExecutor wrap(final ScheduledExecutorService executorService) {
 
         return new ScheduledExecutor() {
 
@@ -70,18 +71,18 @@ class CacheRevalidatorBase implements Closeable {
             public ScheduledFuture<?> schedule(final Runnable command, final TimeValue timeValue) throws RejectedExecutionException {
                 Args.notNull(command, "Runnable");
                 Args.notNull(timeValue, "Time value");
-                return threadPoolExecutor.schedule(command, timeValue.getDuration(), timeValue.getTimeUnit());
+                return executorService.schedule(command, timeValue.getDuration(), timeValue.getTimeUnit());
             }
 
             @Override
             public void shutdown() {
-                threadPoolExecutor.shutdown();
+                executorService.shutdown();
             }
 
             @Override
             public void awaitTermination(final Timeout timeout) throws InterruptedException {
                 Args.notNull(timeout, "Timeout");
-                threadPoolExecutor.awaitTermination(timeout.getDuration(), timeout.getTimeUnit());
+                executorService.awaitTermination(timeout.getDuration(), timeout.getTimeUnit());
             }
 
         };

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
index 774c726..a01e696 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
@@ -31,6 +31,7 @@ import java.io.InputStream;
 import java.util.Date;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
 
 import org.apache.hc.client5.http.HttpRoute;
 import org.apache.hc.client5.http.async.methods.SimpleBody;
@@ -43,8 +44,10 @@ import org.apache.hc.client5.http.cache.ResourceFactory;
 import org.apache.hc.client5.http.cache.ResourceIOException;
 import org.apache.hc.client5.http.classic.ExecChain;
 import org.apache.hc.client5.http.classic.ExecChainHandler;
+import org.apache.hc.client5.http.impl.ExecSupport;
 import org.apache.hc.client5.http.impl.classic.ClassicRequestCopier;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.schedule.SchedulingStrategy;
 import org.apache.hc.client5.http.utils.DateUtils;
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
@@ -101,27 +104,18 @@ import org.apache.logging.log4j.Logger;
 public class CachingExec extends CachingExecBase implements ExecChainHandler {
 
     private final HttpCache responseCache;
+    private final DefaultCacheRevalidator cacheRevalidator;
     private final ConditionalRequestBuilder<ClassicHttpRequest> conditionalRequestBuilder;
 
     private final Logger log = LogManager.getLogger(getClass());
 
-    public CachingExec(final HttpCache cache, final CacheConfig config) {
+    CachingExec(final HttpCache cache, final DefaultCacheRevalidator cacheRevalidator, final CacheConfig config) {
         super(config);
         this.responseCache = Args.notNull(cache, "Response cache");
+        this.cacheRevalidator = cacheRevalidator;
         this.conditionalRequestBuilder = new ConditionalRequestBuilder<>(ClassicRequestCopier.INSTANCE);
     }
 
-    public CachingExec(
-            final ResourceFactory resourceFactory,
-            final HttpCacheStorage storage,
-            final CacheConfig config) {
-        this(new BasicHttpCache(resourceFactory, storage), config);
-    }
-
-    public CachingExec() {
-        this(new BasicHttpCache(), CacheConfig.DEFAULT);
-    }
-
     CachingExec(
             final HttpCache responseCache,
             final CacheValidityPolicy validityPolicy,
@@ -129,16 +123,37 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
             final CachedHttpResponseGenerator responseGenerator,
             final CacheableRequestPolicy cacheableRequestPolicy,
             final CachedResponseSuitabilityChecker suitabilityChecker,
-            final ConditionalRequestBuilder<ClassicHttpRequest> conditionalRequestBuilder,
             final ResponseProtocolCompliance responseCompliance,
             final RequestProtocolCompliance requestCompliance,
+            final DefaultCacheRevalidator cacheRevalidator,
+            final ConditionalRequestBuilder<ClassicHttpRequest> conditionalRequestBuilder,
             final CacheConfig config) {
         super(validityPolicy, responseCachingPolicy, responseGenerator, cacheableRequestPolicy,
                 suitabilityChecker, responseCompliance, requestCompliance, config);
         this.responseCache = responseCache;
+        this.cacheRevalidator = cacheRevalidator;
         this.conditionalRequestBuilder = conditionalRequestBuilder;
     }
 
+    public CachingExec(
+            final HttpCache cache,
+            final ScheduledExecutorService executorService,
+            final SchedulingStrategy schedulingStrategy,
+            final CacheConfig config) {
+        this(cache,
+                executorService != null ? new DefaultCacheRevalidator(executorService, schedulingStrategy) : null,
+                config);
+    }
+
+    public CachingExec(
+            final ResourceFactory resourceFactory,
+            final HttpCacheStorage storage,
+            final ScheduledExecutorService executorService,
+            final SchedulingStrategy schedulingStrategy,
+            final CacheConfig config) {
+        this(new BasicHttpCache(resourceFactory, storage), executorService, schedulingStrategy, config);
+    }
+
     @Override
     public ClassicHttpResponse execute(
             final ClassicHttpRequest request,
@@ -167,7 +182,7 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
 
         final SimpleHttpResponse fatalErrorResponse = getFatallyNoncompliantResponse(request, context);
         if (fatalErrorResponse != null) {
-            return convert(fatalErrorResponse);
+            return convert(fatalErrorResponse, scope);
         }
 
         requestCompliance.makeRequestCompliant(request);
@@ -188,7 +203,7 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
         }
     }
 
-    private static ClassicHttpResponse convert(final SimpleHttpResponse cacheResponse) {
+    private static ClassicHttpResponse convert(final SimpleHttpResponse cacheResponse, final ExecChain.Scope scope) {
         if (cacheResponse == null) {
             return null;
         }
@@ -205,6 +220,7 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
                 response.setEntity(new ByteArrayEntity(body.getBodyBytes(), body.getContentType()));
             }
         }
+        scope.clientContext.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
         return response;
     }
 
@@ -240,15 +256,11 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
         if (suitabilityChecker.canCachedResponseBeUsed(target, request, entry, now)) {
             log.debug("Cache hit");
             try {
-                final ClassicHttpResponse response = convert(generateCachedResponse(request, context, entry, now));
-                context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
-                return response;
+                return convert(generateCachedResponse(request, context, entry, now), scope);
             } catch (final ResourceIOException ex) {
                 recordCacheFailure(target, request);
                 if (!mayCallBackend(request)) {
-                    final ClassicHttpResponse response = convert(generateGatewayTimeout(context));
-                    context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
-                    return response;
+                    return convert(generateGatewayTimeout(context), scope);
                 } else {
                     setResponseStatus(scope.clientContext, CacheResponseStatus.FAILURE);
                     return chain.proceed(request, scope);
@@ -256,15 +268,38 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
             }
         } else if (!mayCallBackend(request)) {
             log.debug("Cache entry not suitable but only-if-cached requested");
-            final ClassicHttpResponse response = convert(generateGatewayTimeout(context));
-            context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
-            return response;
+            return convert(generateGatewayTimeout(context), scope);
         } else if (!(entry.getStatus() == HttpStatus.SC_NOT_MODIFIED && !suitabilityChecker.isConditional(request))) {
             log.debug("Revalidating cache entry");
             try {
-                return revalidateCacheEntry(target, request, scope, chain, entry);
+                if (cacheRevalidator != null
+                        && !staleResponseNotAllowed(request, entry, now)
+                        && validityPolicy.mayReturnStaleWhileRevalidating(entry, now)) {
+                    log.debug("Serving stale with asynchronous revalidation");
+                    final String exchangeId = ExecSupport.getNextExchangeId();
+                    final ExecChain.Scope fork = new ExecChain.Scope(
+                            exchangeId,
+                            scope.route,
+                            scope.originalRequest,
+                            scope.execRuntime.fork(null),
+                            HttpClientContext.create());
+                    final SimpleHttpResponse response = generateCachedResponse(request, context, entry, now);
+                    cacheRevalidator.revalidateCacheEntry(
+                            responseCache.generateKey(target, request, entry),
+                            new DefaultCacheRevalidator.RevalidationCall() {
+
+                        @Override
+                        public ClassicHttpResponse execute() throws HttpException, IOException {
+                            return revalidateCacheEntry(target, request, fork, chain, entry);
+                        }
+
+                    });
+                    return convert(response, scope);
+                } else {
+                    return revalidateCacheEntry(target, request, scope, chain, entry);
+                }
             } catch (final IOException ioex) {
-                return convert(handleRevalidationFailure(request, context, entry, now));
+                return convert(handleRevalidationFailure(request, context, entry, now), scope);
             }
         } else {
             log.debug("Cache entry not usable; calling backend");
@@ -307,9 +342,9 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
                         target, request, cacheEntry, backendResponse, requestDate, responseDate);
                 if (suitabilityChecker.isConditional(request)
                         && suitabilityChecker.allConditionalsMatch(request, updatedEntry, new Date())) {
-                    return convert(responseGenerator.generateNotModifiedResponse(updatedEntry));
+                    return convert(responseGenerator.generateNotModifiedResponse(updatedEntry), scope);
                 }
-                return convert(responseGenerator.generateResponse(request, updatedEntry));
+                return convert(responseGenerator.generateResponse(request, updatedEntry), scope);
             }
 
             if (staleIfErrorAppliesTo(statusCode)
@@ -318,7 +353,7 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
                 try {
                     final SimpleHttpResponse cachedResponse = responseGenerator.generateResponse(request, cacheEntry);
                     cachedResponse.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\"");
-                    return convert(cachedResponse);
+                    return convert(cachedResponse, scope);
                 } finally {
                     backendResponse.close();
                 }
@@ -344,7 +379,7 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
         final boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse);
         if (cacheable) {
             storeRequestIfModifiedSinceFor304Response(request, backendResponse);
-            return cacheAndReturnResponse(target, request, backendResponse, requestDate, responseDate);
+            return cacheAndReturnResponse(target, request, backendResponse, scope, requestDate, responseDate);
         } else {
             log.debug("Backend response is not cacheable");
             responseCache.flushCacheEntriesFor(target, request);
@@ -356,6 +391,7 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
             final HttpHost target,
             final HttpRequest request,
             final ClassicHttpResponse backendResponse,
+            final ExecChain.Scope scope,
             final Date requestSent,
             final Date responseReceived) throws IOException {
         log.debug("Caching backend response");
@@ -395,7 +431,7 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
             cacheEntry = responseCache.createCacheEntry(target, request, backendResponse, buf, requestSent, responseReceived);
             log.debug("Backend response successfully cached (freshness check skipped)");
         }
-        return convert(responseGenerator.generateResponse(request, cacheEntry));
+        return convert(responseGenerator.generateResponse(request, cacheEntry), scope);
     }
 
     private ClassicHttpResponse handleCacheMiss(
@@ -423,8 +459,7 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
             final ExecChain.Scope scope,
             final ExecChain chain,
             final Map<String, Variant> variants) throws IOException, HttpException {
-        final ClassicHttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(
-                request, variants);
+        final ClassicHttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(request, variants);
 
         final Date requestDate = getCurrentDate();
         final ClassicHttpResponse backendResponse = chain.proceed(conditionalRequest, scope);
@@ -468,11 +503,11 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
                     target, conditionalRequest, backendResponse, matchingVariant, requestDate, responseDate);
             backendResponse.close();
             if (shouldSendNotModifiedResponse(request, responseEntry)) {
-                return convert(responseGenerator.generateNotModifiedResponse(responseEntry));
+                return convert(responseGenerator.generateNotModifiedResponse(responseEntry), scope);
             }
-            final SimpleHttpResponse resp = responseGenerator.generateResponse(request, responseEntry);
+            final SimpleHttpResponse response = responseGenerator.generateResponse(request, responseEntry);
             responseCache.reuseVariantEntryFor(target, request, matchingVariant);
-            return convert(resp);
+            return convert(response, scope);
         } catch (final IOException | RuntimeException ex) {
             backendResponse.close();
             throw ex;

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttp2AsyncClientBuilder.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttp2AsyncClientBuilder.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttp2AsyncClientBuilder.java
index 83b6935..927d79c 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttp2AsyncClientBuilder.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttp2AsyncClientBuilder.java
@@ -29,6 +29,8 @@ package org.apache.hc.client5.http.impl.cache;
 import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
 
 import org.apache.hc.client5.http.async.AsyncExecChainHandler;
 import org.apache.hc.client5.http.cache.HttpAsyncCacheInvalidator;
@@ -38,6 +40,8 @@ import org.apache.hc.client5.http.cache.HttpCacheStorage;
 import org.apache.hc.client5.http.cache.ResourceFactory;
 import org.apache.hc.client5.http.impl.ChainElements;
 import org.apache.hc.client5.http.impl.async.Http2AsyncClientBuilder;
+import org.apache.hc.client5.http.impl.schedule.ImmediateSchedulingStrategy;
+import org.apache.hc.client5.http.schedule.SchedulingStrategy;
 import org.apache.hc.core5.http.config.NamedElementChain;
 
 /**
@@ -51,6 +55,7 @@ public class CachingHttp2AsyncClientBuilder extends Http2AsyncClientBuilder {
     private ResourceFactory resourceFactory;
     private HttpAsyncCacheStorage storage;
     private File cacheDir;
+    private SchedulingStrategy schedulingStrategy;
     private CacheConfig cacheConfig;
     private HttpAsyncCacheInvalidator httpCacheInvalidator;
     private boolean deleteCache;
@@ -84,6 +89,11 @@ public class CachingHttp2AsyncClientBuilder extends Http2AsyncClientBuilder {
         return this;
     }
 
+    public final CachingHttp2AsyncClientBuilder setSchedulingStrategy(final SchedulingStrategy schedulingStrategy) {
+        this.schedulingStrategy = schedulingStrategy;
+        return this;
+    }
+
     public final CachingHttp2AsyncClientBuilder setCacheConfig(final CacheConfig cacheConfig) {
         this.cacheConfig = cacheConfig;
         return this;
@@ -138,7 +148,26 @@ public class CachingHttp2AsyncClientBuilder extends Http2AsyncClientBuilder {
                 CacheKeyGenerator.INSTANCE,
                 this.httpCacheInvalidator != null ? this.httpCacheInvalidator : new DefaultAsyncCacheInvalidator());
 
-        final AsyncCachingExec cachingExec = new AsyncCachingExec(httpCache, config);
+        DefaultAsyncCacheRevalidator cacheRevalidator = null;
+        if (config.getAsynchronousWorkers() > 0) {
+            final ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(config.getAsynchronousWorkers());
+            addCloseable(new Closeable() {
+
+                @Override
+                public void close() throws IOException {
+                    executorService.shutdownNow();
+                }
+
+            });
+            cacheRevalidator = new DefaultAsyncCacheRevalidator(
+                    executorService,
+                    this.schedulingStrategy != null ? this.schedulingStrategy : ImmediateSchedulingStrategy.INSTANCE);
+        }
+
+        final AsyncCachingExec cachingExec = new AsyncCachingExec(
+                httpCache,
+                cacheRevalidator,
+                config);
         execChainDefinition.addBefore(ChainElements.PROTOCOL.name(), cachingExec, ChainElements.CACHING.name());
     }
 

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpAsyncClientBuilder.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpAsyncClientBuilder.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpAsyncClientBuilder.java
index f44a04c..7295347 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpAsyncClientBuilder.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpAsyncClientBuilder.java
@@ -29,6 +29,8 @@ package org.apache.hc.client5.http.impl.cache;
 import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
 
 import org.apache.hc.client5.http.async.AsyncExecChainHandler;
 import org.apache.hc.client5.http.cache.HttpAsyncCacheInvalidator;
@@ -38,6 +40,8 @@ import org.apache.hc.client5.http.cache.HttpCacheStorage;
 import org.apache.hc.client5.http.cache.ResourceFactory;
 import org.apache.hc.client5.http.impl.ChainElements;
 import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
+import org.apache.hc.client5.http.impl.schedule.ImmediateSchedulingStrategy;
+import org.apache.hc.client5.http.schedule.SchedulingStrategy;
 import org.apache.hc.core5.http.config.NamedElementChain;
 
 /**
@@ -51,6 +55,7 @@ public class CachingHttpAsyncClientBuilder extends HttpAsyncClientBuilder {
     private ResourceFactory resourceFactory;
     private HttpAsyncCacheStorage storage;
     private File cacheDir;
+    private SchedulingStrategy schedulingStrategy;
     private CacheConfig cacheConfig;
     private HttpAsyncCacheInvalidator httpCacheInvalidator;
     private boolean deleteCache;
@@ -84,6 +89,11 @@ public class CachingHttpAsyncClientBuilder extends HttpAsyncClientBuilder {
         return this;
     }
 
+    public final CachingHttpAsyncClientBuilder setSchedulingStrategy(final SchedulingStrategy schedulingStrategy) {
+        this.schedulingStrategy = schedulingStrategy;
+        return this;
+    }
+
     public final CachingHttpAsyncClientBuilder setCacheConfig(final CacheConfig cacheConfig) {
         this.cacheConfig = cacheConfig;
         return this;
@@ -138,7 +148,26 @@ public class CachingHttpAsyncClientBuilder extends HttpAsyncClientBuilder {
                 CacheKeyGenerator.INSTANCE,
                 this.httpCacheInvalidator != null ? this.httpCacheInvalidator : new DefaultAsyncCacheInvalidator());
 
-        final AsyncCachingExec cachingExec = new AsyncCachingExec(httpCache, config);
+        DefaultAsyncCacheRevalidator cacheRevalidator = null;
+        if (config.getAsynchronousWorkers() > 0) {
+            final ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(config.getAsynchronousWorkers());
+            addCloseable(new Closeable() {
+
+                @Override
+                public void close() throws IOException {
+                    executorService.shutdownNow();
+                }
+
+            });
+            cacheRevalidator = new DefaultAsyncCacheRevalidator(
+                    executorService,
+                    this.schedulingStrategy != null ? this.schedulingStrategy : ImmediateSchedulingStrategy.INSTANCE);
+        }
+
+        final AsyncCachingExec cachingExec = new AsyncCachingExec(
+                httpCache,
+                cacheRevalidator,
+                config);
         execChainDefinition.addBefore(ChainElements.PROTOCOL.name(), cachingExec, ChainElements.CACHING.name());
     }
 

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java
index 9836479..714b34c 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingHttpClientBuilder.java
@@ -29,6 +29,8 @@ package org.apache.hc.client5.http.impl.cache;
 import java.io.Closeable;
 import java.io.File;
 import java.io.IOException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
 
 import org.apache.hc.client5.http.cache.HttpCacheInvalidator;
 import org.apache.hc.client5.http.cache.HttpCacheStorage;
@@ -36,6 +38,8 @@ import org.apache.hc.client5.http.cache.ResourceFactory;
 import org.apache.hc.client5.http.classic.ExecChainHandler;
 import org.apache.hc.client5.http.impl.ChainElements;
 import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
+import org.apache.hc.client5.http.impl.schedule.ImmediateSchedulingStrategy;
+import org.apache.hc.client5.http.schedule.SchedulingStrategy;
 import org.apache.hc.core5.http.config.NamedElementChain;
 
 /**
@@ -49,6 +53,7 @@ public class CachingHttpClientBuilder extends HttpClientBuilder {
     private ResourceFactory resourceFactory;
     private HttpCacheStorage storage;
     private File cacheDir;
+    private SchedulingStrategy schedulingStrategy;
     private CacheConfig cacheConfig;
     private HttpCacheInvalidator httpCacheInvalidator;
     private boolean deleteCache;
@@ -68,31 +73,32 @@ public class CachingHttpClientBuilder extends HttpClientBuilder {
         return this;
     }
 
-    public final CachingHttpClientBuilder setHttpCacheStorage(
-            final HttpCacheStorage storage) {
+    public final CachingHttpClientBuilder setHttpCacheStorage(final HttpCacheStorage storage) {
         this.storage = storage;
         return this;
     }
 
-    public final CachingHttpClientBuilder setCacheDir(
-            final File cacheDir) {
+    public final CachingHttpClientBuilder setCacheDir(final File cacheDir) {
         this.cacheDir = cacheDir;
         return this;
     }
 
-    public final CachingHttpClientBuilder setCacheConfig(
-            final CacheConfig cacheConfig) {
+    public final CachingHttpClientBuilder setSchedulingStrategy(final SchedulingStrategy schedulingStrategy) {
+        this.schedulingStrategy = schedulingStrategy;
+        return this;
+    }
+
+    public final CachingHttpClientBuilder setCacheConfig(final CacheConfig cacheConfig) {
         this.cacheConfig = cacheConfig;
         return this;
     }
 
-    public final CachingHttpClientBuilder setHttpCacheInvalidator(
-            final HttpCacheInvalidator cacheInvalidator) {
+    public final CachingHttpClientBuilder setHttpCacheInvalidator(final HttpCacheInvalidator cacheInvalidator) {
         this.httpCacheInvalidator = cacheInvalidator;
         return this;
     }
 
-    public CachingHttpClientBuilder setDeleteCache(final boolean deleteCache) {
+    public final CachingHttpClientBuilder setDeleteCache(final boolean deleteCache) {
         this.deleteCache = deleteCache;
         return this;
     }
@@ -136,7 +142,25 @@ public class CachingHttpClientBuilder extends HttpClientBuilder {
                 CacheKeyGenerator.INSTANCE,
                 this.httpCacheInvalidator != null ? this.httpCacheInvalidator : new DefaultCacheInvalidator());
 
-        final CachingExec cachingExec = new CachingExec(httpCache, config);
+        DefaultCacheRevalidator cacheRevalidator = null;
+        if (config.getAsynchronousWorkers() > 0) {
+            final ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(config.getAsynchronousWorkers());
+            addCloseable(new Closeable() {
+
+                @Override
+                public void close() throws IOException {
+                    executorService.shutdownNow();
+                }
+
+            });
+            cacheRevalidator = new DefaultCacheRevalidator(
+                    executorService,
+                    this.schedulingStrategy != null ? this.schedulingStrategy : ImmediateSchedulingStrategy.INSTANCE);
+        }
+        final CachingExec cachingExec = new CachingExec(
+                httpCache,
+                cacheRevalidator,
+                config);
         execChainDefinition.addBefore(ChainElements.PROTOCOL.name(), cachingExec, ChainElements.CACHING.name());
     }
 

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultAsyncCacheRevalidator.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultAsyncCacheRevalidator.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultAsyncCacheRevalidator.java
index 6324124..fd24741 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultAsyncCacheRevalidator.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultAsyncCacheRevalidator.java
@@ -26,20 +26,20 @@
  */
 package org.apache.hc.client5.http.impl.cache;
 
-import java.util.concurrent.ExecutionException;
+import java.io.IOException;
 import java.util.concurrent.Future;
 import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.hc.client5.http.async.AsyncExecCallback;
-import org.apache.hc.client5.http.async.AsyncExecChain;
-import org.apache.hc.client5.http.cache.HttpCacheEntry;
+import org.apache.hc.client5.http.impl.Operations;
 import org.apache.hc.client5.http.schedule.SchedulingStrategy;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.HttpRequest;
-import org.apache.hc.core5.http.nio.AsyncEntityProducer;
+import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.nio.AsyncDataConsumer;
 import org.apache.hc.core5.util.TimeValue;
 import org.apache.hc.core5.util.Timeout;
 
@@ -49,33 +49,10 @@ import org.apache.hc.core5.util.Timeout;
  */
 class DefaultAsyncCacheRevalidator extends CacheRevalidatorBase {
 
-    private static final Future<Void> NOOP_FUTURE = new Future<Void>() {
+    interface RevalidationCall {
 
-        @Override
-        public Void get() throws InterruptedException, ExecutionException {
-            return null;
-        }
-
-        @Override
-        public Void get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
-            return null;
-        }
-        @Override
-        public boolean cancel(final boolean mayInterruptIfRunning) {
-            return false;
-        }
-
-        @Override
-        public boolean isCancelled() {
-            return false;
-        }
-
-        @Override
-        public boolean isDone() {
-            return true;
-        }
-
-    };
+        void execute(AsyncExecCallback asyncExecCallback);;
+    }
 
     static class InternalScheduledExecutor implements ScheduledExecutor {
 
@@ -89,7 +66,7 @@ class DefaultAsyncCacheRevalidator extends CacheRevalidatorBase {
         public Future<?> schedule(final Runnable command, final TimeValue timeValue) throws RejectedExecutionException {
             if (timeValue.toMillis() <= 0) {
                 command.run();
-                return NOOP_FUTURE;
+                return new Operations.CompletedFuture<Void>(null);
             } else {
                 return executor.schedule(command, timeValue);
             }
@@ -107,7 +84,6 @@ class DefaultAsyncCacheRevalidator extends CacheRevalidatorBase {
 
     }
 
-    private final AsyncCachingExec cachingExec;
     private final CacheKeyGenerator cacheKeyGenerator;
 
     /**
@@ -116,42 +92,72 @@ class DefaultAsyncCacheRevalidator extends CacheRevalidatorBase {
      */
     public DefaultAsyncCacheRevalidator(
             final ScheduledExecutor scheduledExecutor,
-            final SchedulingStrategy schedulingStrategy,
-            final AsyncCachingExec cachingExec) {
+            final SchedulingStrategy schedulingStrategy) {
         super(new InternalScheduledExecutor(scheduledExecutor), schedulingStrategy);
-        this.cachingExec = cachingExec;
         this.cacheKeyGenerator = CacheKeyGenerator.INSTANCE;
 
     }
 
     /**
      * Create CacheValidator which will make ache revalidation requests
-     * using the supplied {@link SchedulingStrategy} and {@link ScheduledThreadPoolExecutor}.
+     * using the supplied {@link SchedulingStrategy} and {@link ScheduledExecutorService}.
      */
     public DefaultAsyncCacheRevalidator(
-            final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor,
-            final SchedulingStrategy schedulingStrategy,
-            final AsyncCachingExec cachingExec) {
-        this(wrap(scheduledThreadPoolExecutor), schedulingStrategy, cachingExec);
+            final ScheduledExecutorService executorService,
+            final SchedulingStrategy schedulingStrategy) {
+        this(wrap(executorService), schedulingStrategy);
     }
 
     /**
      * Schedules an asynchronous re-validation
      */
     public void revalidateCacheEntry(
-            final HttpHost target,
-            final HttpRequest request,
-            final AsyncEntityProducer entityProducer,
-            final AsyncExecChain.Scope scope,
-            final AsyncExecChain chain,
+            final String cacheKey ,
             final AsyncExecCallback asyncExecCallback,
-            final HttpCacheEntry entry) {
-        final String cacheKey = cacheKeyGenerator.generateKey(target, request, entry);
+            final RevalidationCall call) {
         scheduleRevalidation(cacheKey, new Runnable() {
 
                         @Override
                         public void run() {
-                            cachingExec.revalidateCacheEntry(target, request, entityProducer, scope, chain, asyncExecCallback, entry);
+                            call.execute(new AsyncExecCallback() {
+
+                                private final AtomicReference<HttpResponse> responseRef = new AtomicReference<>(null);
+
+                                @Override
+                                public AsyncDataConsumer handleResponse(
+                                        final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException {
+                                    responseRef.set(response);
+                                    return asyncExecCallback.handleResponse(response, entityDetails);
+                                }
+
+                                @Override
+                                public void completed() {
+                                    final HttpResponse httpResponse = responseRef.getAndSet(null);
+                                    if (httpResponse != null && httpResponse.getCode() < HttpStatus.SC_SERVER_ERROR && !isStale(httpResponse)) {
+                                        jobSuccessful(cacheKey);
+                                    } else {
+                                        jobFailed(cacheKey);
+                                    }
+                                    asyncExecCallback.completed();
+                                }
+
+                                @Override
+                                public void failed(final Exception cause) {
+                                    if (cause instanceof IOException) {
+                                        log.debug("Asynchronous revalidation failed due to I/O error", cause);
+                                    } else if (cause instanceof HttpException) {
+                                        log.error("HTTP protocol exception during asynchronous revalidation", cause);
+                                    } else {
+                                        log.error("Unexpected runtime exception thrown during asynchronous revalidation", cause);
+                                    }
+                                    try {
+                                        jobFailed(cacheKey);
+                                    } finally {
+                                        asyncExecCallback.failed(cause);
+                                    }
+                                }
+
+                            });
                         }
 
                     });

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultCacheRevalidator.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultCacheRevalidator.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultCacheRevalidator.java
index f033b86..8e343b3 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultCacheRevalidator.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/DefaultCacheRevalidator.java
@@ -27,15 +27,11 @@
 package org.apache.hc.client5.http.impl.cache;
 
 import java.io.IOException;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ScheduledExecutorService;
 
-import org.apache.hc.client5.http.cache.HttpCacheEntry;
-import org.apache.hc.client5.http.classic.ExecChain;
 import org.apache.hc.client5.http.schedule.SchedulingStrategy;
-import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.ClassicHttpResponse;
 import org.apache.hc.core5.http.HttpException;
-import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.HttpStatus;
 
 /**
@@ -44,8 +40,10 @@ import org.apache.hc.core5.http.HttpStatus;
  */
 class DefaultCacheRevalidator extends CacheRevalidatorBase {
 
-    private final CachingExec cachingExec;
-    private final CacheKeyGenerator cacheKeyGenerator;
+    interface RevalidationCall {
+
+        ClassicHttpResponse execute() throws IOException, HttpException;
+    }
 
     /**
      * Create DefaultCacheRevalidator which will make ache revalidation requests
@@ -53,56 +51,45 @@ class DefaultCacheRevalidator extends CacheRevalidatorBase {
      */
     public DefaultCacheRevalidator(
             final CacheRevalidatorBase.ScheduledExecutor scheduledExecutor,
-            final SchedulingStrategy schedulingStrategy,
-            final CachingExec cachingExec) {
+            final SchedulingStrategy schedulingStrategy) {
         super(scheduledExecutor, schedulingStrategy);
-        this.cachingExec = cachingExec;
-        this.cacheKeyGenerator = CacheKeyGenerator.INSTANCE;
-
     }
 
     /**
      * Create CacheValidator which will make ache revalidation requests
-     * using the supplied {@link SchedulingStrategy} and {@link ScheduledThreadPoolExecutor}.
+     * using the supplied {@link SchedulingStrategy} and {@link ScheduledExecutorService}.
      */
     public DefaultCacheRevalidator(
-            final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor,
-            final SchedulingStrategy schedulingStrategy,
-            final CachingExec cachingExec) {
-        this(wrap(scheduledThreadPoolExecutor), schedulingStrategy, cachingExec);
+            final ScheduledExecutorService scheduledThreadPoolExecutor,
+            final SchedulingStrategy schedulingStrategy) {
+        this(wrap(scheduledThreadPoolExecutor), schedulingStrategy);
     }
 
     /**
      * Schedules an asynchronous re-validation
      */
     public void revalidateCacheEntry(
-            final HttpHost target,
-            final ClassicHttpRequest request,
-            final ExecChain.Scope scope,
-            final ExecChain chain,
-            final HttpCacheEntry entry) {
-        final String cacheKey = cacheKeyGenerator.generateKey(target, request, entry);
+            final String cacheKey,
+            final RevalidationCall call) {
         scheduleRevalidation(cacheKey, new Runnable() {
 
                         @Override
                         public void run() {
-                            try {
-                                try (ClassicHttpResponse httpResponse = cachingExec.revalidateCacheEntry(target, request, scope, chain, entry)) {
-                                    if (httpResponse.getCode() < HttpStatus.SC_SERVER_ERROR && !isStale(httpResponse)) {
-                                        jobSuccessful(cacheKey);
-                                    } else {
-                                        jobFailed(cacheKey);
-                                    }
+                            try (ClassicHttpResponse httpResponse = call.execute()) {
+                                if (httpResponse.getCode() < HttpStatus.SC_SERVER_ERROR && !isStale(httpResponse)) {
+                                    jobSuccessful(cacheKey);
+                                } else {
+                                    jobFailed(cacheKey);
                                 }
-                            } catch (final IOException ioe) {
+                            } catch (final IOException ex) {
                                 jobFailed(cacheKey);
-                                log.debug("Asynchronous revalidation failed due to I/O error", ioe);
-                            } catch (final HttpException pe) {
+                                log.debug("Asynchronous revalidation failed due to I/O error", ex);
+                            } catch (final HttpException ex) {
                                 jobFailed(cacheKey);
-                                log.error("HTTP protocol exception during asynchronous revalidation", pe);
-                            } catch (final RuntimeException re) {
+                                log.error("HTTP protocol exception during asynchronous revalidation", ex);
+                            } catch (final RuntimeException ex) {
                                 jobFailed(cacheKey);
-                                log.error("Unexpected runtime exception thrown during asynchronous revalidation" + re);
+                                log.error("Unexpected runtime exception thrown during asynchronous revalidation", ex);
                             }
 
                         }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpAsyncCache.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpAsyncCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpAsyncCache.java
index 8f1bd0e..dc4b2e6 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpAsyncCache.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpAsyncCache.java
@@ -44,6 +44,8 @@ import org.apache.hc.core5.util.ByteArrayBuffer;
 @Internal
 interface HttpAsyncCache {
 
+    String generateKey (HttpHost host, HttpRequest request, HttpCacheEntry cacheEntry);
+
     /**
      * Clear all matching {@link HttpCacheEntry}s.
      */

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpCache.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpCache.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpCache.java
index 8cb0e07..d2c6e66 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpCache.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HttpCache.java
@@ -40,6 +40,8 @@ import org.apache.hc.core5.util.ByteArrayBuffer;
  */
 interface HttpCache {
 
+    String generateKey (HttpHost host, HttpRequest request, HttpCacheEntry cacheEntry);
+
     /**
      * Clear all matching {@link HttpCacheEntry}s.
      */

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/schedule/ImmediateSchedulingStrategy.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/schedule/ImmediateSchedulingStrategy.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/schedule/ImmediateSchedulingStrategy.java
index bb498f6..362b1ad 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/schedule/ImmediateSchedulingStrategy.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/schedule/ImmediateSchedulingStrategy.java
@@ -39,7 +39,7 @@ import org.apache.hc.core5.util.TimeValue;
 @Contract(threading = ThreadingBehavior.STATELESS)
 public class ImmediateSchedulingStrategy implements SchedulingStrategy {
 
-    private final static ImmediateSchedulingStrategy INSTANCE = new ImmediateSchedulingStrategy();
+    public final static ImmediateSchedulingStrategy INSTANCE = new ImmediateSchedulingStrategy();
 
     @Override
     public TimeValue schedule(final int attemptNumber) {

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/AbstractProtocolTest.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/AbstractProtocolTest.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/AbstractProtocolTest.java
index 1d433fe..1adf72e 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/AbstractProtocolTest.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/AbstractProtocolTest.java
@@ -57,7 +57,7 @@ public abstract class AbstractProtocolTest {
     protected HttpEntity body;
     protected HttpClientContext context;
     protected ExecChain mockExecChain;
-    protected ExecRuntime mockEndpoint;
+    protected ExecRuntime mockExecRuntime;
     protected HttpCache mockCache;
     protected ClassicHttpRequest request;
     protected ClassicHttpResponse originResponse;
@@ -101,18 +101,18 @@ public abstract class AbstractProtocolTest {
 
         cache = new BasicHttpCache(config);
         mockExecChain = EasyMock.createNiceMock(ExecChain.class);
-        mockEndpoint = EasyMock.createNiceMock(ExecRuntime.class);
+        mockExecRuntime = EasyMock.createNiceMock(ExecRuntime.class);
         mockCache = EasyMock.createNiceMock(HttpCache.class);
         impl = createCachingExecChain(cache, config);
     }
 
     public ClassicHttpResponse execute(final ClassicHttpRequest request) throws IOException, HttpException {
         return impl.execute(ClassicRequestCopier.INSTANCE.copy(request), new ExecChain.Scope(
-                "test", route, request, mockEndpoint, context), mockExecChain);
+                "test", route, request, mockExecRuntime, context), mockExecChain);
     }
 
     protected ExecChainHandler createCachingExecChain(final HttpCache cache, final CacheConfig config) {
-        return new CachingExec(cache, config);
+        return new CachingExec(cache, null, config);
     }
 
     protected boolean supportsRangeAndContentRangeHeaders(final ExecChainHandler impl) {
@@ -148,7 +148,7 @@ public abstract class AbstractProtocolTest {
         mockExecChain = EasyMock.createNiceMock(ExecChain.class);
         mockCache = EasyMock.createNiceMock(HttpCache.class);
 
-        impl = new CachingExec(mockCache, config);
+        impl = new CachingExec(mockCache, null, config);
 
         EasyMock.expect(mockCache.getCacheEntry(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class)))
             .andReturn(null).anyTimes();
@@ -174,7 +174,7 @@ public abstract class AbstractProtocolTest {
                 .setMaxObjectSize(MAX_BYTES)
                 .setSharedCache(false)
                 .build();
-        impl = new CachingExec(cache, config);
+        impl = new CachingExec(cache, null, config);
     }
 
     public AbstractProtocolTest() {

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExec.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExec.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExec.java
index 446512a..95969a3 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExec.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExec.java
@@ -65,7 +65,6 @@ import org.easymock.EasyMock;
 import org.easymock.IExpectationSetters;
 import org.junit.Assert;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 
 @SuppressWarnings("boxing") // test code
@@ -107,9 +106,10 @@ public class TestCachingExec extends TestCachingExecChain {
             final CachedHttpResponseGenerator mockResponseGenerator,
             final CacheableRequestPolicy mockRequestPolicy,
             final CachedResponseSuitabilityChecker mockSuitabilityChecker,
-            final ConditionalRequestBuilder<ClassicHttpRequest> mockConditionalRequestBuilder,
             final ResponseProtocolCompliance mockResponseProtocolCompliance,
             final RequestProtocolCompliance mockRequestProtocolCompliance,
+            final DefaultCacheRevalidator mockCacheRevalidator,
+            final ConditionalRequestBuilder<ClassicHttpRequest> mockConditionalRequestBuilder,
             final CacheConfig config) {
         return impl = new CachingExec(
                 mockCache,
@@ -118,15 +118,16 @@ public class TestCachingExec extends TestCachingExecChain {
                 mockResponseGenerator,
                 mockRequestPolicy,
                 mockSuitabilityChecker,
-                mockConditionalRequestBuilder,
                 mockResponseProtocolCompliance,
                 mockRequestProtocolCompliance,
+                mockCacheRevalidator,
+                mockConditionalRequestBuilder,
                 config);
     }
 
     @Override
     public CachingExec createCachingExecChain(final HttpCache cache, final CacheConfig config) {
-        return impl = new CachingExec(cache, config);
+        return impl = new CachingExec(cache, null, config);
     }
 
     @Override
@@ -210,7 +211,7 @@ public class TestCachingExec extends TestCachingExecChain {
         Assert.assertEquals(1, impl.getCacheUpdates());
     }
 
-    @Test @Ignore
+    @Test
     public void testUnsuitableValidatableCacheEntryCausesRevalidation() throws Exception {
         mockImplMethods(REVALIDATE_CACHE_ENTRY);
         requestPolicyAllowsCaching(true);
@@ -447,9 +448,10 @@ public class TestCachingExec extends TestCachingExecChain {
                 mockResponseGenerator,
                 mockRequestPolicy,
                 mockSuitabilityChecker,
-                mockConditionalRequestBuilder,
                 mockResponseProtocolCompliance,
                 mockRequestProtocolCompliance,
+                mockCacheRevalidator,
+                mockConditionalRequestBuilder,
                 config).addMockedMethods(methods).createNiceMock();
     }
 

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java
index 3237533..b84d91e 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java
@@ -104,10 +104,11 @@ public abstract class TestCachingExecChain {
     protected CachedHttpResponseGenerator mockResponseGenerator;
     private HttpClientResponseHandler<Object> mockHandler;
     private ClassicHttpRequest mockUriRequest;
-    protected ConditionalRequestBuilder<ClassicHttpRequest> mockConditionalRequestBuilder;
     private HttpRequest mockConditionalRequest;
     protected ResponseProtocolCompliance mockResponseProtocolCompliance;
     protected RequestProtocolCompliance mockRequestProtocolCompliance;
+    protected DefaultCacheRevalidator mockCacheRevalidator;
+    protected ConditionalRequestBuilder<ClassicHttpRequest> mockConditionalRequestBuilder;
     protected CacheConfig config;
 
     protected HttpRoute route;
@@ -130,10 +131,11 @@ public abstract class TestCachingExecChain {
         mockUriRequest = createNiceMock(ClassicHttpRequest.class);
         mockCacheEntry = createNiceMock(HttpCacheEntry.class);
         mockResponseGenerator = createNiceMock(CachedHttpResponseGenerator.class);
-        mockConditionalRequestBuilder = createNiceMock(ConditionalRequestBuilder.class);
         mockConditionalRequest = createNiceMock(HttpRequest.class);
         mockResponseProtocolCompliance = createNiceMock(ResponseProtocolCompliance.class);
         mockRequestProtocolCompliance = createNiceMock(RequestProtocolCompliance.class);
+        mockCacheRevalidator = createNiceMock(DefaultCacheRevalidator.class);
+        mockConditionalRequestBuilder = createNiceMock(ConditionalRequestBuilder.class);
         mockStorage = createNiceMock(HttpCacheStorage.class);
         config = CacheConfig.DEFAULT;
 
@@ -143,9 +145,9 @@ public abstract class TestCachingExecChain {
         context = HttpCacheContext.create();
         entry = HttpTestUtils.makeCacheEntry();
         impl = createCachingExecChain(mockCache, mockValidityPolicy,
-            mockResponsePolicy, mockResponseGenerator, mockRequestPolicy, mockSuitabilityChecker,
-            mockConditionalRequestBuilder, mockResponseProtocolCompliance,
-            mockRequestProtocolCompliance, config);
+                mockResponsePolicy, mockResponseGenerator, mockRequestPolicy, mockSuitabilityChecker,
+                mockResponseProtocolCompliance,mockRequestProtocolCompliance,
+                mockCacheRevalidator, mockConditionalRequestBuilder, config);
     }
 
     public abstract CachingExec createCachingExecChain(
@@ -153,8 +155,9 @@ public abstract class TestCachingExecChain {
             ResponseCachingPolicy responseCachingPolicy, CachedHttpResponseGenerator responseGenerator,
             CacheableRequestPolicy cacheableRequestPolicy,
             CachedResponseSuitabilityChecker suitabilityChecker,
-            ConditionalRequestBuilder<ClassicHttpRequest> conditionalRequestBuilder,
             ResponseProtocolCompliance responseCompliance, RequestProtocolCompliance requestCompliance,
+            DefaultCacheRevalidator cacheRevalidator,
+            ConditionalRequestBuilder<ClassicHttpRequest> conditionalRequestBuilder,
             CacheConfig config);
 
     public abstract CachingExec createCachingExecChain(HttpCache cache, CacheConfig config);
@@ -1242,11 +1245,11 @@ public abstract class TestCachingExecChain {
         mockCache = EasyMock.createStrictMock(HttpCache.class);
         impl = createCachingExecChain(mockCache, mockValidityPolicy,
                 mockResponsePolicy, mockResponseGenerator, mockRequestPolicy, mockSuitabilityChecker,
-                mockConditionalRequestBuilder, mockResponseProtocolCompliance,
-                mockRequestProtocolCompliance, config);
+                mockResponseProtocolCompliance, mockRequestProtocolCompliance,
+                mockCacheRevalidator, mockConditionalRequestBuilder, config);
 
         final HttpHost host = new HttpHost("foo.example.com");
-        final HttpRequest request = new HttpGet("http://foo.example.com/bar");
+        final ClassicHttpRequest request = new HttpGet("http://foo.example.com/bar");
 
         final Date now = new Date();
         final Date requestSent = new Date(now.getTime() - 3 * 1000L);
@@ -1260,8 +1263,8 @@ public abstract class TestCachingExecChain {
         originResponse.setHeader("ETag", "\"etag\"");
 
         replayMocks();
-
-        impl.cacheAndReturnResponse(host, request, originResponse, requestSent, responseReceived);
+        final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, mockEndpoint, context);
+        impl.cacheAndReturnResponse(host, request, originResponse, scope, requestSent, responseReceived);
 
         verifyMocks();
     }
@@ -1269,7 +1272,7 @@ public abstract class TestCachingExecChain {
     @Test
     public void testSmallEnoughResponsesAreCached() throws Exception {
         final HttpHost host = new HttpHost("foo.example.com");
-        final HttpRequest request = new HttpGet("http://foo.example.com/bar");
+        final ClassicHttpRequest request = new HttpGet("http://foo.example.com/bar");
 
         final Date now = new Date();
         final Date requestSent = new Date(now.getTime() - 3 * 1000L);
@@ -1297,7 +1300,8 @@ public abstract class TestCachingExecChain {
                 same(httpCacheEntry))).andReturn(response).once();
         replayMocks();
 
-        impl.cacheAndReturnResponse(host, request, originResponse, requestSent, responseReceived);
+        final ExecChain.Scope scope = new ExecChain.Scope("test", route, request, mockEndpoint, context);
+        impl.cacheAndReturnResponse(host, request, originResponse, scope, requestSent, responseReceived);
 
         verifyMocks();
     }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingHttpClientBuilder.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingHttpClientBuilder.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingHttpClientBuilder.java
deleted file mode 100644
index ade3973..0000000
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingHttpClientBuilder.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation.  For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-package org.apache.hc.client5.http.impl.cache;
-
-import org.junit.Test;
-
-public class TestCachingHttpClientBuilder {
-
-    @Test
-    public void testAsynchronousWorkersMax0() throws Exception {
-        final CacheConfig cacheConfig = CacheConfig.custom()
-                .setAsynchronousWorkersMax(0)
-                .build();
-        // Asynchronous validation should be disabled but we should not get an
-        // Exception
-        CachingHttpClientBuilder.create().setCacheConfig(cacheConfig).build();
-    }
-
-    @Test
-    public void testNullCacheConfig() throws Exception {
-        CachingHttpClientBuilder.create().setCacheConfig(null).build();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestHttpCacheJiraNumber1147.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestHttpCacheJiraNumber1147.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestHttpCacheJiraNumber1147.java
index f665654..a07b09c 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestHttpCacheJiraNumber1147.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestHttpCacheJiraNumber1147.java
@@ -142,7 +142,7 @@ public class TestHttpCacheJiraNumber1147 {
     }
 
     protected ExecChainHandler createCachingExecChain(final BasicHttpCache cache, final CacheConfig config) {
-        return new CachingExec(cache, config);
+        return new CachingExec(cache, null, config);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/16147b18/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolDeviations.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolDeviations.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolDeviations.java
index 5f56ff1..e42088a 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolDeviations.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestProtocolDeviations.java
@@ -127,7 +127,7 @@ public class TestProtocolDeviations {
     }
 
     protected ExecChainHandler createCachingExecChain(final HttpCache cache, final CacheConfig config) {
-        return new CachingExec(cache, config);
+        return new CachingExec(cache, null, config);
     }
 
     private ClassicHttpResponse make200Response() {