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/01 16:01:43 UTC

[1/3] httpcomponents-client git commit: HttCache and HttpAsyncCache implementation to treat ResourceIOExceptions as non-fatal and log ResourceIOExceptions as warnings

Repository: httpcomponents-client
Updated Branches:
  refs/heads/master 13acc440e -> 194e4f528


HttCache and HttpAsyncCache implementation to treat ResourceIOExceptions as non-fatal and log ResourceIOExceptions as warnings


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

Branch: refs/heads/master
Commit: 77703a7eef7e1131cbb1d495366adfa2cc5c0233
Parents: 13acc44
Author: Oleg Kalnichevski <ol...@apache.org>
Authored: Sat Dec 30 00:08:47 2017 +0100
Committer: Oleg Kalnichevski <ol...@apache.org>
Committed: Sat Dec 30 12:31:46 2017 +0100

----------------------------------------------------------------------
 .../http/impl/cache/AsyncCachingExec.java       |   6 +-
 .../http/impl/cache/BasicHttpAsyncCache.java    | 312 ++++++++++++++++---
 .../client5/http/impl/cache/BasicHttpCache.java | 278 ++++++++++++-----
 .../hc/client5/http/impl/cache/CachingExec.java |  73 +----
 .../http/impl/cache/HeapResourceFactory.java    |   2 +-
 .../client5/http/impl/cache/HttpAsyncCache.java |   9 +-
 .../hc/client5/http/impl/cache/HttpCache.java   |  28 +-
 .../hc/client5/http/impl/cache/Variant.java     |   6 +
 .../http/impl/cache/TestBasicHttpCache.java     |  20 +-
 9 files changed, 504 insertions(+), 230 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/77703a7e/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 c429868..1332f74 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
@@ -826,8 +826,7 @@ public class AsyncCachingExec extends CachingExecBase implements AsyncExecChainH
                         callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
                         return;
                     }
