Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce header cache memory usage on non persistent requests #6494

Merged
merged 3 commits into from Jul 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
124 changes: 102 additions & 22 deletions jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
Expand Up @@ -15,6 +15,7 @@

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
Expand Down Expand Up @@ -217,6 +218,7 @@ public enum State
private final int _maxHeaderBytes;
private final HttpCompliance _complianceMode;
private final Utf8StringBuilder _uri = new Utf8StringBuilder(INITIAL_URI_LENGTH);
private final FieldCache _fieldCache = new FieldCache();
private HttpField _field;
private HttpHeader _header;
private String _headerString;
Expand All @@ -241,11 +243,8 @@ public enum State
private boolean _headResponse;
private boolean _cr;
private ByteBuffer _contentChunk;
private Index.Mutable<HttpField> _fieldCache;
private int _length;
private final StringBuilder _string = new StringBuilder();
private int _headerCacheSize = 1024;
private boolean _headerCacheCaseSensitive;

private static HttpCompliance compliance()
{
Expand Down Expand Up @@ -304,22 +303,22 @@ public HttpHandler getHandler()

public int getHeaderCacheSize()
{
return _headerCacheSize;
return _fieldCache.getCapacity();
}

public void setHeaderCacheSize(int headerCacheSize)
{
_headerCacheSize = headerCacheSize;
_fieldCache.setCapacity(headerCacheSize);
}

public boolean isHeaderCacheCaseSensitive()
{
return _headerCacheCaseSensitive;
return _fieldCache.isCaseSensitive();
}

public void setHeaderCacheCaseSensitive(boolean headerCacheCaseSensitive)
{
_headerCacheCaseSensitive = headerCacheCaseSensitive;
_fieldCache.setCaseSensitive(headerCacheCaseSensitive);
}

protected void checkViolation(Violation violation) throws BadMessageException
Expand Down Expand Up @@ -746,6 +745,7 @@ private boolean parseLine(ByteBuffer buffer)
break;

case LF:
_fieldCache.prepare();
setState(State.HEADER);
_responseHandler.startResponse(_version, _responseStatus, null);
break;
Expand Down Expand Up @@ -851,6 +851,7 @@ else if (n == HttpTokens.LINE_FEED)
case LF:
if (_responseHandler != null)
{
_fieldCache.prepare();
setState(State.HEADER);
_responseHandler.startResponse(_version, _responseStatus, null);
}
Expand Down Expand Up @@ -881,7 +882,7 @@ else if (n == HttpTokens.LINE_FEED)
_version = HttpVersion.CACHE.get(takeString());
}
checkVersion();

_fieldCache.prepare();
setState(State.HEADER);

_requestHandler.startRequest(_methodString, _uri.toString(), _version);
Expand All @@ -905,6 +906,7 @@ else if (n == HttpTokens.LINE_FEED)
{
case LF:
String reason = takeString();
_fieldCache.prepare();
setState(State.HEADER);
_responseHandler.startResponse(_version, _responseStatus, reason);
continue;
Expand Down Expand Up @@ -1026,7 +1028,7 @@ else if (_endOfContent == EndOfContent.CHUNKED_CONTENT)
_field = new HostPortHttpField(_header,
CASE_SENSITIVE_FIELD_NAME.isAllowedBy(_complianceMode) ? _headerString : _header.asString(),
_valueString);
addToFieldCache = true;
addToFieldCache = _fieldCache.isEnabled();
}
break;

Expand All @@ -1035,7 +1037,7 @@ else if (_endOfContent == EndOfContent.CHUNKED_CONTENT)
if (_field == null)
_field = new HttpField(_header, caseInsensitiveHeader(_headerString, _header.asString()), _valueString);
if (getHeaderCacheSize() > 0 && _field.contains(HttpHeaderValue.CLOSE.asString()))
_fieldCache = NO_CACHE;
_fieldCache.setCapacity(-1);
break;

case AUTHORIZATION:
Expand All @@ -1046,26 +1048,20 @@ else if (_endOfContent == EndOfContent.CHUNKED_CONTENT)
case COOKIE:
case CACHE_CONTROL:
case USER_AGENT:
addToFieldCache = _field == null;
addToFieldCache = _field == null && _fieldCache.cacheable(_header, _valueString);
break;

default:
break;
}

