From c78032d638b19d0a44341e06561050401cc53aba Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Mon, 1 Jan 2018 18:31:23 +0100 Subject: [PATCH] HTTPCLIENT-1395: added config parameter to skip an extra cache entry freshness check upon cache update in case of a cache miss --- .../http/impl/cache/AsyncCachingExec.java | 119 ++++++++++-------- .../client5/http/impl/cache/CacheConfig.java | 41 ++++-- .../client5/http/impl/cache/CachingExec.java | 21 ++-- 3 files changed, 107 insertions(+), 74 deletions(-) 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 c812b12bd..59d6e07bb 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 @@ -486,72 +486,81 @@ public class AsyncCachingExec extends CachingExecBase implements AsyncExecChainH } } - @Override - public void completed() { + void triggerNewCacheEntryResponse(final HttpResponse backendResponse, final Date responseDate, final ByteArrayBuffer buffer) { 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() { + future.setDependency(responseCache.createCacheEntry( + target, + request, + backendResponse, + buffer, + requestDate, + responseDate, + new FutureCallback() { - @Override - public void completed(final HttpCacheEntry existingEntry) { - final HttpResponse backendResponse = cachingDataConsumer.backendResponse; - if (DateUtils.isAfter(existingEntry, backendResponse, HttpHeaders.DATE)) { + @Override + public void completed(final HttpCacheEntry newEntry) { + log.debug("Backend response successfully cached"); try { - final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, existingEntry); + final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, newEntry); 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() { - - @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 failed(final Exception ex) { + asyncExecCallback.failed(ex); + } - @Override - public void cancelled() { - asyncExecCallback.failed(new InterruptedIOException()); - } + @Override + public void cancelled() { + asyncExecCallback.failed(new InterruptedIOException()); + } - })); + })); + + } + + @Override + public void completed() { + final CachingAsyncDataConsumer cachingDataConsumer = cachingConsumerRef.getAndSet(null); + if (cachingDataConsumer != null && !cachingDataConsumer.writtenThrough.get()) { + final ByteArrayBuffer buffer = cachingDataConsumer.bufferRef.getAndSet(null); + final HttpResponse backendResponse = cachingDataConsumer.backendResponse; + if (cacheConfig.isFreshnessCheckEnabled()) { + final ComplexFuture future = scope.future; + future.setDependency(responseCache.getCacheEntry(target, request, new FutureCallback() { + + @Override + public void completed(final HttpCacheEntry existingEntry) { + if (DateUtils.isAfter(existingEntry, backendResponse, HttpHeaders.DATE)) { + log.debug("Backend already contains fresher cache entry"); + try { + final SimpleHttpResponse cacheResponse = responseGenerator.generateResponse(request, existingEntry); + triggerResponse(cacheResponse, scope, asyncExecCallback); + } catch (final ResourceIOException ex) { + asyncExecCallback.failed(ex); + } + } else { + triggerNewCacheEntryResponse(backendResponse, responseDate, buffer); + } + } + + @Override + public void failed(final Exception cause) { + asyncExecCallback.failed(cause); + } + + @Override + public void cancelled() { + asyncExecCallback.failed(new InterruptedIOException()); + } + + })); + } else { + triggerNewCacheEntryResponse(backendResponse, responseDate, buffer); + } } else { asyncExecCallback.completed(); } diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheConfig.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheConfig.java index 5994aa519..d63e62315 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheConfig.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheConfig.java @@ -168,7 +168,8 @@ public class CacheConfig implements Cloneable { private final boolean heuristicCachingEnabled; private final float heuristicCoefficient; private final long heuristicDefaultLifetime; - private final boolean isSharedCache; + private final boolean sharedCache; + private final boolean freshnessCheckEnabled; private final int asynchronousWorkersMax; private final int asynchronousWorkersCore; private final int asynchronousWorkerIdleLifetimeSecs; @@ -184,7 +185,8 @@ public class CacheConfig implements Cloneable { final boolean heuristicCachingEnabled, final float heuristicCoefficient, final long heuristicDefaultLifetime, - final boolean isSharedCache, + final boolean sharedCache, + final boolean freshnessCheckEnabled, final int asynchronousWorkersMax, final int asynchronousWorkersCore, final int asynchronousWorkerIdleLifetimeSecs, @@ -199,7 +201,8 @@ public class CacheConfig implements Cloneable { this.heuristicCachingEnabled = heuristicCachingEnabled; this.heuristicCoefficient = heuristicCoefficient; this.heuristicDefaultLifetime = heuristicDefaultLifetime; - this.isSharedCache = isSharedCache; + this.sharedCache = sharedCache; + this.freshnessCheckEnabled = freshnessCheckEnabled; this.asynchronousWorkersMax = asynchronousWorkersMax; this.asynchronousWorkersCore = asynchronousWorkersCore; this.asynchronousWorkerIdleLifetimeSecs = asynchronousWorkerIdleLifetimeSecs; @@ -285,7 +288,17 @@ public class CacheConfig implements Cloneable { * shared (private) cache */ public boolean isSharedCache() { - return isSharedCache; + return sharedCache; + } + + /** + * Returns whether the cache will perform an extra cache entry freshness check + * upon cache update in case of a cache miss + * + * @since 5.0 + */ + public boolean isFreshnessCheckEnabled() { + return freshnessCheckEnabled; } /** @@ -359,7 +372,8 @@ public class CacheConfig implements Cloneable { private boolean heuristicCachingEnabled; private float heuristicCoefficient; private long heuristicDefaultLifetime; - private boolean isSharedCache; + private boolean sharedCache; + private boolean freshnessCheckEnabled; private int asynchronousWorkersMax; private int asynchronousWorkersCore; private int asynchronousWorkerIdleLifetimeSecs; @@ -372,10 +386,11 @@ public class CacheConfig implements Cloneable { this.maxUpdateRetries = DEFAULT_MAX_UPDATE_RETRIES; this.allow303Caching = DEFAULT_303_CACHING_ENABLED; this.weakETagOnPutDeleteAllowed = DEFAULT_WEAK_ETAG_ON_PUTDELETE_ALLOWED; - this.heuristicCachingEnabled = false; + this.heuristicCachingEnabled = DEFAULT_HEURISTIC_CACHING_ENABLED; this.heuristicCoefficient = DEFAULT_HEURISTIC_COEFFICIENT; this.heuristicDefaultLifetime = DEFAULT_HEURISTIC_LIFETIME; - this.isSharedCache = true; + this.sharedCache = true; + this.freshnessCheckEnabled = true; this.asynchronousWorkersMax = DEFAULT_ASYNCHRONOUS_WORKERS_MAX; this.asynchronousWorkersCore = DEFAULT_ASYNCHRONOUS_WORKERS_CORE; this.asynchronousWorkerIdleLifetimeSecs = DEFAULT_ASYNCHRONOUS_WORKER_IDLE_LIFETIME_SECS; @@ -468,12 +483,12 @@ public class CacheConfig implements Cloneable { /** * Sets whether the cache should behave as a shared cache or not. - * @param isSharedCache true to behave as a shared cache, false to + * @param sharedCache true to behave as a shared cache, false to * behave as a non-shared (private) cache. To have the cache * behave like a browser cache, you want to set this to {@code false}. */ - public Builder setSharedCache(final boolean isSharedCache) { - this.isSharedCache = isSharedCache; + public Builder setSharedCache(final boolean sharedCache) { + this.sharedCache = sharedCache; return this; } @@ -542,7 +557,8 @@ public class CacheConfig implements Cloneable { heuristicCachingEnabled, heuristicCoefficient, heuristicDefaultLifetime, - isSharedCache, + sharedCache, + freshnessCheckEnabled, asynchronousWorkersMax, asynchronousWorkersCore, asynchronousWorkerIdleLifetimeSecs, @@ -563,7 +579,8 @@ public class CacheConfig implements Cloneable { .append(", heuristicCachingEnabled=").append(this.heuristicCachingEnabled) .append(", heuristicCoefficient=").append(this.heuristicCoefficient) .append(", heuristicDefaultLifetime=").append(this.heuristicDefaultLifetime) - .append(", isSharedCache=").append(this.isSharedCache) + .append(", sharedCache=").append(this.sharedCache) + .append(", freshnessCheckEnabled=").append(this.freshnessCheckEnabled) .append(", asynchronousWorkersMax=").append(this.asynchronousWorkersMax) .append(", asynchronousWorkersCore=").append(this.asynchronousWorkersCore) .append(", asynchronousWorkerIdleLifetimeSecs=").append(this.asynchronousWorkerIdleLifetimeSecs) 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 7fb7e426b..3b2491d8f 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 @@ -380,15 +380,22 @@ public class CachingExec extends CachingExecBase implements ExecChainHandler { buf = null; } backendResponse.close(); - final HttpCacheEntry existingEntry = responseCache.getCacheEntry(target, request); - if (DateUtils.isAfter(existingEntry, backendResponse, HttpHeaders.DATE)) { - return convert(responseGenerator.generateResponse(request, existingEntry)); + + final HttpCacheEntry cacheEntry; + if (cacheConfig.isFreshnessCheckEnabled()) { + final HttpCacheEntry existingEntry = responseCache.getCacheEntry(target, request); + if (DateUtils.isAfter(existingEntry, backendResponse, HttpHeaders.DATE)) { + log.debug("Backend already contains fresher cache entry"); + cacheEntry = existingEntry; + } else { + cacheEntry = responseCache.createCacheEntry(target, request, backendResponse, buf, requestSent, responseReceived); + log.debug("Backend response successfully cached"); + } } else { - final HttpCacheEntry newEntry = responseCache.createCacheEntry( - target, request, backendResponse, buf, requestSent, responseReceived); - log.debug("Backend response successfully cached"); - return convert(responseGenerator.generateResponse(request, newEntry)); + cacheEntry = responseCache.createCacheEntry(target, request, backendResponse, buf, requestSent, responseReceived); + log.debug("Backend response successfully cached (freshness check skipped)"); } + return convert(responseGenerator.generateResponse(request, cacheEntry)); } private ClassicHttpResponse handleCacheMiss(