-                    final HttpCacheEntry matchedEntry = matchingVariant.getEntry();
-                    if (revalidationResponseIsTooOld(backendResponse, matchedEntry)) {
+                    if (revalidationResponseIsTooOld(backendResponse, matchingVariant.getEntry())) {
                         final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(request);
                         scope.clientContext.setAttribute(HttpCoreContext.HTTP_REQUEST, unconditional);
                         callBackend(target, unconditional, entityProducer, scope, chain, asyncExecCallback);
@@ -837,11 +836,10 @@ public class AsyncCachingExec extends CachingExecBase implements AsyncExecChainH
                     future.setDependency(responseCache.updateVariantCacheEntry(
                             target,
                             conditionalRequest,
-                            matchedEntry,
                             backendResponse,
+                            matchingVariant,
                             requestDate,
                             responseDateRef.get(),
-                            matchingVariant.getCacheKey(),
                             new FutureCallback<HttpCacheEntry>() {
 
                                 @Override

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/77703a7e/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 853f72f..55337dc 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
@@ -29,6 +29,7 @@ package org.apache.hc.client5.http.impl.cache;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.hc.client5.http.StandardMethods;
 import org.apache.hc.client5.http.cache.HeaderConstants;
@@ -36,6 +37,7 @@ import org.apache.hc.client5.http.cache.HttpAsyncCacheInvalidator;
 import org.apache.hc.client5.http.cache.HttpAsyncCacheStorage;
 import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
+import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
 import org.apache.hc.client5.http.cache.ResourceFactory;
 import org.apache.hc.client5.http.cache.ResourceIOException;
 import org.apache.hc.client5.http.impl.Operations;
@@ -45,10 +47,16 @@ import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.message.RequestLine;
+import org.apache.hc.core5.http.message.StatusLine;
 import org.apache.hc.core5.util.ByteArrayBuffer;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 
 class BasicHttpAsyncCache implements HttpAsyncCache {
 
+    private final Logger log = LogManager.getLogger(getClass());
+
     private final CacheUpdateHandler cacheUpdateHandler;
     private final CacheKeyGenerator cacheKeyGenerator;
     private final HttpAsyncCacheInvalidator cacheInvalidator;
@@ -79,9 +87,36 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
     @Override
     public Cancellable flushCacheEntriesFor(
             final HttpHost host, final HttpRequest request, final FutureCallback<Boolean> callback) {
+        if (log.isDebugEnabled()) {
+            log.debug("Flush cache entries: " + host + "; " + new RequestLine(request));
+        }
         if (!StandardMethods.isSafe(request.getMethod())) {
-            final String uri = cacheKeyGenerator.generateKey(host, request);
-            return storage.removeEntry(uri, callback);
+            final String cacheKey = cacheKeyGenerator.generateKey(host, request);
+            return storage.removeEntry(cacheKey, new FutureCallback<Boolean>() {
+
+                @Override
+                public void completed(final Boolean result) {
+                    callback.completed(result);
+                }
+
+                @Override
+                public void failed(final Exception ex) {
+                    if (ex instanceof ResourceIOException) {
+                        if (log.isWarnEnabled()) {
+                            log.warn("I/O error removing cache entry with key " + cacheKey);
+                        }
+                        callback.completed(Boolean.TRUE);
+                    } else {
+                        callback.failed(ex);
+                    }
+                }
+
+                @Override
+                public void cancelled() {
+                    callback.cancelled();
+                }
+
+            });
         } else {
             callback.completed(Boolean.TRUE);
             return Operations.nonCancellable();
@@ -91,6 +126,9 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
     @Override
     public Cancellable flushInvalidatedCacheEntriesFor(
             final HttpHost host, final HttpRequest request, final HttpResponse response, final FutureCallback<Boolean> callback) {
+        if (log.isDebugEnabled()) {
+            log.debug("Flush cache entries: " + host + "; " + new RequestLine(request) + " " + new StatusLine(response));
+        }
         if (!StandardMethods.isSafe(request.getMethod())) {
             return cacheInvalidator.flushInvalidatedCacheEntries(host, request, response, cacheKeyGenerator, storage, callback);
         } else {
@@ -102,52 +140,117 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
     @Override
     public Cancellable flushInvalidatedCacheEntriesFor(
             final HttpHost host, final HttpRequest request, final FutureCallback<Boolean> callback) {
+        if (log.isDebugEnabled()) {
+            log.debug("Flush invalidated cache entries: " + host + "; " + new RequestLine(request));
+        }
         return cacheInvalidator.flushInvalidatedCacheEntries(host, request, cacheKeyGenerator, storage, callback);
     }
 
     Cancellable storeInCache(
-            final HttpHost target, final HttpRequest request, final HttpCacheEntry entry, final FutureCallback<Boolean> callback) {
+            final String cacheKey,
+            final HttpHost host,
+            final HttpRequest request,
+            final HttpCacheEntry entry,
+            final FutureCallback<Boolean> callback) {
         if (entry.hasVariants()) {
-            return storeVariantEntry(target, request, entry, callback);
+            return storeVariantEntry(cacheKey, host, request, entry, callback);
         } else {
-            return storeNonVariantEntry(target, request, entry, callback);
+            return storeEntry(cacheKey, entry, callback);
         }
     }
 
-    Cancellable storeNonVariantEntry(
-            final HttpHost target,
-            final HttpRequest req,
+    Cancellable storeEntry(
+            final String cacheKey,
             final HttpCacheEntry entry,
             final FutureCallback<Boolean> callback) {
-        final String uri = cacheKeyGenerator.generateKey(target, req);
-        return storage.putEntry(uri, entry, callback);
+        return storage.putEntry(cacheKey, entry, new FutureCallback<Boolean>() {
+
+            @Override
+            public void completed(final Boolean result) {
+                callback.completed(result);
+            }
+
+            @Override
+            public void failed(final Exception ex) {
+                if (ex instanceof ResourceIOException) {
+                    if (log.isWarnEnabled()) {
+                        log.warn("I/O error storing cache entry with key " + cacheKey);
+                    }
+                    callback.completed(Boolean.TRUE);
+                } else {
+                    callback.failed(ex);
+                }
+            }
+
+            @Override
+            public void cancelled() {
+                callback.cancelled();
+            }
+
+        });
     }
 
     Cancellable storeVariantEntry(
-            final HttpHost target,
+            final String cacheKey,
+            final HttpHost host,
             final HttpRequest req,
             final HttpCacheEntry entry,
             final FutureCallback<Boolean> callback) {
-        final String parentCacheKey = cacheKeyGenerator.generateKey(target, req);
         final String variantKey = cacheKeyGenerator.generateVariantKey(req, entry);
-        final String variantURI = cacheKeyGenerator.generateVariantURI(target, req, entry);
-        return storage.putEntry(variantURI, entry, new FutureCallback<Boolean>() {
+        final String variantCacheKey = cacheKeyGenerator.generateVariantURI(host, req, entry);
+        return storage.putEntry(variantCacheKey, entry, new FutureCallback<Boolean>() {
 
             @Override
             public void completed(final Boolean result) {
-                storage.updateEntry(parentCacheKey, new HttpCacheCASOperation() {
+                storage.updateEntry(cacheKey,
+                        new HttpCacheCASOperation() {
+
+                            @Override
+                            public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
+                                return cacheUpdateHandler.updateParentCacheEntry(req.getRequestUri(), existing, entry, variantKey, variantCacheKey);
+                            }
+
+                        },
+                        new FutureCallback<Boolean>() {
+
+                            @Override
+                            public void completed(final Boolean result) {
+                                callback.completed(result);
+                            }
+
+                            @Override
+                            public void failed(final Exception ex) {
+                                if (ex instanceof HttpCacheUpdateException) {
+                                    if (log.isWarnEnabled()) {
+                                        log.warn("Cannot update cache entry with key " + cacheKey);
+                                    }
+                                } else if (ex instanceof ResourceIOException) {
+                                    if (log.isWarnEnabled()) {
+                                        log.warn("I/O error updating cache entry with key " + cacheKey);
+                                    }
+                                } else {
+                                    callback.failed(ex);
+                                }
+                            }
 
-                    @Override
-                    public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
-                        return cacheUpdateHandler.updateParentCacheEntry(req.getRequestUri(), existing, entry, variantKey, variantURI);
-                    }
+                            @Override
+                            public void cancelled() {
+                                callback.cancelled();
+                            }
 
-                }, callback);
+                        });
             }
 
             @Override
             public void failed(final Exception ex) {
-                callback.failed(ex);
+                if (ex instanceof ResourceIOException) {
+                    if (log.isWarnEnabled()) {
+                        log.warn("I/O error updating cache entry with key " + variantCacheKey);
+                    }
+                    callback.completed(Boolean.TRUE);
+                } else {
+                    callback.failed(ex);
+                }
             }
 
             @Override
@@ -160,30 +263,66 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
 
     @Override
     public Cancellable reuseVariantEntryFor(
-            final HttpHost target, final HttpRequest req, final Variant variant, final FutureCallback<Boolean> callback) {
-        final String parentCacheKey = cacheKeyGenerator.generateKey(target, req);
+            final HttpHost host, final HttpRequest request, final Variant variant, final FutureCallback<Boolean> callback) {
+        if (log.isDebugEnabled()) {
+            log.debug("Re-use variant entry: " + host + "; " + new RequestLine(request) + " / " + variant);
+        }
+        final String cacheKey = cacheKeyGenerator.generateKey(host, request);
         final HttpCacheEntry entry = variant.getEntry();
-        final String variantKey = cacheKeyGenerator.generateVariantKey(req, entry);
+        final String variantKey = cacheKeyGenerator.generateVariantKey(request, entry);
         final String variantCacheKey = variant.getCacheKey();
-        return storage.updateEntry(parentCacheKey, new HttpCacheCASOperation() {
+        return storage.updateEntry(cacheKey,
+                new HttpCacheCASOperation() {
 
-            @Override
-            public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
-                return cacheUpdateHandler.updateParentCacheEntry(req.getRequestUri(), existing, entry, variantKey, variantCacheKey);
-            }
+                    @Override
+                    public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
+                        return cacheUpdateHandler.updateParentCacheEntry(request.getRequestUri(), existing, entry, variantKey, variantCacheKey);
+                    }
 
-        }, callback);
+                },
+                new FutureCallback<Boolean>() {
+
+                    @Override
+                    public void completed(final Boolean result) {
+                        callback.completed(result);
+                    }
+
+                    @Override
+                    public void failed(final Exception ex) {
+                        if (ex instanceof HttpCacheUpdateException) {
+                            if (log.isWarnEnabled()) {
+                                log.warn("Cannot update cache entry with key " + cacheKey);
+                            }
+                        } else if (ex instanceof ResourceIOException) {
+                            if (log.isWarnEnabled()) {
+                                log.warn("I/O error updating cache entry with key " + cacheKey);
+                            }
+                        } else {
+                            callback.failed(ex);
+                        }
+                    }
+
+                    @Override
+                    public void cancelled() {
+                        callback.cancelled();
+                    }
+
+                });
     }
 
     @Override
     public Cancellable updateCacheEntry(
-            final HttpHost target,
+            final HttpHost host,
             final HttpRequest request,
             final HttpCacheEntry stale,
             final HttpResponse originResponse,
             final Date requestSent,
             final Date responseReceived,
             final FutureCallback<HttpCacheEntry> callback) {
+        if (log.isDebugEnabled()) {
+            log.debug("Update cache entry: " + host + "; " + new RequestLine(request));
+        }
+        final String cacheKey = cacheKeyGenerator.generateKey(host, request);
         try {
             final HttpCacheEntry updatedEntry = cacheUpdateHandler.updateCacheEntry(
                     request.getRequestUri(),
@@ -191,7 +330,7 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
                     requestSent,
                     responseReceived,
                     originResponse);
-            return storeInCache(target, request, updatedEntry, new FutureCallback<Boolean>() {
+            return storeInCache(cacheKey, host, request, updatedEntry, new FutureCallback<Boolean>() {
 
                 @Override
                 public void completed(final Boolean result) {
@@ -210,29 +349,36 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
 
             });
         } catch (final ResourceIOException ex) {
-            callback.failed(ex);
+            if (log.isWarnEnabled()) {
+                log.warn("I/O error updating cache entry with key " + cacheKey);
+            }
+            callback.completed(stale);
             return Operations.nonCancellable();
         }
     }
 
     @Override
     public Cancellable updateVariantCacheEntry(
-            final HttpHost target,
+            final HttpHost host,
             final HttpRequest request,
-            final HttpCacheEntry stale,
             final HttpResponse originResponse,
+            final Variant variant,
             final Date requestSent,
             final Date responseReceived,
-            final String cacheKey,
             final FutureCallback<HttpCacheEntry> callback) {
+        if (log.isDebugEnabled()) {
+            log.debug("Update variant cache entry: " + host + "; " + new RequestLine(request) + " / " + variant);
+        }
+        final HttpCacheEntry entry = variant.getEntry();
+        final String cacheKey = variant.getCacheKey();
         try {
             final HttpCacheEntry updatedEntry = cacheUpdateHandler.updateCacheEntry(
                     request.getRequestUri(),
-                    stale,
+                    entry,
                     requestSent,
                     responseReceived,
                     originResponse);
-            return storage.putEntry(cacheKey, updatedEntry, new FutureCallback<Boolean>() {
+            return storeEntry(cacheKey, updatedEntry, new FutureCallback<Boolean>() {
 
                 @Override
                 public void completed(final Boolean result) {
@@ -251,7 +397,10 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
 
             });
         } catch (final ResourceIOException ex) {
-            callback.failed(ex);
+            if (log.isWarnEnabled()) {
+                log.warn("I/O error updating cache entry with key " + cacheKey);
+            }
+            callback.completed(entry);
             return Operations.nonCancellable();
         }
     }
@@ -265,9 +414,13 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
             final Date requestSent,
             final Date responseReceived,
             final FutureCallback<HttpCacheEntry> callback) {
+        if (log.isDebugEnabled()) {
+            log.debug("Create cache entry: " + host + "; " + new RequestLine(request));
+        }
+        final String cacheKey = cacheKeyGenerator.generateKey(host, request);
         try {
             final HttpCacheEntry entry = cacheUpdateHandler.createtCacheEntry(request, originResponse, content, requestSent, responseReceived);
-            return storeInCache(host, request, entry, new FutureCallback<Boolean>() {
+            return storeInCache(cacheKey, host, request, entry, new FutureCallback<Boolean>() {
 
                 @Override
                 public void completed(final Boolean result) {
@@ -286,13 +439,24 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
 
             });
         } catch (final ResourceIOException ex) {
-            callback.failed(ex);
+            if (log.isWarnEnabled()) {
+                log.warn("I/O error creating cache entry with key " + cacheKey);
+            }
+            callback.completed(new HttpCacheEntry(
+                    requestSent,
+                    responseReceived,
+                    originResponse.getCode(),
+                    originResponse.getAllHeaders(),
+                    content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null));
             return Operations.nonCancellable();
         }
     }
 
     @Override
     public Cancellable getCacheEntry(final HttpHost host, final HttpRequest request, final FutureCallback<HttpCacheEntry> callback) {
+        if (log.isDebugEnabled()) {
+            log.debug("Get cache entry: " + host + "; " + new RequestLine(request));
+        }
         final ComplexCancellable complexCancellable = new ComplexCancellable();
         final String cacheKey = cacheKeyGenerator.generateKey(host, request);
         complexCancellable.setDependency(storage.getEntry(cacheKey, new FutureCallback<HttpCacheEntry>() {
@@ -301,9 +465,36 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
             public void completed(final HttpCacheEntry root) {
                 if (root != null) {
                     if (root.hasVariants()) {
-                        final String variantCacheKey = root.getVariantMap().get(cacheKeyGenerator.generateVariantKey(request, root));
+                        final String variantKey = cacheKeyGenerator.generateVariantKey(request, root);
+                        final String variantCacheKey = root.getVariantMap().get(variantKey);
                         if (variantCacheKey != null) {
-                            complexCancellable.setDependency(storage.getEntry(variantCacheKey, callback));
+                            complexCancellable.setDependency(storage.getEntry(
+                                    variantCacheKey,
+                                    new FutureCallback<HttpCacheEntry>() {
+
+                                        @Override
+                                        public void completed(final HttpCacheEntry result) {
+                                            callback.completed(result);
+                                        }
+
+                                        @Override
+                                        public void failed(final Exception ex) {
+                                            if (ex instanceof ResourceIOException) {
+                                                if (log.isWarnEnabled()) {
+                                                    log.warn("I/O error retrieving cache entry with key " + variantCacheKey);
+                                                }
+                                                callback.completed(null);
+                                            } else {
+                                                callback.failed(ex);
+                                            }
+                                        }
+
+                                        @Override
+                                        public void cancelled() {
+                                            callback.cancelled();
+                                        }
+
+                                    }));
                             return;
                         }
                     }
@@ -313,7 +504,14 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
 
             @Override
             public void failed(final Exception ex) {
-                callback.failed(ex);
+                if (ex instanceof ResourceIOException) {
+                    if (log.isWarnEnabled()) {
+                        log.warn("I/O error retrieving cache entry with key " + cacheKey);
+                    }
+                    callback.completed(null);
+                } else {
+                    callback.failed(ex);
+                }
             }
 
             @Override
@@ -328,16 +526,20 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
     @Override
     public Cancellable getVariantCacheEntriesWithEtags(
             final HttpHost host, final HttpRequest request, final FutureCallback<Map<String, Variant>> callback) {
+        if (log.isDebugEnabled()) {
+            log.debug("Get variant cache entries: " + host + "; " + new RequestLine(request));
+        }
         final ComplexCancellable complexCancellable = new ComplexCancellable();
         final String cacheKey = cacheKeyGenerator.generateKey(host, request);
+        final Map<String, Variant> variants = new HashMap<>();
         complexCancellable.setDependency(storage.getEntry(cacheKey, new FutureCallback<HttpCacheEntry>() {
 
             @Override
             public void completed(final HttpCacheEntry rootEntry) {
-                final Map<String, Variant> variants = new HashMap<>();
                 if (rootEntry != null && rootEntry.hasVariants()) {
+                    final Set<String> variantCacheKeys = rootEntry.getVariantMap().keySet();
                     complexCancellable.setDependency(storage.getEntries(
-                            rootEntry.getVariantMap().keySet(),
+                            variantCacheKeys,
                             new FutureCallback<Map<String, HttpCacheEntry>>() {
 
                                 @Override
@@ -355,7 +557,14 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
 
                                 @Override
                                 public void failed(final Exception ex) {
-                                    callback.failed(ex);
+                                    if (ex instanceof ResourceIOException) {
+                                        if (log.isWarnEnabled()) {
+                                            log.warn("I/O error retrieving cache entry with keys " + variantCacheKeys);
+                                        }
+                                        callback.completed(variants);
+                                    } else {
+                                        callback.failed(ex);
+                                    }
                                 }
 
                                 @Override
@@ -371,7 +580,14 @@ class BasicHttpAsyncCache implements HttpAsyncCache {
 
             @Override
             public void failed(final Exception ex) {
-                callback.failed(ex);
+                if (ex instanceof ResourceIOException) {
+                    if (log.isWarnEnabled()) {
+                        log.warn("I/O error retrieving cache entry with key " + cacheKey);
+                    }
+                    callback.completed(variants);
+                } else {
+                    callback.failed(ex);
+                }
             }
 
             @Override

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/77703a7e/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 fc4acef..6c8463a 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
@@ -43,19 +43,21 @@ import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.message.RequestLine;
+import org.apache.hc.core5.http.message.StatusLine;
 import org.apache.hc.core5.util.ByteArrayBuffer;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 class BasicHttpCache implements HttpCache {
 
+    private final Logger log = LogManager.getLogger(getClass());
+
     private final CacheUpdateHandler cacheUpdateHandler;
     private final CacheKeyGenerator cacheKeyGenerator;
     private final HttpCacheInvalidator cacheInvalidator;
     private final HttpCacheStorage storage;
 
-    private final Logger log = LogManager.getLogger(getClass());
-
     public BasicHttpCache(
             final ResourceFactory resourceFactory,
             final HttpCacheStorage storage,
@@ -87,110 +89,178 @@ class BasicHttpCache implements HttpCache {
     }
 
     @Override
-    public void flushCacheEntriesFor(final HttpHost host, final HttpRequest request) throws ResourceIOException {
+    public void flushCacheEntriesFor(final HttpHost host, final HttpRequest request) {
+        if (log.isDebugEnabled()) {
+            log.debug("Flush cache entries: " + host + "; " + new RequestLine(request));
+        }
         if (!StandardMethods.isSafe(request.getMethod())) {
-            final String uri = cacheKeyGenerator.generateKey(host, request);
-            storage.removeEntry(uri);
+            final String cacheKey = cacheKeyGenerator.generateKey(host, request);
+            try {
+                storage.removeEntry(cacheKey);
+            } catch (final ResourceIOException ex) {
+                if (log.isWarnEnabled()) {
+                    log.warn("I/O error removing cache entry with key " + cacheKey);
+                }
+            }
         }
     }
 
     @Override
     public void flushInvalidatedCacheEntriesFor(final HttpHost host, final HttpRequest request, final HttpResponse response) {
+        if (log.isDebugEnabled()) {
+            log.debug("Flush cache entries: " + host + "; " + new RequestLine(request) + " " + new StatusLine(response));
+        }
         if (!StandardMethods.isSafe(request.getMethod())) {
             cacheInvalidator.flushInvalidatedCacheEntries(host, request, response, cacheKeyGenerator, storage);
         }
     }
 
+    @Override
+    public void flushInvalidatedCacheEntriesFor(final HttpHost host, final HttpRequest request) {
+        if (log.isDebugEnabled()) {
+            log.debug("Flush invalidated cache entries: " + host + "; " + new RequestLine(request));
+        }
+        cacheInvalidator.flushInvalidatedCacheEntries(host, request, cacheKeyGenerator, storage);
+    }
+
     void storeInCache(
-            final HttpHost target, final HttpRequest request, final HttpCacheEntry entry) throws ResourceIOException {
+            final String cacheKey,
+            final HttpHost host,
+            final HttpRequest request,
+            final HttpCacheEntry entry) {
         if (entry.hasVariants()) {
-            storeVariantEntry(target, request, entry);
+            storeVariantEntry(cacheKey, host, request, entry);
         } else {
-            storeNonVariantEntry(target, request, entry);
+            storeEntry(cacheKey, entry);
         }
     }
 
-    void storeNonVariantEntry(
-            final HttpHost target, final HttpRequest req, final HttpCacheEntry entry) throws ResourceIOException {
-        final String uri = cacheKeyGenerator.generateKey(target, req);
-        storage.putEntry(uri, entry);
+    void storeEntry(final String cacheKey, final HttpCacheEntry entry) {
+        try {
+            storage.putEntry(cacheKey, entry);
+        } catch (final ResourceIOException ex) {
+            if (log.isWarnEnabled()) {
+                log.warn("I/O error storing cache entry with key " + cacheKey);
+            }
+        }
     }
 
     void storeVariantEntry(
-            final HttpHost target,
+            final String cacheKey,
+            final HttpHost host,
             final HttpRequest req,
-            final HttpCacheEntry entry) throws ResourceIOException {
-        final String parentCacheKey = cacheKeyGenerator.generateKey(target, req);
+            final HttpCacheEntry entry) {
         final String variantKey = cacheKeyGenerator.generateVariantKey(req, entry);
-        final String variantURI = cacheKeyGenerator.generateVariantURI(target, req, entry);
-        storage.putEntry(variantURI, entry);
-
+        final String variantCacheKey = cacheKeyGenerator.generateVariantURI(host, req, entry);
+        storeEntry(variantCacheKey, entry);
         try {
-            storage.updateEntry(parentCacheKey, new HttpCacheCASOperation() {
+            storage.updateEntry(cacheKey, new HttpCacheCASOperation() {
 
                 @Override
                 public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
-                    return cacheUpdateHandler.updateParentCacheEntry(req.getRequestUri(), existing, entry, variantKey, variantURI);
+                    return cacheUpdateHandler.updateParentCacheEntry(req.getRequestUri(), existing, entry, variantKey, variantCacheKey);
                 }
 
             });
-        } catch (final HttpCacheUpdateException e) {
-            log.warn("Could not processChallenge key [" + parentCacheKey + "]", e);
+        } catch (final HttpCacheUpdateException ex) {
+            if (log.isWarnEnabled()) {
+                log.warn("Cannot update cache entry with key " + cacheKey);
+            }
+        } catch (final ResourceIOException ex) {
+            if (log.isWarnEnabled()) {
+                log.warn("I/O error updating cache entry with key " + cacheKey);
+            }
         }
     }
 
     @Override
     public void reuseVariantEntryFor(
-            final HttpHost target, final HttpRequest req, final Variant variant) throws ResourceIOException {
-        final String parentCacheKey = cacheKeyGenerator.generateKey(target, req);
+            final HttpHost host, final HttpRequest request, final Variant variant) {
+        if (log.isDebugEnabled()) {
+            log.debug("Re-use variant entry: " + host + "; " + new RequestLine(request) + " / " + variant);
+        }
+        final String cacheKey = cacheKeyGenerator.generateKey(host, request);
         final HttpCacheEntry entry = variant.getEntry();
-        final String variantKey = cacheKeyGenerator.generateVariantKey(req, entry);
+        final String variantKey = cacheKeyGenerator.generateVariantKey(request, entry);
         final String variantCacheKey = variant.getCacheKey();
 
         try {
-            storage.updateEntry(parentCacheKey, new HttpCacheCASOperation() {
+            storage.updateEntry(cacheKey, new HttpCacheCASOperation() {
 
                 @Override
                 public HttpCacheEntry execute(final HttpCacheEntry existing) throws ResourceIOException {
-                    return cacheUpdateHandler.updateParentCacheEntry(req.getRequestUri(), existing, entry, variantKey, variantCacheKey);
+                    return cacheUpdateHandler.updateParentCacheEntry(request.getRequestUri(), existing, entry, variantKey, variantCacheKey);
                 }
 
             });
-        } catch (final HttpCacheUpdateException e) {
-            log.warn("Could not processChallenge key [" + parentCacheKey + "]", e);
+        } catch (final HttpCacheUpdateException ex) {
+            if (log.isWarnEnabled()) {
+                log.warn("Cannot update cache entry with key " + cacheKey);
+            }
+        } catch (final ResourceIOException ex) {
+            if (log.isWarnEnabled()) {
+                log.warn("I/O error updating cache entry with key " + cacheKey);
+            }
         }
     }
 
     @Override
     public HttpCacheEntry updateCacheEntry(
-            final HttpHost target,
+            final HttpHost host,
             final HttpRequest request,
             final HttpCacheEntry stale,
             final HttpResponse originResponse,
             final Date requestSent,
-            final Date responseReceived) throws ResourceIOException {
-        final HttpCacheEntry updatedEntry = cacheUpdateHandler.updateCacheEntry(
-                request.getRequestUri(),
-                stale,
-                requestSent,
-                responseReceived,
-                originResponse);
-        storeInCache(target, request, updatedEntry);
-        return updatedEntry;
+            final Date responseReceived) {
+        if (log.isDebugEnabled()) {
+            log.debug("Update cache entry: " + host + "; " + new RequestLine(request));
+        }
+        final String cacheKey = cacheKeyGenerator.generateKey(host, request);
+        try {
+            final HttpCacheEntry updatedEntry = cacheUpdateHandler.updateCacheEntry(
+                    request.getRequestUri(),
+                    stale,
+                    requestSent,
+                    responseReceived,
+                    originResponse);
+            storeInCache(cacheKey, host, request, updatedEntry);
+            return updatedEntry;
+        } catch (final ResourceIOException ex) {
+            if (log.isWarnEnabled()) {
+                log.warn("I/O error updating cache entry with key " + cacheKey);
+            }
+            return stale;
+        }
     }
 
     @Override
-    public HttpCacheEntry updateVariantCacheEntry(final HttpHost target, final HttpRequest request,
-            final HttpCacheEntry stale, final HttpResponse originResponse,
-            final Date requestSent, final Date responseReceived, final String cacheKey) throws ResourceIOException {
-        final HttpCacheEntry updatedEntry = cacheUpdateHandler.updateCacheEntry(
-                request.getRequestUri(),
-                stale,
-                requestSent,
-                responseReceived,
-                originResponse);
-        storage.putEntry(cacheKey, updatedEntry);
-        return updatedEntry;
+    public HttpCacheEntry updateVariantCacheEntry(
+            final HttpHost host,
+            final HttpRequest request,
+            final HttpResponse originResponse,
+            final Variant variant,
+            final Date requestSent,
+            final Date responseReceived) {
+        if (log.isDebugEnabled()) {
+            log.debug("Update variant cache entry: " + host + "; " + new RequestLine(request) + " / " + variant);
+        }
+        final HttpCacheEntry entry = variant.getEntry();
+        final String cacheKey = variant.getCacheKey();
+        try {
+            final HttpCacheEntry updatedEntry = cacheUpdateHandler.updateCacheEntry(
+                    request.getRequestUri(),
+                    entry,
+                    requestSent,
+                    responseReceived,
+                    originResponse);
+            storeEntry(cacheKey, updatedEntry);
+            return updatedEntry;
+        } catch (final ResourceIOException ex) {
+            if (log.isWarnEnabled()) {
+                log.warn("I/O error updating cache entry with key " + cacheKey);
+            }
+            return entry;
+        }
     }
 
     @Override
@@ -200,60 +270,100 @@ class BasicHttpCache implements HttpCache {
             final HttpResponse originResponse,
             final ByteArrayBuffer content,
             final Date requestSent,
-            final Date responseReceived) throws ResourceIOException {
-        final HttpCacheEntry entry = cacheUpdateHandler.createtCacheEntry(request, originResponse, content, requestSent, responseReceived);
-        storeInCache(host, request, entry);
-        return entry;
+            final Date responseReceived) {
+        if (log.isDebugEnabled()) {
+            log.debug("Create cache entry: " + host + "; " + new RequestLine(request));
+        }
+        final String cacheKey = cacheKeyGenerator.generateKey(host, request);
+        try {
+            final HttpCacheEntry entry = cacheUpdateHandler.createtCacheEntry(request, originResponse, content, requestSent, responseReceived);
+            storeInCache(cacheKey, host, request, entry);
+            return entry;
+        } catch (final ResourceIOException ex) {
+            if (log.isWarnEnabled()) {
+                log.warn("I/O error creating cache entry with key " + cacheKey);
+            }
+            return new HttpCacheEntry(
+                    requestSent,
+                    responseReceived,
+                    originResponse.getCode(),
+                    originResponse.getAllHeaders(),
+                    content != null ? HeapResourceFactory.INSTANCE.generate(null, content.array(), 0, content.length()) : null);
+        }
     }
 
     @Override
-    public HttpCacheEntry getCacheEntry(final HttpHost host, final HttpRequest request) throws ResourceIOException {
-        final HttpCacheEntry root = storage.getEntry(cacheKeyGenerator.generateKey(host, request));
+    public HttpCacheEntry getCacheEntry(final HttpHost host, final HttpRequest request) {
+        if (log.isDebugEnabled()) {
+            log.debug("Get cache entry: " + host + "; " + new RequestLine(request));
+        }
+        final String cacheKey = cacheKeyGenerator.generateKey(host, request);
+        final HttpCacheEntry root;
+        try {
+            root = storage.getEntry(cacheKey);
+        } catch (final ResourceIOException ex) {
+            if (log.isWarnEnabled()) {
+                log.warn("I/O error retrieving cache entry with key " + cacheKey);
+            }
+            return null;
+        }
         if (root == null) {
             return null;
         }
         if (!root.hasVariants()) {
             return root;
         }
-        final String variantCacheKey = root.getVariantMap().get(cacheKeyGenerator.generateVariantKey(request, root));
+        final String variantKey = cacheKeyGenerator.generateVariantKey(request, root);
+        final String variantCacheKey = root.getVariantMap().get(variantKey);
         if (variantCacheKey == null) {
             return null;
         }
-        return storage.getEntry(variantCacheKey);
-    }
-
-    @Override
-    public void flushInvalidatedCacheEntriesFor(final HttpHost host,
-            final HttpRequest request) throws ResourceIOException {
-        cacheInvalidator.flushInvalidatedCacheEntries(host, request, cacheKeyGenerator, storage);
+        try {
+            return storage.getEntry(variantCacheKey);
+        } catch (final ResourceIOException ex) {
+            if (log.isWarnEnabled()) {
+                log.warn("I/O error retrieving cache entry with key " + variantCacheKey);
+            }
+            return null;
+        }
     }
 
     @Override
-    public Map<String, Variant> getVariantCacheEntriesWithEtags(final HttpHost host, final HttpRequest request)
-            throws ResourceIOException {
+    public Map<String, Variant> getVariantCacheEntriesWithEtags(final HttpHost host, final HttpRequest request) {
+        if (log.isDebugEnabled()) {
+            log.debug("Get variant cache entries: " + host + "; " + new RequestLine(request));
+        }
         final Map<String,Variant> variants = new HashMap<>();
-        final HttpCacheEntry root = storage.getEntry(cacheKeyGenerator.generateKey(host, request));
-        if (root == null || !root.hasVariants()) {
+        final String cacheKey = cacheKeyGenerator.generateKey(host, request);
+        final HttpCacheEntry root;
+        try {
+            root = storage.getEntry(cacheKey);
+        } catch (final ResourceIOException ex) {
+            if (log.isWarnEnabled()) {
+                log.warn("I/O error retrieving cache entry with key " + cacheKey);
+            }
             return variants;
         }
-        for(final Map.Entry<String, String> variant : root.getVariantMap().entrySet()) {
-            final String variantCacheKey = variant.getValue();
-            addVariantWithEtag(variantCacheKey, variants);
+        if (root != null && root.hasVariants()) {
+            for(final Map.Entry<String, String> variant : root.getVariantMap().entrySet()) {
+                final String variantCacheKey = variant.getValue();
+                try {
+                    final HttpCacheEntry entry = storage.getEntry(variantCacheKey);
+                    if (entry != null) {
+                        final Header etagHeader = entry.getFirstHeader(HeaderConstants.ETAG);
+                        if (etagHeader != null) {
+                            variants.put(etagHeader.getValue(), new Variant(variantCacheKey, entry));
+                        }
+                    }
+                } catch (final ResourceIOException ex) {
+                    if (log.isWarnEnabled()) {
+                        log.warn("I/O error retrieving cache entry with key " + variantCacheKey);
+                    }
+                    return variants;
+                }
+            }
         }
         return variants;
     }
 
-    private void addVariantWithEtag(
-            final String variantCacheKey, final Map<String, Variant> variants) throws ResourceIOException {
-        final HttpCacheEntry entry = storage.getEntry(variantCacheKey);
-        if (entry == null) {
-            return;
-        }
-        final Header etagHeader = entry.getFirstHeader(HeaderConstants.ETAG);
-        if (etagHeader == null) {
-            return;
-        }
-        variants.put(etagHeader.getValue(), new Variant(variantCacheKey, entry));
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/77703a7e/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 92ed4e8..57de39c 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
@@ -175,11 +175,11 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
 
         if (!cacheableRequestPolicy.isServableFromCache(request)) {
             log.debug("Request is not servable from cache");
-            flushEntriesInvalidatedByRequest(target, request);
+            responseCache.flushInvalidatedCacheEntriesFor(target, request);
             return callBackend(target, request, scope, chain);
         }
 
-        final HttpCacheEntry entry = satisfyFromCache(target, request);
+        final HttpCacheEntry entry = responseCache.getCacheEntry(target, request);
         if (entry == null) {
             log.debug("Cache miss");
             return handleCacheMiss(target, request, scope, chain);
@@ -305,8 +305,7 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
 
             if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
                 final HttpCacheEntry updatedEntry = responseCache.updateCacheEntry(
-                        target, request, cacheEntry,
-                        backendResponse, requestDate, responseDate);
+                        target, request, cacheEntry, backendResponse, requestDate, responseDate);
                 if (suitabilityChecker.isConditional(request)
                         && suitabilityChecker.allConditionalsMatch(request, updatedEntry, new Date())) {
                     return convert(responseGenerator.generateNotModifiedResponse(updatedEntry));
@@ -332,45 +331,6 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
         }
     }
 
-    HttpCacheEntry satisfyFromCache(final HttpHost target, final HttpRequest request) {
-        HttpCacheEntry entry = null;
-        try {
-            entry = responseCache.getCacheEntry(target, request);
-        } catch (final IOException ioe) {
-            log.warn("Unable to retrieve entries from cache", ioe);
-        }
-        return entry;
-    }
-
-    Map<String, Variant> getExistingCacheVariants(final HttpHost target, final HttpRequest request) {
-        Map<String,Variant> variants = null;
-        try {
-            variants = responseCache.getVariantCacheEntriesWithEtags(target, request);
-        } catch (final IOException ioe) {
-            log.warn("Unable to retrieve variant entries from cache", ioe);
-        }
-        return variants;
-    }
-
-    void flushEntriesInvalidatedByRequest(final HttpHost target, final HttpRequest request) {
-        try {
-            responseCache.flushInvalidatedCacheEntriesFor(target, request);
-        } catch (final IOException ioe) {
-            log.warn("Unable to flush invalidated entries from cache", ioe);
-        }
-    }
-
-    void tryToUpdateVariantMap(
-            final HttpHost target,
-            final HttpRequest request,
-            final Variant matchingVariant) {
-        try {
-            responseCache.reuseVariantEntryFor(target, request, matchingVariant);
-        } catch (final IOException ioe) {
-            log.warn("Could not update cache entry to reuse variant", ioe);
-        }
-    }
-
     ClassicHttpResponse handleBackendResponse(
             final HttpHost target,
             final ClassicHttpRequest request,
@@ -388,11 +348,7 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
             return cacheAndReturnResponse(target, request, backendResponse, requestDate, responseDate);
         } else {
             log.debug("Backend response is not cacheable");
-            try {
-                responseCache.flushCacheEntriesFor(target, request);
-            } catch (final IOException ioe) {
-                log.warn("Unable to flush invalid cache entries", ioe);
-            }
+            responseCache.flushCacheEntriesFor(target, request);
         }
         return backendResponse;
     }
@@ -447,7 +403,7 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
             return new BasicClassicHttpResponse(HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout");
         }
 
-        final Map<String, Variant> variants = getExistingCacheVariants(target, request);
+        final Map<String, Variant> variants = responseCache.getVariantCacheEntriesWithEtags(target, request);
         if (variants != null && !variants.isEmpty()) {
             return negotiateResponseFromVariants(target, request, scope, chain, variants);
         }
@@ -492,9 +448,7 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
                 return callBackend(target, request, scope, chain);
             }
 
-            final HttpCacheEntry matchedEntry = matchingVariant.getEntry();
-
-            if (revalidationResponseIsTooOld(backendResponse, matchedEntry)) {
+            if (revalidationResponseIsTooOld(backendResponse, matchingVariant.getEntry())) {
                 EntityUtils.consume(backendResponse.getEntity());
                 backendResponse.close();
                 final ClassicHttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(request);
@@ -503,21 +457,14 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
 
             recordCacheUpdate(scope.clientContext);
 
-            HttpCacheEntry responseEntry = matchedEntry;
-            try {
-                responseEntry = responseCache.updateVariantCacheEntry(target, conditionalRequest,
-                        matchedEntry, backendResponse, requestDate, responseDate, matchingVariant.getCacheKey());
-            } catch (final IOException ioe) {
-                log.warn("Could not processChallenge cache entry", ioe);
-            } finally {
-                backendResponse.close();
-            }
-
+            final HttpCacheEntry responseEntry = responseCache.updateVariantCacheEntry(
+                    target, conditionalRequest, backendResponse, matchingVariant, requestDate, responseDate);
+            backendResponse.close();
             if (shouldSendNotModifiedResponse(request, responseEntry)) {
                 return convert(responseGenerator.generateNotModifiedResponse(responseEntry));
             }
             final SimpleHttpResponse resp = responseGenerator.generateResponse(request, responseEntry);
-            tryToUpdateVariantMap(target, request, matchingVariant);
+            responseCache.reuseVariantEntryFor(target, request, matchingVariant);
             return convert(resp);
         } catch (final IOException | RuntimeException ex) {
             backendResponse.close();

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/77703a7e/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HeapResourceFactory.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HeapResourceFactory.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HeapResourceFactory.java
index d407a56..bb656e0 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HeapResourceFactory.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/HeapResourceFactory.java
@@ -46,7 +46,7 @@ public class HeapResourceFactory implements ResourceFactory {
     @Override
     public Resource generate(
             final String requestId,
-            final byte[] content, final int off, final int len) throws ResourceIOException {
+            final byte[] content, final int off, final int len) {
         final byte[] copy = new byte[len];
         System.arraycopy(content, off, copy, 0, len);
         return new HeapResource(copy);

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/77703a7e/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 7a8b08b..a83cb63 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
@@ -90,7 +90,7 @@ interface HttpAsyncCache {
      * Update a {@link HttpCacheEntry} using a 304 {@link HttpResponse}.
      */
     Cancellable updateCacheEntry(
-            HttpHost target,
+            HttpHost host,
             HttpRequest request,
             HttpCacheEntry stale,
             HttpResponse originResponse,
@@ -103,13 +103,12 @@ interface HttpAsyncCache {
      * using a 304 {@link HttpResponse}.
      */
     Cancellable updateVariantCacheEntry(
-            HttpHost target,
+            HttpHost host,
             HttpRequest request,
-            HttpCacheEntry stale,
             HttpResponse originResponse,
+            Variant variant,
             Date requestSent,
             Date responseReceived,
-            String cacheKey,
             FutureCallback<HttpCacheEntry> callback);
 
     /**
@@ -117,7 +116,7 @@ interface HttpAsyncCache {
      * requests whose varying headers match those of the given client request.
      */
     Cancellable reuseVariantEntryFor(
-            HttpHost target,
+            HttpHost host,
             HttpRequest req,
             Variant variant,
             FutureCallback<Boolean> callback);

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/77703a7e/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 7bcdd82..756f6f3 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
@@ -30,7 +30,6 @@ import java.util.Date;
 import java.util.Map;
 
 import org.apache.hc.client5.http.cache.HttpCacheEntry;
-import org.apache.hc.client5.http.cache.ResourceIOException;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
@@ -44,12 +43,12 @@ interface HttpCache {
     /**
      * Clear all matching {@link HttpCacheEntry}s.
      */
-    void flushCacheEntriesFor(HttpHost host, HttpRequest request) throws ResourceIOException;
+    void flushCacheEntriesFor(HttpHost host, HttpRequest request);
 
     /**
      * Clear invalidated matching {@link HttpCacheEntry}s
      */
-    void flushInvalidatedCacheEntriesFor(HttpHost host, HttpRequest request) throws ResourceIOException;
+    void flushInvalidatedCacheEntriesFor(HttpHost host, HttpRequest request);
 
     /** Clear any entries that may be invalidated by the given response to
      * a particular request.
@@ -59,13 +58,13 @@ interface HttpCache {
     /**
      * Retrieve matching {@link HttpCacheEntry} from the cache if it exists.
      */
-    HttpCacheEntry getCacheEntry(HttpHost host, HttpRequest request) throws ResourceIOException;
+    HttpCacheEntry getCacheEntry(HttpHost host, HttpRequest request);
 
     /**
      * Retrieve all variants from the cache, if there are no variants then an empty
      * {@link Map} is returned
      */
-    Map<String,Variant> getVariantCacheEntriesWithEtags(HttpHost host, HttpRequest request) throws ResourceIOException;
+    Map<String,Variant> getVariantCacheEntriesWithEtags(HttpHost host, HttpRequest request);
 
     /**
      * Store a {@link HttpResponse} in the cache if possible, and return
@@ -76,38 +75,37 @@ interface HttpCache {
             HttpResponse originResponse,
             ByteArrayBuffer content,
             Date requestSent,
-            Date responseReceived) throws ResourceIOException;
+            Date responseReceived);
 
     /**
      * Update a {@link HttpCacheEntry} using a 304 {@link HttpResponse}.
      */
     HttpCacheEntry updateCacheEntry(
-            HttpHost target,
+            HttpHost host,
             HttpRequest request,
             HttpCacheEntry stale,
             HttpResponse originResponse,
             Date requestSent,
-            Date responseReceived) throws ResourceIOException;
+            Date responseReceived);
 
     /**
      * Update a specific {@link HttpCacheEntry} representing a cached variant
      * using a 304 {@link HttpResponse}.
      */
     HttpCacheEntry updateVariantCacheEntry(
-            HttpHost target,
+            HttpHost host,
             HttpRequest request,
-            HttpCacheEntry stale,
             HttpResponse originResponse,
+            Variant variant,
             Date requestSent,
-            Date responseReceived,
-            String cacheKey) throws ResourceIOException;
+            Date responseReceived);
 
     /**
      * Specifies cache should reuse the given cached variant to satisfy
      * requests whose varying headers match those of the given client request.
      */
     void reuseVariantEntryFor(
-            HttpHost target,
-            HttpRequest req,
-            Variant variant) throws ResourceIOException;
+            HttpHost host,
+            HttpRequest request,
+            Variant variant);
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/77703a7e/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/Variant.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/Variant.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/Variant.java
index 5f94c60..c5fd4c1 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/Variant.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/Variant.java
@@ -46,4 +46,10 @@ class Variant {
     public HttpCacheEntry getEntry() {
         return entry;
     }
+
+    @Override
+    public String toString() {
+        return cacheKey;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/77703a7e/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpCache.java
----------------------------------------------------------------------
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpCache.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpCache.java
index 45b7cdd..1b21f59 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpCache.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestBasicHttpCache.java
@@ -71,7 +71,7 @@ public class TestBasicHttpCache {
     public void testDoNotFlushCacheEntriesOnGet() throws Exception {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest req = new HttpGet("/bar");
-        final String key = (new CacheKeyGenerator()).generateKey(host, req);
+        final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
 
         backing.map.put(key, entry);
@@ -85,7 +85,7 @@ public class TestBasicHttpCache {
     public void testDoNotFlushCacheEntriesOnHead() throws Exception {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest req = new HttpHead("/bar");
-        final String key = (new CacheKeyGenerator()).generateKey(host, req);
+        final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
 
         backing.map.put(key, entry);
@@ -99,7 +99,7 @@ public class TestBasicHttpCache {
     public void testDoNotFlushCacheEntriesOnOptions() throws Exception {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest req = new HttpOptions("/bar");
-        final String key = (new CacheKeyGenerator()).generateKey(host, req);
+        final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
 
         backing.map.put(key, entry);
@@ -113,7 +113,7 @@ public class TestBasicHttpCache {
     public void testDoNotFlushCacheEntriesOnTrace() throws Exception {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest req = new HttpTrace("/bar");
-        final String key = (new CacheKeyGenerator()).generateKey(host, req);
+        final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
 
         backing.map.put(key, entry);
@@ -131,7 +131,7 @@ public class TestBasicHttpCache {
         final HttpResponse resp = HttpTestUtils.make200Response();
         resp.setHeader("Content-Location", "/bar");
         resp.setHeader(HeaderConstants.ETAG, "\"etag\"");
-        final String key = (new CacheKeyGenerator()).generateKey(host, new HttpGet("/bar"));
+        final String key = CacheKeyGenerator.INSTANCE.generateKey(host, new HttpGet("/bar"));
 
         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
            new BasicHeader("Date", DateUtils.formatDate(new Date())),
@@ -152,7 +152,7 @@ public class TestBasicHttpCache {
         final HttpRequest req = new HttpGet("/foo");
         final HttpResponse resp = HttpTestUtils.make200Response();
         resp.setHeader("Content-Location", "/bar");
-        final String key = (new CacheKeyGenerator()).generateKey(host, new HttpGet("/bar"));
+        final String key = CacheKeyGenerator.INSTANCE.generateKey(host, new HttpGet("/bar"));
 
         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry(new Header[] {
            new BasicHeader("Date", DateUtils.formatDate(new Date())),
@@ -170,7 +170,7 @@ public class TestBasicHttpCache {
     public void testCanFlushCacheEntriesAtUri() throws Exception {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest req = new HttpDelete("/bar");
-        final String key = (new CacheKeyGenerator()).generateKey(host, req);
+        final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
         final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
 
         backing.map.put(key, entry);
@@ -186,9 +186,9 @@ public class TestBasicHttpCache {
         assertFalse(entry.hasVariants());
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest req = new HttpGet("http://foo.example.com/bar");
-        final String key = (new CacheKeyGenerator()).generateKey(host, req);
+        final String key = CacheKeyGenerator.INSTANCE.generateKey(host, req);
 
-        impl.storeInCache(host, req, entry);
+        impl.storeInCache(key, host, req, entry);
         assertSame(entry, backing.map.get(key));
     }
 
@@ -207,7 +207,7 @@ public class TestBasicHttpCache {
         final HttpHost host = new HttpHost("foo.example.com");
         final HttpRequest request = new HttpGet("http://foo.example.com/bar");
 
-        final String key = (new CacheKeyGenerator()).generateKey(host, request);
+        final String key = CacheKeyGenerator.INSTANCE.generateKey(host, request);
 
         backing.map.put(key,entry);
 


[3/3] httpcomponents-client git commit: Corrected handling of request with non-repeatable by caching request execution interceptors

Posted by ol...@apache.org.
Corrected handling of request with non-repeatable by caching request execution interceptors


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

Branch: refs/heads/master
Commit: 194e4f528973a7139c178f198fde644d4ea03cb3
Parents: c607197
Author: Oleg Kalnichevski <ol...@apache.org>
Authored: Mon Jan 1 16:52:50 2018 +0100
Committer: Oleg Kalnichevski <ol...@apache.org>
Committed: Mon Jan 1 16:52:50 2018 +0100

----------------------------------------------------------------------
 .../org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java | 5 +++--
 .../java/org/apache/hc/client5/http/impl/cache/CachingExec.java | 3 ++-
 2 files changed, 5 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/194e4f52/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 179fbcd..c812b12 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
@@ -711,7 +711,8 @@ public class AsyncCachingExec extends CachingExecBase implements AsyncExecChainH
                 final Date responseDate1 = getCurrentDate();
 
                 final AsyncExecCallback callback1;
-                if (revalidationResponseIsTooOld(backendResponse1, cacheEntry)) {
+                if (revalidationResponseIsTooOld(backendResponse1, cacheEntry)
+                        && (entityProducer == null || entityProducer.isRepeatable())) {
 
                     final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
                             scope.originalRequest);
@@ -805,7 +806,7 @@ public class AsyncCachingExec extends CachingExecBase implements AsyncExecChainH
 
                         @Override
                         public void completed(final Map<String, Variant> variants) {
-                            if (variants != null && !variants.isEmpty()) {
+                            if (variants != null && !variants.isEmpty() && (entityProducer == null || entityProducer.isRepeatable())) {
                                 negotiateResponseFromVariants(target, request, entityProducer, scope, chain, asyncExecCallback, variants);
                             } else {
                                 callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/194e4f52/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 724fa8a..7fb7e42 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
@@ -447,7 +447,8 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
                 return callBackend(target, request, scope, chain);
             }
 
-            if (revalidationResponseIsTooOld(backendResponse, matchingVariant.getEntry())) {
+            if (revalidationResponseIsTooOld(backendResponse, matchingVariant.getEntry())
+                    && (request.getEntity() == null || request.getEntity().isRepeatable())) {
                 EntityUtils.consume(backendResponse.getEntity());
                 backendResponse.close();
                 final ClassicHttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(request);


[2/3] httpcomponents-client git commit: Code cleanup in AsyncCachingExec: async code made more consistent with that of its classic counterpart and hopefully a bit more readable

Posted by ol...@apache.org.
Code cleanup in AsyncCachingExec: async code made more consistent with that of its classic counterpart and hopefully a bit more readable


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

Branch: refs/heads/master
Commit: c607197fd00a26bc1f946ebdeb865c8777c23540
Parents: 77703a7
Author: Oleg Kalnichevski <ol...@apache.org>
Authored: Sun Dec 31 14:08:46 2017 +0100
Committer: Oleg Kalnichevski <ol...@apache.org>
Committed: Mon Jan 1 16:45:46 2018 +0100

----------------------------------------------------------------------
 .../http/impl/cache/AsyncCachingExec.java       | 953 ++++++++++---------
 .../hc/client5/http/impl/cache/CachingExec.java |   3 +-
 2 files changed, 522 insertions(+), 434 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/c607197f/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 1332f74..179fbcd 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
@@ -143,6 +143,34 @@ public class AsyncCachingExec extends CachingExecBase implements AsyncExecChainH
         }
     }
 
+    static class AsyncExecCallbackWrapper implements AsyncExecCallback {
+
+        private final AsyncExecCallback asyncExecCallback;
+        private final Runnable command;
+
+        AsyncExecCallbackWrapper(final AsyncExecCallback asyncExecCallback, final Runnable command) {
+            this.asyncExecCallback = asyncExecCallback;
+            this.command = command;
+        }
+
+        @Override
+        public AsyncDataConsumer handleResponse(
+                final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException {
+            return null;
+        }
+
+        @Override
+        public void completed() {
+            command.run();
+        }
+
+        @Override
+        public void failed(final Exception cause) {
+            asyncExecCallback.failed(cause);
+        }
+
+    }
+
     @Override
     public void execute(
             final HttpRequest request,
@@ -230,10 +258,17 @@ public class AsyncCachingExec extends CachingExecBase implements AsyncExecChainH
         }
     }
 
-    interface InternalCallback extends AsyncExecCallback {
-
-        boolean cacheResponse(HttpResponse backendResponse) throws HttpException, IOException;
-
+    void chainProceed(
+            final HttpRequest request,
+            final AsyncEntityProducer entityProducer,
+            final AsyncExecChain.Scope scope,
+            final AsyncExecChain chain,
+            final AsyncExecCallback asyncExecCallback) {
+        try {
+            chain.proceed(request, entityProducer, scope, asyncExecCallback);
+        } catch (final HttpException | IOException ex) {
+            asyncExecCallback.failed(ex);
+        }
     }
 
     void callBackend(
@@ -243,273 +278,290 @@ public class AsyncCachingExec extends CachingExecBase implements AsyncExecChainH
             final AsyncExecChain.Scope scope,
             final AsyncExecChain chain,
             final AsyncExecCallback asyncExecCallback) {
-        callBackendInternal(target, request, entityProducer, scope, chain, new InternalCallback() {
-
-            @Override
-            public boolean cacheResponse(final HttpResponse backendResponse) {
-                return true;
-            }
+        log.debug("Calling the backend");
+        final Date requestDate = getCurrentDate();
+        final AtomicReference<AsyncExecCallback> callbackRef = new AtomicReference<>();
+        chainProceed(request, entityProducer, scope, chain, new AsyncExecCallback() {
 
             @Override
             public AsyncDataConsumer handleResponse(
-                    final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException {
-                return asyncExecCallback.handleResponse(response, entityDetails);
+                    final HttpResponse backendResponse, final EntityDetails entityDetails) throws HttpException, IOException {
+                final Date responseDate = getCurrentDate();
+                backendResponse.addHeader("Via", generateViaHeader(backendResponse));
+
+                final AsyncExecCallback callback = new BackendResponseHandler(target, request, requestDate, responseDate, scope, asyncExecCallback);
+                callbackRef.set(callback);
+                return callback.handleResponse(backendResponse, entityDetails);
             }
 
             @Override
             public void completed() {
-                asyncExecCallback.completed();
+                final AsyncExecCallback callback = callbackRef.getAndSet(null);
+                if (callback != null) {
+                    callback.completed();
+                } else {
+                    asyncExecCallback.completed();
+                }
             }
 
             @Override
             public void failed(final Exception cause) {
-                asyncExecCallback.failed(cause);
+                final AsyncExecCallback callback = callbackRef.getAndSet(null);
+                if (callback != null) {
+                    callback.failed(cause);
+                } else {
+                    asyncExecCallback.failed(cause);
+                }
             }
 
         });
     }
 
-    static class ResponseState {
-
-        final HttpResponse backendResponse;
-        final Date responseDate;
-        final ByteArrayBuffer buffer;
-
-        ResponseState(final HttpResponse backendResponse, final Date responseDate, final ByteArrayBuffer buffer) {
+    class CachingAsyncDataConsumer implements AsyncDataConsumer {
+
+        private final AsyncExecCallback fallback;
+        private final HttpResponse backendResponse;
+        private final EntityDetails entityDetails;
+        private final Date responseDate;
+        private final AtomicBoolean writtenThrough;
+        private final AtomicReference<ByteArrayBuffer> bufferRef;
+        private final AtomicReference<AsyncDataConsumer> dataConsumerRef;
+
+        CachingAsyncDataConsumer(
+                final AsyncExecCallback fallback,
+                final HttpResponse backendResponse,
+                final EntityDetails entityDetails,
+                final Date responseDate) {
+            this.fallback = fallback;
             this.backendResponse = backendResponse;
             this.responseDate = responseDate;
-            this.buffer = buffer;
+            this.entityDetails = entityDetails;
+            this.writtenThrough = new AtomicBoolean(false);
+            this.bufferRef = new AtomicReference<>(entityDetails != null ? new ByteArrayBuffer(1024) : null);
+            this.dataConsumerRef = new AtomicReference<>();
         }
 
-    }
-
-    void callBackendInternal(
-            final HttpHost target,
-            final HttpRequest request,
-            final AsyncEntityProducer entityProducer,
-            final AsyncExecChain.Scope scope,
-            final AsyncExecChain chain,
-            final InternalCallback asyncExecCallback) {
-        log.debug("Calling the backend");
-        final ComplexFuture<?> future = scope.future;
-        final Date requestDate = getCurrentDate();
-        try {
-            chain.proceed(request, entityProducer, scope, new AsyncExecCallback() {
+        @Override
+        public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
+            final AsyncDataConsumer dataConsumer = dataConsumerRef.get();
+            if (dataConsumer != null) {
+                dataConsumer.updateCapacity(capacityChannel);
+            } else {
+                capacityChannel.update(Integer.MAX_VALUE);
+            }
+        }
 
-                private final AtomicReference<ResponseState> responseStateRef = new AtomicReference<>();
-                private final AtomicReference<AsyncDataConsumer> dataConsumerRef = new AtomicReference<>();
+        @Override
+        public final int consume(final ByteBuffer src) throws IOException {
+            final ByteArrayBuffer buffer = bufferRef.get();
+            if (buffer != null) {
+                if (src.hasArray()) {
+                    buffer.append(src.array(), src.arrayOffset() + src.position(), src.remaining());
+                } else {
+                    while (src.hasRemaining()) {
+                        buffer.append(src.get());
+                    }
+                }
+                if (buffer.length() > cacheConfig.getMaxObjectSize()) {
+                    log.debug("Backend response content length exceeds maximum");
+                    // Over the max limit. Stop buffering and forward the response
+                    // along with all the data buffered so far to the caller.
+                    bufferRef.set(null);
+                    try {
+                        final AsyncDataConsumer dataConsumer = fallback.handleResponse(backendResponse, entityDetails);
+                        if (dataConsumer != null) {
+                            dataConsumerRef.set(dataConsumer);
+                            writtenThrough.set(true);
+                            return dataConsumer.consume(ByteBuffer.wrap(buffer.array(), 0, buffer.length()));
+                        }
+                    } catch (final HttpException ex) {
+                        fallback.failed(ex);
+                    }
+                }
+                return Integer.MAX_VALUE;
+            } else {
+                final AsyncDataConsumer dataConsumer = dataConsumerRef.get();
+                if (dataConsumer != null) {
+                    return dataConsumer.consume(src);
+                } else {
+                    return Integer.MAX_VALUE;
+                }
+            }
+        }
 
-                @Override
-                public AsyncDataConsumer handleResponse(
-                        final HttpResponse backendResponse,
-                        final EntityDetails entityDetails) throws HttpException, IOException {
-                    final Date responseDate = getCurrentDate();
-                    backendResponse.addHeader("Via", generateViaHeader(backendResponse));
+        @Override
+        public final void streamEnd(final List<? extends Header> trailers) throws HttpException, IOException {
+            final AsyncDataConsumer dataConsumer = dataConsumerRef.getAndSet(null);
+            if (dataConsumer != null) {
+                dataConsumer.streamEnd(trailers);
+            }
+        }
 
-                    responseCompliance.ensureProtocolCompliance(scope.originalRequest, request, backendResponse);
-                    responseCache.flushInvalidatedCacheEntriesFor(target, request, backendResponse, new FutureCallback<Boolean>() {
+        @Override
+        public void releaseResources() {
+            final AsyncDataConsumer dataConsumer = dataConsumerRef.getAndSet(null);
+            if (dataConsumer != null) {
+                dataConsumer.releaseResources();
+            }
+        }
 
-                        @Override
-                        public void completed(final Boolean result) {
-                        }
+    };
+
+    class BackendResponseHandler implements AsyncExecCallback {
+
+        private final HttpHost target;
+        private final HttpRequest request;
+        private final Date requestDate;
+        private final Date responseDate;
+        private final AsyncExecChain.Scope scope;
+        private final AsyncExecCallback asyncExecCallback;
+        private final AtomicReference<CachingAsyncDataConsumer> cachingConsumerRef;
+
+        BackendResponseHandler(
+                final HttpHost target,
+                final HttpRequest request,
+                final Date requestDate,
+                final Date responseDate,
+                final AsyncExecChain.Scope scope,
+                final AsyncExecCallback asyncExecCallback) {
+            this.target = target;
+            this.request = request;
+            this.requestDate = requestDate;
+            this.responseDate = responseDate;
+            this.scope = scope;
+            this.asyncExecCallback = asyncExecCallback;
+            this.cachingConsumerRef = new AtomicReference<>();
+        }
 
-                        @Override
-                        public void failed(final Exception ex) {
-                            log.warn("Unable to flush invalidated entries from cache", ex);
-                        }
+        @Override
+        public AsyncDataConsumer handleResponse(
+                final HttpResponse backendResponse,
+                final EntityDetails entityDetails) throws HttpException, IOException {
+            responseCompliance.ensureProtocolCompliance(scope.originalRequest, request, backendResponse);
+            responseCache.flushInvalidatedCacheEntriesFor(target, request, backendResponse, new FutureCallback<Boolean>() {
 
-                        @Override
-                        public void cancelled() {
-                        }
+                @Override
+                public void completed(final Boolean result) {
+                }
 
-                    });
-                    final boolean cacheable = asyncExecCallback.cacheResponse(backendResponse)
-                            && responseCachingPolicy.isResponseCacheable(request, backendResponse);
-                    if (cacheable) {
-                        responseStateRef.set(new ResponseState(
-                                backendResponse,
-                                responseDate,
-                                entityDetails != null ? new ByteArrayBuffer(1024) : null));
-                        storeRequestIfModifiedSinceFor304Response(request, backendResponse);
-                    } else {
-                        log.debug("Backend response is not cacheable");
-                        responseCache.flushCacheEntriesFor(target, request, new FutureCallback<Boolean>() {
+                @Override
+                public void failed(final Exception ex) {
+                    log.warn("Unable to flush invalidated entries from cache", ex);
+                }
 
-                            @Override
-                            public void completed(final Boolean result) {
-                            }
+                @Override
+                public void cancelled() {
+                }
 
-                            @Override
-                            public void failed(final Exception ex) {
-                                log.warn("Unable to flush invalidated entries from cache", ex);
-                            }
+            });
+            final boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse);
+            if (cacheable) {
+                cachingConsumerRef.set(new CachingAsyncDataConsumer(asyncExecCallback, backendResponse, entityDetails, responseDate));
+                storeRequestIfModifiedSinceFor304Response(request, backendResponse);
+            } else {
+                log.debug("Backend response is not cacheable");
+                responseCache.flushCacheEntriesFor(target, request, new FutureCallback<Boolean>() {
+
+                    @Override
+                    public void completed(final Boolean result) {
+                    }
 
-                            @Override
-                            public void cancelled() {
-                            }
+                    @Override
+                    public void failed(final Exception ex) {
+                        log.warn("Unable to flush invalidated entries from cache", ex);
+                    }
 
-                        });
+                    @Override
+                    public void cancelled() {
                     }
-                    if (responseStateRef.get() != null) {
-                        log.debug("Caching backend response");
-                        if (entityDetails == null) {
-                            scope.execRuntime.releaseConnection();
-                            return null;
-                        } else {
-                            return new AsyncDataConsumer() {
 
-                                @Override
-                                public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
-                                    final AsyncDataConsumer dataConsumer = dataConsumerRef.get();
-                                    if (dataConsumer != null) {
-                                        dataConsumer.updateCapacity(capacityChannel);
-                                    } else {
-                                        capacityChannel.update(Integer.MAX_VALUE);
-                                    }
-                                }
+                });
+            }
+            final CachingAsyncDataConsumer cachingDataConsumer = cachingConsumerRef.get();
+            if (cachingDataConsumer != null) {
+                log.debug("Caching backend response");
+                return cachingDataConsumer;
+            } else {
+                return asyncExecCallback.handleResponse(backendResponse, entityDetails);
+            }
+        }
 
-                                @Override
-                                public final int consume(final ByteBuffer src) throws IOException {
-                                    final ResponseState responseState = responseStateRef.get();
-                                    if (responseState != null) {
-                                        final ByteArrayBuffer buffer = responseState.buffer;
-                                        if (src.hasArray()) {
-                                            buffer.append(src.array(), src.arrayOffset() + src.position(), src.remaining());
-                                        } else {
-                                            while (src.hasRemaining()) {
-                                                buffer.append(src.get());
-                                            }
-                                        }
-                                        if (buffer.length() > cacheConfig.getMaxObjectSize()) {
-                                            log.debug("Backend response content length exceeds maximum");
-                                            // Over the max limit. Stop buffering and forward the response
-                                            // along with all the data buffered so far to the caller.
-                                            responseStateRef.set(null);
+        @Override
+        public void completed() {
+            final ComplexFuture<?> future = scope.future;
+            final CachingAsyncDataConsumer cachingDataConsumer = cachingConsumerRef.getAndSet(null);
+            if (cachingDataConsumer != null && !cachingDataConsumer.writtenThrough.get()) {
+                future.setDependency(responseCache.getCacheEntry(target, request, new FutureCallback<HttpCacheEntry>() {
+
+                    @Override
+                    public void completed(final HttpCacheEntry existingEntry) {
+                        final HttpResponse backendResponse = cachingDataConsumer.backendResponse;
+                        if (DateUtils.isAfter(existingEntry, backendResponse, HttpHeaders.DATE)) {
+                            try {
+                                final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, existingEntry);
+                                triggerResponse(cacheResponse, scope, asyncExecCallback);
+                            } catch (final ResourceIOException ex) {
+                                asyncExecCallback.failed(ex);
+                            }
+                        } else {
+                            final Date responseDate = cachingDataConsumer.responseDate;
+                            final ByteArrayBuffer buffer = cachingDataConsumer.bufferRef.getAndSet(null);
+                            future.setDependency(responseCache.createCacheEntry(
+                                    target,
+                                    request,
+                                    backendResponse,
+                                    buffer,
+                                    requestDate,
+                                    responseDate,
+                                    new FutureCallback<HttpCacheEntry>() {
+
+                                        @Override
+                                        public void completed(final HttpCacheEntry newEntry) {
+                                            log.debug("Backend response successfully cached");
                                             try {
-                                                final AsyncDataConsumer dataConsumer = asyncExecCallback.handleResponse(
-                                                        backendResponse, entityDetails);
-                                                if (dataConsumer != null) {
-                                                    dataConsumerRef.set(dataConsumer);
-                                                    return dataConsumer.consume(ByteBuffer.wrap(buffer.array(), 0, buffer.length()));
-                                                }
-                                            } catch (final HttpException ex) {
+                                                final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, newEntry);
+                                                triggerResponse(cacheResponse, scope, asyncExecCallback);
+                                            } catch (final ResourceIOException ex) {
                                                 asyncExecCallback.failed(ex);
                                             }
                                         }
-                                        return Integer.MAX_VALUE;
-                                    } else {
-                                        final AsyncDataConsumer dataConsumer = dataConsumerRef.get();
-                                        if (dataConsumer != null) {
-                                            return dataConsumer.consume(src);
-                                        } else {
-                                            return Integer.MAX_VALUE;
+
+                                        @Override
+                                        public void failed(final Exception ex) {
+                                            asyncExecCallback.failed(ex);
                                         }
-                                    }
-                                }
 
-                                @Override
-                                public final void streamEnd(final List<? extends Header> trailers) throws HttpException, IOException {
-                                    scope.execRuntime.releaseConnection();
-                                    final AsyncDataConsumer dataConsumer = dataConsumerRef.getAndSet(null);
-                                    if (dataConsumer != null) {
-                                        dataConsumer.streamEnd(trailers);
-                                    }
-                                }
+                                        @Override
+                                        public void cancelled() {
+                                            asyncExecCallback.failed(new InterruptedIOException());
+                                        }
 
-                                @Override
-                                public void releaseResources() {
-                                    final AsyncDataConsumer dataConsumer = dataConsumerRef.getAndSet(null);
-                                    if (dataConsumer != null) {
-                                        dataConsumer.releaseResources();
-                                    }
-                                }
+                                    }));
 
-                            };
                         }
-                    } else {
-                        return asyncExecCallback.handleResponse(backendResponse, entityDetails);
                     }
-                }
-
-                @Override
-                public void completed() {
-                    final ResponseState responseState = responseStateRef.getAndSet(null);
-                    if (responseState != null) {
-                        future.setDependency(responseCache.getCacheEntry(target, request, new FutureCallback<HttpCacheEntry>() {
-
-                            @Override
-                            public void completed(final HttpCacheEntry existingEntry) {
-                                final HttpResponse backendResponse = responseState.backendResponse;
-                                if (DateUtils.isAfter(existingEntry, backendResponse, HttpHeaders.DATE)) {
-                                    try {
-                                        final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, existingEntry);
-                                        triggerResponse(cacheResponse, scope, asyncExecCallback);
-                                    } catch (final ResourceIOException ex) {
-                                        asyncExecCallback.failed(ex);
-                                    }
-                                } else {
-                                    final Date responseDate = responseState.responseDate;
-                                    final ByteArrayBuffer buffer = responseState.buffer;
-                                    future.setDependency(responseCache.createCacheEntry(
-                                            target,
-                                            request,
-                                            backendResponse,
-                                            buffer,
-                                            requestDate,
-                                            responseDate,
-                                            new FutureCallback<HttpCacheEntry>() {
-
-                                                @Override
-                                                public void completed(final HttpCacheEntry newEntry) {
-                                                    log.debug("Backend response successfully cached");
-                                                    try {
-                                                        final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, newEntry);
-                                                        triggerResponse(cacheResponse, scope, asyncExecCallback);
-                                                    } catch (final ResourceIOException ex) {
-                                                        asyncExecCallback.failed(ex);
-                                                    }
-                                                }
-
-                                                @Override
-                                                public void failed(final Exception ex) {
-                                                    asyncExecCallback.failed(ex);
-                                                }
-
-                                                @Override
-                                                public void cancelled() {
-                                                    asyncExecCallback.failed(new InterruptedIOException());
-                                                }
-
-                                            }));
-
-                                }
-                            }
-
-                            @Override
-                            public void failed(final Exception cause) {
-                                asyncExecCallback.failed(cause);
-                            }
 
-                            @Override
-                            public void cancelled() {
-                                asyncExecCallback.failed(new InterruptedIOException());
-                            }
+                    @Override
+                    public void failed(final Exception cause) {
+                        asyncExecCallback.failed(cause);
+                    }
 
-                        }));
-                    } else {
-                        asyncExecCallback.completed();
+                    @Override
+                    public void cancelled() {
+                        asyncExecCallback.failed(new InterruptedIOException());
                     }
-                }
 
-                @Override
-                public void failed(final Exception cause) {
-                    asyncExecCallback.failed(cause);
-                }
+                }));
+            } else {
+                asyncExecCallback.completed();
+            }
+        }
 
-            });
-        } catch (final HttpException | IOException ex) {
-            asyncExecCallback.failed(ex);
+        @Override
+        public void failed(final Exception cause) {
+            asyncExecCallback.failed(cause);
         }
+
     }
 
     private void handleCacheHit(
@@ -563,170 +615,172 @@ public class AsyncCachingExec extends CachingExecBase implements AsyncExecChainH
             final AsyncExecChain chain,
             final AsyncExecCallback asyncExecCallback,
             final HttpCacheEntry cacheEntry) {
-
-        final ComplexFuture<?> future = scope.future;
         final Date requestDate = getCurrentDate();
-        final InternalCallback internalCallback = new InternalCallback() {
+        final HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(scope.originalRequest, cacheEntry);
+        chainProceed(conditionalRequest, entityProducer, scope, chain, new AsyncExecCallback() {
+
+            final AtomicReference<AsyncExecCallback> callbackRef = new AtomicReference<>();
+
+            void triggerUpdatedCacheEntryResponse(final HttpResponse backendResponse, final Date responseDate) {
+                final ComplexFuture<?> future = scope.future;
+                recordCacheUpdate(scope.clientContext);
+                future.setDependency(responseCache.updateCacheEntry(
+                        target,
+                        request,
+                        cacheEntry,
+                        backendResponse,
+                        requestDate,
+                        responseDate,
+                        new FutureCallback<HttpCacheEntry>() {
+
+                            @Override
+                            public void completed(final HttpCacheEntry updatedEntry) {
+                                if (suitabilityChecker.isConditional(request)
+                                        && suitabilityChecker.allConditionalsMatch(request, updatedEntry, new Date())) {
+                                    final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(updatedEntry);
+                                    triggerResponse(cacheResponse, scope, asyncExecCallback);
+                                } else {
+                                    try {
+                                        final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, updatedEntry);
+                                        triggerResponse(cacheResponse, scope, asyncExecCallback);
+                                    } catch (final ResourceIOException ex) {
+                                        asyncExecCallback.failed(ex);
+                                    }
+                                }
+                            }
 
-            private final AtomicReference<Date> responseDateRef = new AtomicReference<>(null);
-            private final AtomicReference<HttpResponse> backendResponseRef = new AtomicReference<>(null);
+                            @Override
+                            public void failed(final Exception ex) {
+                                asyncExecCallback.failed(ex);
+                            }
+
+                            @Override
+                            public void cancelled() {
+                                asyncExecCallback.failed(new InterruptedIOException());
+                            }
+
+                        }));
+            }
+
+            void triggerResponseStaleCacheEntry() {
+                try {
+                    final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, cacheEntry);
+                    cacheResponse.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\"");
+                    triggerResponse(cacheResponse, scope, asyncExecCallback);
+                } catch (final ResourceIOException ex) {
+                    asyncExecCallback.failed(ex);
+                }
+            }
+
+            AsyncExecCallback evaluateResponse(final HttpResponse backendResponse, final Date responseDate) {
+                backendResponse.addHeader(HeaderConstants.VIA, generateViaHeader(backendResponse));
 
-            @Override
-            public boolean cacheResponse(final HttpResponse backendResponse) throws IOException {
-                final Date responseDate = getCurrentDate();
-                responseDateRef.set(requestDate);
                 final int statusCode = backendResponse.getCode();
                 if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
                     recordCacheUpdate(scope.clientContext);
                 }
                 if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
-                    backendResponseRef.set(backendResponse);
-                    return false;
+                    return new AsyncExecCallbackWrapper(asyncExecCallback, new Runnable() {
+
+                        @Override
+                        public void run() {
+                            triggerUpdatedCacheEntryResponse(backendResponse, responseDate);
+                        }
+
+                    });
                 }
                 if (staleIfErrorAppliesTo(statusCode)
                         && !staleResponseNotAllowed(request, cacheEntry, getCurrentDate())
                         && validityPolicy.mayReturnStaleIfError(request, cacheEntry, responseDate)) {
-                    backendResponseRef.set(backendResponse);
-                    return false;
+                    return new AsyncExecCallbackWrapper(asyncExecCallback, new Runnable() {
+
+                        @Override
+                        public void run() {
+                            triggerResponseStaleCacheEntry();
+                        }
+
+                    });
                 }
-                return true;
+                return new BackendResponseHandler(target, conditionalRequest, requestDate, responseDate, scope, asyncExecCallback);
             }
 
             @Override
             public AsyncDataConsumer handleResponse(
-                    final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException {
-                if (backendResponseRef.get() == null) {
-                    return asyncExecCallback.handleResponse(response, entityDetails);
-                } else {
-                    return null;
-                }
-            }
+                    final HttpResponse backendResponse1, final EntityDetails entityDetails) throws HttpException, IOException {
 
-            @Override
-            public void completed() {
-                final HttpResponse backendResponse = backendResponseRef.getAndSet(null);
-                if (backendResponse != null) {
-                    final int statusCode = backendResponse.getCode();
-                    try {
-                        if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
-                            future.setDependency(responseCache.updateCacheEntry(
-                                    target,
-                                    request,
-                                    cacheEntry,
-                                    backendResponse,
-                                    requestDate,
-                                    responseDateRef.get(),
-                                    new FutureCallback<HttpCacheEntry>() {
+                final Date responseDate1 = getCurrentDate();
 
-                                        @Override
-                                        public void completed(final HttpCacheEntry updatedEntry) {
-                                            if (suitabilityChecker.isConditional(request)
-                                                    && suitabilityChecker.allConditionalsMatch(request, updatedEntry, new Date())) {
-                                                final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(updatedEntry);
-                                                triggerResponse(cacheResponse, scope, asyncExecCallback);
-                                            } else {
-                                                try {
-                                                    final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, updatedEntry);
-                                                    triggerResponse(cacheResponse, scope, asyncExecCallback);
-                                                } catch (final ResourceIOException ex) {
-                                                    asyncExecCallback.failed(ex);
-                                                }
-                                            }
-                                        }
+                final AsyncExecCallback callback1;
+                if (revalidationResponseIsTooOld(backendResponse1, cacheEntry)) {
 
-                                        @Override
-                                        public void failed(final Exception ex) {
-                                            asyncExecCallback.failed(ex);
-                                        }
+                    final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
+                            scope.originalRequest);
 
-                                        @Override
-                                        public void cancelled() {
-                                            asyncExecCallback.failed(new InterruptedIOException());
-                                        }
+                    callback1 = new AsyncExecCallbackWrapper(asyncExecCallback, new Runnable() {
 
-                                    }));
-                        } else if (staleIfErrorAppliesTo(statusCode)) {
-                            final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, cacheEntry);
-                            cacheResponse.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\"");
-                            triggerResponse(cacheResponse, scope, asyncExecCallback);
-                        }
-                    } catch (final IOException ex) {
-                        asyncExecCallback.failed(ex);
-                    }
-                } else {
-                    asyncExecCallback.completed();
-                }
-            }
+                        @Override
+                        public void run() {
+                            chainProceed(unconditional, entityProducer, scope, chain, new AsyncExecCallback() {
 
-            @Override
-            public void failed(final Exception cause) {
-                asyncExecCallback.failed(cause);
-            }
+                                @Override
+                                public AsyncDataConsumer handleResponse(
+                                        final HttpResponse backendResponse2, final EntityDetails entityDetails) throws HttpException, IOException {
+                                    final Date responseDate2 = getCurrentDate();
+                                    final AsyncExecCallback callback2 = evaluateResponse(backendResponse2, responseDate2);
+                                    callbackRef.set(callback2);
+                                    return callback2.handleResponse(backendResponse2, entityDetails);
+                                }
 
-        };
+                                @Override
+                                public void completed() {
+                                    final AsyncExecCallback callback2 = callbackRef.getAndSet(null);
+                                    if (callback2 != null) {
+                                        callback2.completed();
+                                    } else {
+                                        asyncExecCallback.completed();
+                                    }
+                                }
 
-        final HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(scope.originalRequest, cacheEntry);
-        callBackendInternal(target, conditionalRequest, entityProducer, scope, chain, new InternalCallback() {
+                                @Override
+                                public void failed(final Exception cause) {
+                                    final AsyncExecCallback callback2 = callbackRef.getAndSet(null);
+                                    if (callback2 != null) {
+                                        callback2.failed(cause);
+                                    } else {
+                                        asyncExecCallback.failed(cause);
+                                    }
+                                }
 
-            private final AtomicBoolean revalidate = new AtomicBoolean(false);
+                            });
 
-            @Override
-            public boolean cacheResponse(final HttpResponse backendResponse) throws HttpException, IOException {
-                if (revalidationResponseIsTooOld(backendResponse, cacheEntry)) {
-                    revalidate.set(true);
-                    return false;
-                } else {
-                    return internalCallback.cacheResponse(backendResponse);
-                }
-            }
+                        }
 
-            @Override
-            public AsyncDataConsumer handleResponse(
-                    final HttpResponse response,
-                    final EntityDetails entityDetails) throws HttpException, IOException {
-                if (revalidate.get()) {
-                    return null;
+                    });
                 } else {
-                    return internalCallback.handleResponse(response, entityDetails);
+                    callback1 = evaluateResponse(backendResponse1, responseDate1);
                 }
+                callbackRef.set(callback1);
+                return callback1.handleResponse(backendResponse1, entityDetails);
             }
 
             @Override
             public void completed() {
-                if (revalidate.getAndSet(false)) {
-                    final HttpRequest unconditionalRequest = conditionalRequestBuilder.buildUnconditionalRequest(scope.originalRequest);
-                    callBackendInternal(target, unconditionalRequest, entityProducer, scope, chain, new InternalCallback() {
-
-                        @Override
-                        public boolean cacheResponse(final HttpResponse backendResponse) throws HttpException, IOException {
-                            return internalCallback.cacheResponse(backendResponse);
-                        }
-
-                        @Override
-                        public AsyncDataConsumer handleResponse(
-                                final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException {
-                            return internalCallback.handleResponse(response, entityDetails);
-                        }
-
-                        @Override
-                        public void completed() {
-                            internalCallback.completed();
-                        }
-
-                        @Override
-                        public void failed(final Exception cause) {
-                            internalCallback.failed(cause);
-                        }
-
-                    });
+                final AsyncExecCallback callback1 = callbackRef.getAndSet(null);
+                if (callback1 != null) {
+                    callback1.completed();
                 } else {
-                    internalCallback.completed();
+                    asyncExecCallback.completed();
                 }
             }
 
             @Override
             public void failed(final Exception cause) {
-                internalCallback.failed(cause);
+                final AsyncExecCallback callback1 = callbackRef.getAndSet(null);
+                if (callback1 != null) {
+                    callback1.failed(cause);
+                } else {
+                    asyncExecCallback.failed(cause);
+                }
             }
 
         });
@@ -787,109 +841,139 @@ public class AsyncCachingExec extends CachingExecBase implements AsyncExecChainH
         final HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(request, variants);
 
         final Date requestDate = getCurrentDate();
-        callBackendInternal(target, conditionalRequest, entityProducer, scope, chain, new InternalCallback() {
+        chainProceed(conditionalRequest, entityProducer, scope, chain, new AsyncExecCallback() {
 
-            private final AtomicReference<Date> responseDateRef = new AtomicReference<>(null);
-            private final AtomicReference<HttpResponse> backendResponseRef = new AtomicReference<>(null);
+            final AtomicReference<AsyncExecCallback> callbackRef = new AtomicReference<>();
 
-            @Override
-            public boolean cacheResponse(final HttpResponse backendResponse) throws IOException {
-                responseDateRef.set(getCurrentDate());
-                if (backendResponse.getCode() == HttpStatus.SC_NOT_MODIFIED) {
-                    backendResponseRef.set(backendResponse);
-                    return false;
-                } else {
-                    return true;
-                }
+            void updateVariantCacheEntry(final HttpResponse backendResponse, final Date responseDate, final Variant matchingVariant) {
+                recordCacheUpdate(scope.clientContext);
+                future.setDependency(responseCache.updateVariantCacheEntry(
+                        target,
+                        conditionalRequest,
+                        backendResponse,
+                        matchingVariant,
+                        requestDate,
+                        responseDate,
+                        new FutureCallback<HttpCacheEntry>() {
+
+                            @Override
+                            public void completed(final HttpCacheEntry responseEntry) {
+                                if (shouldSendNotModifiedResponse(request, responseEntry)) {
+                                    final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(responseEntry);
+                                    triggerResponse(cacheResponse, scope, asyncExecCallback);
+                                } else {
+                                    try {
+                                        final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, responseEntry);
+                                        future.setDependency(responseCache.reuseVariantEntryFor(
+                                                target,
+                                                request,
+                                                matchingVariant,
+                                                new FutureCallback<Boolean>() {
+
+                                                    @Override
+                                                    public void completed(final Boolean result) {
+                                                        triggerResponse(cacheResponse, scope, asyncExecCallback);
+                                                    }
+
+                                                    @Override
+                                                    public void failed(final Exception ex) {
+                                                        asyncExecCallback.failed(ex);
+                                                    }
+
+                                                    @Override
+                                                    public void cancelled() {
+                                                        asyncExecCallback.failed(new InterruptedIOException());
+                                                    }
+
+                                                }));
+                                    } catch (final ResourceIOException ex) {
+                                        asyncExecCallback.failed(ex);
+                                    }
+                                }
+                            }
+
+                            @Override
+                            public void failed(final Exception ex) {
+                                asyncExecCallback.failed(ex);
+                            }
+
+                            @Override
+                            public void cancelled() {
+                                asyncExecCallback.failed(new InterruptedIOException());
+                            }
+
+                        }));
             }
 
             @Override
             public AsyncDataConsumer handleResponse(
-                    final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException {
-                return asyncExecCallback.handleResponse(response, entityDetails);
-            }
+                    final HttpResponse backendResponse, final EntityDetails entityDetails) throws HttpException, IOException {
+                final Date responseDate = getCurrentDate();
+                backendResponse.addHeader("Via", generateViaHeader(backendResponse));
 
-            @Override
-            public void completed() {
-                final HttpResponse backendResponse = backendResponseRef.getAndSet(null);
-                if (backendResponse != null) {
+                final AsyncExecCallback callback;
+
+                if (backendResponse.getCode() != HttpStatus.SC_NOT_MODIFIED) {
+                    callback = new BackendResponseHandler(target, request, requestDate, responseDate, scope, asyncExecCallback);
+                } else {
                     final Header resultEtagHeader = backendResponse.getFirstHeader(HeaderConstants.ETAG);
                     if (resultEtagHeader == null) {
                         log.warn("304 response did not contain ETag");
-                        callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
-                        return;
-                    }
-                    final String resultEtag = resultEtagHeader.getValue();
-                    final Variant matchingVariant = variants.get(resultEtag);
-                    if (matchingVariant == null) {
-                        log.debug("304 response did not contain ETag matching one sent in If-None-Match");
-                        callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
-                        return;
-                    }
-                    if (revalidationResponseIsTooOld(backendResponse, matchingVariant.getEntry())) {
-                        final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(request);
-                        scope.clientContext.setAttribute(HttpCoreContext.HTTP_REQUEST, unconditional);
-                        callBackend(target, unconditional, entityProducer, scope, chain, asyncExecCallback);
-                        return;
-                    }
-                    recordCacheUpdate(scope.clientContext);
-                    future.setDependency(responseCache.updateVariantCacheEntry(
-                            target,
-                            conditionalRequest,
-                            backendResponse,
-                            matchingVariant,
-                            requestDate,
-                            responseDateRef.get(),
-                            new FutureCallback<HttpCacheEntry>() {
+                        callback = new AsyncExecCallbackWrapper(asyncExecCallback, new Runnable() {
 
-                                @Override
-                                public void completed(final HttpCacheEntry responseEntry) {
-                                    if (shouldSendNotModifiedResponse(request, responseEntry)) {
-                                        final SimpleHttpResponse cacheResponse = responseGenerator.generateNotModifiedResponse(responseEntry);
-                                        triggerResponse(cacheResponse, scope, asyncExecCallback);
-                                    } else {
-                                        try {
-                                            final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, responseEntry);
-                                            future.setDependency(responseCache.reuseVariantEntryFor(
-                                                    target,
-                                                    request,
-                                                    matchingVariant,
-                                                    new FutureCallback<Boolean>() {
-
-                                                        @Override
-                                                        public void completed(final Boolean result) {
-                                                            triggerResponse(cacheResponse, scope, asyncExecCallback);
-                                                        }
-
-                                                        @Override
-                                                        public void failed(final Exception ex) {
-                                                            asyncExecCallback.failed(ex);
-                                                        }
-
-                                                        @Override
-                                                        public void cancelled() {
-                                                            asyncExecCallback.failed(new InterruptedIOException());
-                                                        }
-
-                                                    }));
-                                        } catch (final ResourceIOException ex) {
-                                            asyncExecCallback.failed(ex);
-                                        }
-                                    }
-                                }
+                            @Override
+                            public void run() {
+                                callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
+                            }
 
-                                @Override
-                                public void failed(final Exception ex) {
-                                    asyncExecCallback.failed(ex);
-                                }
+                        });
+                    } else {
+                        final String resultEtag = resultEtagHeader.getValue();
+                        final Variant matchingVariant = variants.get(resultEtag);
+                        if (matchingVariant == null) {
+                            log.debug("304 response did not contain ETag matching one sent in If-None-Match");
+                            callback = new AsyncExecCallbackWrapper(asyncExecCallback, new Runnable() {
 
                                 @Override
-                                public void cancelled() {
-                                    asyncExecCallback.failed(new InterruptedIOException());
+                                public void run() {
+                                    callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
                                 }
 
-                            }));
+                            });
+                        } else {
+                            if (revalidationResponseIsTooOld(backendResponse, matchingVariant.getEntry())) {
+                                final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(request);
+                                scope.clientContext.setAttribute(HttpCoreContext.HTTP_REQUEST, unconditional);
+                                callback = new AsyncExecCallbackWrapper(asyncExecCallback, new Runnable() {
+
+                                    @Override
+                                    public void run() {
+                                        callBackend(target, request, entityProducer, scope, chain, asyncExecCallback);
+                                    }
+
+                                });
+                            } else {
+                                callback = new AsyncExecCallbackWrapper(asyncExecCallback, new Runnable() {
+
+                                    @Override
+                                    public void run() {
+                                        updateVariantCacheEntry(backendResponse, responseDate, matchingVariant);
+                                    }
 
+                                });
+                            }
+                        }
+                    }
+                }
+                callbackRef.set(callback);
+                return callback.handleResponse(backendResponse, entityDetails);
+            }
+
+            @Override
+            public void completed() {
+                final AsyncExecCallback callback = callbackRef.getAndSet(null);
+                if (callback != null) {
+                    callback.completed();
                 } else {
                     asyncExecCallback.completed();
                 }
@@ -897,7 +981,12 @@ public class AsyncCachingExec extends CachingExecBase implements AsyncExecChainH
 
             @Override
             public void failed(final Exception cause) {
-                asyncExecCallback.failed(cause);
+                final AsyncExecCallback callback = callbackRef.getAndSet(null);
+                if (callback != null) {
+                    callback.failed(cause);
+                } else {
+                    asyncExecCallback.failed(cause);
+                }
             }
 
         });

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/c607197f/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 57de39c..724fa8a 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
@@ -278,11 +278,10 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler {
             final ExecChain.Scope scope,
             final ExecChain chain,
             final HttpCacheEntry cacheEntry) throws IOException, HttpException {
-
+        Date requestDate = getCurrentDate();
         final ClassicHttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(
                 scope.originalRequest, cacheEntry);
 
-        Date requestDate = getCurrentDate();
         ClassicHttpResponse backendResponse = chain.proceed(conditionalRequest, scope);
         try {
             Date responseDate = getCurrentDate();