// Cache field?
if (addToFieldCache && _header != null && _valueString != null)
if (addToFieldCache)
{
if (_fieldCache == null)
_fieldCache = Index.buildCaseSensitiveMutableVisibleAsciiAlphabet(getHeaderCacheSize());

if (_field == null)
_field = new HttpField(_header, caseInsensitiveHeader(_headerString, _header.asString()), _valueString);
if (_field.getValue().length() < getHeaderCacheSize() && !_fieldCache.put(_field))
{
_fieldCache.clear();
_fieldCache.put(_field);
}

_fieldCache.add(_field);
}
}
_handler.parsedHeader(_field != null ? _field : new HttpField(_header, _headerString, _valueString));
Expand Down Expand Up @@ -1244,7 +1240,7 @@ else if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
if (buffer.hasRemaining())
{
// Try a look ahead for the known header name and value.
HttpField cachedField = _fieldCache == null ? null : _fieldCache.getBest(buffer, -1, buffer.remaining());
HttpField cachedField = _fieldCache.getBest(buffer, -1, buffer.remaining());
if (cachedField == null)
cachedField = CACHE.getBest(buffer, -1, buffer.remaining());

Expand Down Expand Up @@ -1911,7 +1907,8 @@ protected void setState(FieldState state)

public Index<HttpField> getFieldCache()
{
return _fieldCache;
_fieldCache.prepare();
return _fieldCache.getCache();
}

@Override
Expand Down Expand Up @@ -2007,4 +2004,87 @@ private IllegalCharacterException(State state, HttpTokens.Token token, ByteBuffe
LOG.debug(String.format("Illegal character %s in state=%s for buffer %s", token, state, BufferUtil.toDetailString(buffer)));
}
}

private static class FieldCache
{
private int _size = 1024;
private Index.Mutable<HttpField> _cache;
private List<HttpField> _cacheableFields;
private boolean _caseSensitive;

public int getCapacity()
{
return _size;
}

public void setCapacity(int size)
{
_size = size;
if (_size <= 0)
_cache = NO_CACHE;
else
_cache = null;
}

public boolean isCaseSensitive()
{
return _caseSensitive;
}

public void setCaseSensitive(boolean caseSensitive)
{
_caseSensitive = caseSensitive;
}

public boolean isEnabled()
{
return _cache != NO_CACHE;
}

public Index<HttpField> getCache()
{
return _cache;
}

public HttpField getBest(ByteBuffer buffer, int i, int remaining)
{
Index.Mutable<HttpField> cache = _cache;
return cache == null ? null : _cache.getBest(buffer, i, remaining);
}

public void add(HttpField field)
{
if (_cache == null)
{
if (_cacheableFields == null)
_cacheableFields = new ArrayList<>();
_cacheableFields.add(field);
}
else if (!_cache.put(field))
{
_cache.clear();
_cache.put(field);
}
}

public boolean cacheable(HttpHeader header, String valueString)
{
return isEnabled() && header != null && valueString.length() <= _size;
}

private void prepare()
{
if (_cache == null && _cacheableFields != null)
{
_cache = Index.buildMutableVisibleAsciiAlphabet(_caseSensitive, _size);
for (HttpField f : _cacheableFields)
{
if (!_cache.put(f))
break;
}
_cacheableFields.clear();
gregw marked this conversation as resolved.
Show resolved Hide resolved
_cacheableFields = null;
}
}
}
}
8 changes: 4 additions & 4 deletions jetty-util/src/main/java/org/eclipse/jetty/util/Index.java
Expand Up @@ -259,13 +259,13 @@ public Mutable<V> build()
* @param <V> The type of the index
* @return A case sensitive mutable Index tacking visible ASCII alphabet to a max capacity.
*/
static <V> Mutable<V> buildCaseSensitiveMutableVisibleAsciiAlphabet(int maxCapacity)
static <V> Mutable<V> buildMutableVisibleAsciiAlphabet(boolean caseSensitive, int maxCapacity)
{
if (maxCapacity < 0 || maxCapacity > ArrayTrie.MAX_CAPACITY)
return new TreeTrie<>(true);
return new TreeTrie<>(caseSensitive);
if (maxCapacity == 0)
return EmptyTrie.instance(true);
return new ArrayTrie<>(true, maxCapacity);
return EmptyTrie.instance(caseSensitive);
return new ArrayTrie<>(caseSensitive, maxCapacity);
}

static <V> Index<V> empty(boolean caseSensitive)
Expand Down