diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/CachingHttpContentFactory.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/CachingHttpContentFactory.java index 15a5402e62ed..896de6798e2b 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/CachingHttpContentFactory.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/content/CachingHttpContentFactory.java @@ -68,6 +68,7 @@ public class CachingHttpContentFactory implements HttpContent.Factory private final HttpContent.Factory _authority; private final ConcurrentHashMap _cache = new ConcurrentHashMap<>(); private final AtomicLong _cachedSize = new AtomicLong(); + private final AtomicBoolean _shrinking = new AtomicBoolean(); private final ByteBufferPool _bufferPool; private int _maxCachedFileSize = DEFAULT_MAX_CACHED_FILE_SIZE; private int _maxCachedFiles = DEFAULT_MAX_CACHED_FILES; @@ -148,35 +149,46 @@ public void setUseDirectByteBuffers(boolean useDirectByteBuffers) private void shrinkCache() { - // While we need to shrink - int numCacheEntries = _cache.size(); - while (numCacheEntries > 0 && (numCacheEntries > _maxCachedFiles || _cachedSize.get() > _maxCacheSize)) + // Only 1 thread shrinking at once + if (_shrinking.compareAndSet(false, true)) { - // Scan the entire cache and generate an ordered list by last accessed time. - SortedSet sorted = new TreeSet<>((c1, c2) -> + try { - long delta = NanoTime.elapsed(c2.getLastAccessedNanos(), c1.getLastAccessedNanos()); - if (delta != 0) - return delta < 0 ? -1 : 1; - - delta = c1.getContentLengthValue() - c2.getContentLengthValue(); - if (delta != 0) - return delta < 0 ? -1 : 1; - - return c1.getKey().compareTo(c2.getKey()); - }); - sorted.addAll(_cache.values()); - - // TODO: Can we remove the buffers from the content before evicting. - // Invalidate least recently used first - for (CachingHttpContent content : sorted) + // While we need to shrink + int numCacheEntries = _cache.size(); + while (numCacheEntries > 0 && (numCacheEntries > _maxCachedFiles || _cachedSize.get() > _maxCacheSize)) + { + // Scan the entire cache and generate an ordered list by last accessed time. + SortedSet sorted = new TreeSet<>((c1, c2) -> + { + long delta = NanoTime.elapsed(c2.getLastAccessedNanos(), c1.getLastAccessedNanos()); + if (delta != 0) + return delta < 0 ? -1 : 1; + + delta = c1.getContentLengthValue() - c2.getContentLengthValue(); + if (delta != 0) + return delta < 0 ? -1 : 1; + + return c1.getKey().compareTo(c2.getKey()); + }); + sorted.addAll(_cache.values()); + + // TODO: Can we remove the buffers from the content before evicting. + // Invalidate least recently used first + for (CachingHttpContent content : sorted) + { + if (_cache.size() <= _maxCachedFiles && _cachedSize.get() <= _maxCacheSize) + break; + removeFromCache(content); + } + + numCacheEntries = _cache.size(); + } + } + finally { - if (_cache.size() <= _maxCachedFiles && _cachedSize.get() <= _maxCacheSize) - break; - removeFromCache(content); + _shrinking.set(false); } - - numCacheEntries = _cache.size(); } }