From 175cfb5cd3fbd833f7b2159379a8b0a30c8de0a7 Mon Sep 17 00:00:00 2001 From: gregw Date: Mon, 13 Jan 2025 09:16:42 +1100 Subject: [PATCH] Partial fix for #12681 caching content This is a partial fix for #12681. It does not fix the actual release race on a single content, but by restricting the shrinking to a single thread, make the race less likely to occur. There is a more comprehensive fix in #12678 for 12.1, but the cache has changed too much for a straight forward back port. --- .../content/CachingHttpContentFactory.java | 62 +++++++++++-------- 1 file changed, 37 insertions(+), 25 deletions(-) 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(); } }