fix stampede creating CachingHttpContent & remove usage of getPreCompressedContentFormats

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2022-10-12 16:11:33 +11:00
parent a7383ea4de
commit 6a6681a2df
5 changed files with 46 additions and 71 deletions

View File

@ -16,12 +16,12 @@ package org.eclipse.jetty.http;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.util.NanoTime;
@ -171,7 +171,7 @@ public class CachingContentFactory implements HttpContent.ContentFactory
return false;
if (httpContent instanceof MappedFileContentFactory.FileMappedContent)
return true;
return ((len <= _maxCachedFileSize) && (len + getCachedSize() <= _maxCacheSize));
return (len <= _maxCachedFileSize);
}
@Override
@ -186,21 +186,21 @@ public class CachingContentFactory implements HttpContent.ContentFactory
removeFromCache(cachingHttpContent);
}
// TODO: record cache misses.
HttpContent httpContent = _authority.getContent(path);
if (isCacheable(httpContent))
{
cachingHttpContent = new CachingHttpContent(path, httpContent);
httpContent = _cache.putIfAbsent(path, cachingHttpContent);
if (httpContent != null)
AtomicBoolean wasAdded = new AtomicBoolean(false);
cachingHttpContent = _cache.computeIfAbsent(path, p ->
{
cachingHttpContent.release();
}
else
{
httpContent = cachingHttpContent;
_cachedSize.addAndGet(cachingHttpContent.getContentLengthValue());
wasAdded.set(true);
_cachedSize.addAndGet(httpContent.getContentLengthValue());
return new CachingHttpContent(p, httpContent);
});
if (wasAdded.get())
shrinkCache();
}
return cachingHttpContent;
}
return httpContent;
}
@ -212,15 +212,14 @@ public class CachingContentFactory implements HttpContent.ContentFactory
private final String _cacheKey;
private final HttpField _etagField;
private final long _contentLengthValue;
private final Set<CompressedContentFormat> _precompressedContents;
private final AtomicLong _lastAccessed = new AtomicLong();
private CachingHttpContent(String key, HttpContent httpContent) throws IOException
private CachingHttpContent(String key, HttpContent httpContent)
{
this(key, httpContent, httpContent.getETagValue());
}
private CachingHttpContent(String key, HttpContent httpContent, String etagValue) throws IOException
private CachingHttpContent(String key, HttpContent httpContent, String etagValue)
{
super(httpContent);
@ -239,6 +238,7 @@ public class CachingContentFactory implements HttpContent.ContentFactory
if (resourceSize < 0)
throw new IllegalArgumentException("Resource with negative size: " + _delegate.getResource());
// TODO: do all the following lazily and asynchronously.
HttpField etagField = _delegate.getETag();
if (StringUtil.isNotBlank(etagValue))
{
@ -252,7 +252,6 @@ public class CachingContentFactory implements HttpContent.ContentFactory
_cacheKey = key;
_lastModifiedValue = _delegate.getResource().lastModified();
_lastAccessed.set(NanoTime.now());
_precompressedContents = _delegate.getPreCompressedContentFormats();
}
@Override
@ -307,11 +306,5 @@ public class CachingContentFactory implements HttpContent.ContentFactory
return null;
return etag.getValue();
}
@Override
public Set<CompressedContentFormat> getPreCompressedContentFormats()
{
return _precompressedContents;
}
}
}

View File

@ -155,7 +155,7 @@ public class ResourceHttpContent implements HttpContent
@Override
public String toString()
{
return String.format("%s@%x{r=%s,ct=%s,c=%b}", this.getClass().getSimpleName(), hashCode(), _resource, _contentType, _precompressedContents != null);
return String.format("%s@%x{r=%s,ct=%s}", this.getClass().getSimpleName(), hashCode(), _resource, _contentType);
}
@Override

View File

@ -23,10 +23,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jetty.http.ByteRange;
@ -109,33 +107,32 @@ public class ResourceService
HttpContent content = _contentFactory.getContent(path == null ? "" : path);
if (content != null)
{
Set<CompressedContentFormat> preCompressedContentFormats = content.getPreCompressedContentFormats();
if (!preCompressedContentFormats.isEmpty())
{
HashSet<CompressedContentFormat> availableFormats = new HashSet<>(preCompressedContentFormats);
availableFormats.retainAll(_precompressedFormats);
for (String encoding : getPreferredEncodingOrder(request))
{
CompressedContentFormat contentFormat = isEncodingAvailable(encoding, availableFormats);
if (contentFormat == null)
continue;
HttpContent preCompressedContent = _contentFactory.getContent(path + contentFormat.getExtension());
if (preCompressedContent == null)
continue;
AliasCheck aliasCheck = ContextHandler.getContextHandler(request);
if (aliasCheck != null && !aliasCheck.checkAlias(path, content.getResource()))
return null;
return new PrecompressedHttpContent(content, preCompressedContent, contentFormat);
}
}
AliasCheck aliasCheck = ContextHandler.getContextHandler(request);
if (aliasCheck != null && !aliasCheck.checkAlias(path, content.getResource()))
return null;
if (!_precompressedFormats.isEmpty())
{
List<String> preferredEncodingOrder = getPreferredEncodingOrder(request);
if (!preferredEncodingOrder.isEmpty())
{
for (String encoding : preferredEncodingOrder)
{
CompressedContentFormat contentFormat = isEncodingAvailable(encoding, _precompressedFormats);
if (contentFormat == null)
continue;
HttpContent preCompressedContent = _contentFactory.getContent(path + contentFormat.getExtension());
if (preCompressedContent == null)
continue;
if (aliasCheck != null && !aliasCheck.checkAlias(path, preCompressedContent.getResource()))
return null;
return new PrecompressedHttpContent(content, preCompressedContent, contentFormat);
}
}
}
}
else
{

View File

@ -1526,6 +1526,9 @@ public class ResourceHandlerTest
while (Files.getLastModifiedTime(tempPath).equals(before));
long newExpectedSize = Files.size(tempPath);
// The cached Resource will only go to fileSystem for expiryTime once per second.
Thread.sleep(1100);
for (int i = 0; i < 10; i++)
{
HttpTester.Response response = HttpTester.parseResponse(
@ -1941,6 +1944,9 @@ public class ResourceHandlerTest
}
while (Files.getLastModifiedTime(testFile).equals(before));
// The cached Resource will only go to fileSystem for expiryTime once per second.
Thread.sleep(1100);
response = HttpTester.parseResponse(
_local.getResponse("""
GET /context/test-etag-file.txt HTTP/1.1\r

View File

@ -15,8 +15,6 @@ package org.eclipse.jetty.ee9.nested;
import java.io.IOException;
import java.nio.file.InvalidPathException;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jetty.http.CompressedContentFormat;
import org.eclipse.jetty.http.HttpContent;
@ -69,7 +67,6 @@ public class ResourceContentFactory implements ContentFactory
}
private HttpContent load(String pathInContext, Resource resource)
throws IOException
{
if (resource == null || !resource.exists())
return null;
@ -77,25 +74,7 @@ public class ResourceContentFactory implements ContentFactory
if (resource.isDirectory())
return new ResourceHttpContent(resource, _mimeTypes.getMimeByExtension(resource.toString()));
// Look for a precompressed resource or content
String mt = _mimeTypes.getMimeByExtension(pathInContext);
if (_precompressedFormats.length > 0)
{
// Is there a compressed resource?
Map<CompressedContentFormat, HttpContent> compressedContents = new HashMap<>(_precompressedFormats.length);
for (CompressedContentFormat format : _precompressedFormats)
{
String compressedPathInContext = pathInContext + format.getExtension();
Resource compressedResource = _factory.newResource(compressedPathInContext);
if (compressedResource != null && compressedResource.exists() && ResourceContentFactory.newerThanOrEqual(compressedResource, resource) &&
compressedResource.length() < resource.length())
compressedContents.put(format,
new ResourceHttpContent(compressedResource, _mimeTypes.getMimeByExtension(compressedPathInContext)));
}
if (!compressedContents.isEmpty())
return new ResourceHttpContent(resource, mt, compressedContents);
}
return new ResourceHttpContent(resource, mt);
return new ResourceHttpContent(resource, _mimeTypes.getMimeByExtension(pathInContext));
}
/**