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

Issue #4861 - reduce garbage created by the async request attributes #4867

Merged
merged 4 commits into from
May 14, 2020

Conversation

lachlan-roberts
Copy link
Contributor

Issue #4861

Reduce garbage produced by creating the ConcurrentHashMap of AttributesMap when going async. In Request we now use ServletAttributes as the intial Attributes instance which can still be subsequently wrapped by forwards and includes, this will store the async mapping attributes as local variables instead of using the AttributesMap.

The HttpServletMapping needs to be added back to AsyncAttributes when this is merged to 10.

@lachlan-roberts lachlan-roberts requested review from gregw and lorban May 12, 2020 08:41
Comment on lines 26 to 33
public class ServletAttributes implements Attributes
{
private final Attributes _attributes;

public ServletAttributes()
{
_attributes = new AsyncAttributes(new AttributesMap());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like all the dereferences here. We end up with ServletAttributes->AsyncAttributes->AttributesMap, so 3 object and 3 dereferences.
I think it would be much better to do (also note the name change):

Suggested change
public class ServletAttributes implements Attributes
{
private final Attributes _attributes;
public ServletAttributes()
{
_attributes = new AsyncAttributes(new AttributesMap());
}
public class AsyncAttributesMap extends AttributesMap
{
public AsyncAttributesMap()
{
}

The override the methods as follows:

    @Overridepublic Object getAttribute(String name)
    {
        // do the AsyncAttribute handling here
        // else
        return super.getAttribute(name);
    }

Then you don't need AsyncAttributes class.

@@ -199,7 +200,7 @@ public static Request getBaseRequest(ServletRequest request)
private boolean _handled = false;
private boolean _contentParamsExtracted;
private boolean _requestedSessionIdFromCookie = false;
private Attributes _attributes;
private Attributes _attributes = new ServletAttributes();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah we can't make this final because of possible wrappers that don't unwrap.
So go back to lazy creation of this field...... OR have 2 fields:

Suggested change
private Attributes _attributes = new ServletAttributes();
private final AsyncAttributesMap _asyncAttributes = new AsyncAttributesMap();
private Attributes _attributes = _asyncAttributes;

This way, async could as the request directly for the _asyncAttributes and set the fields on it.... but we'd still create an extra class with 6 fields even if we never use attributes.... hmmmm @lorban your thoughts on which is best?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave the _attributes field non-final, this is the simplest way to go.

Any sort of cleanup seem to require a non-negligible amount of refactoring, so I'd keep #4861 open until the code is cleaned up. Regarding perf, this small change has 99% of the benefits of a larger refactoring.

Copy link
Contributor

@gregw gregw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm approving, but there are a few niggles that I think you should change... but will not need rereview.

if (ServletAttributes.class.equals(_attributes.getClass()))
_attributes.clearAttributes();
else
_attributes = new ServletAttributes();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just reset to null

Comment on lines 39 to 42
if (_asyncAttributes == null)
_attributes.removeAttribute(name);
else
_asyncAttributes.removeAttribute(name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is clearer as:

Suggested change
if (_asyncAttributes == null)
_attributes.removeAttribute(name);
else
_asyncAttributes.removeAttribute(name);
(_asyncAttributes == null ? _asyncAttributes : _attributes).removeAttribute(name);

or maybe even with a private method:

    attributes().removeAttribute(name);

same for other methods below.

Signed-off-by: Lachlan Roberts <[email protected]>
@joakime
Copy link
Contributor

joakime commented May 13, 2020

Build failure is a StackOverflowError

2020-05-13 14:31:50.243:WARN:oejut.QueuedThreadPool:qtp148783622-491: 
java.lang.StackOverflowError
	at org.eclipse.jetty.server.ServletAttributes.getAttribute(ServletAttributes.java)
	at org.eclipse.jetty.util.Attributes$Wrapper.getAttribute(Attributes.java:84)
	at org.eclipse.jetty.server.AsyncAttributes.getAttribute(AsyncAttributes.java:63)
	at org.eclipse.jetty.server.ServletAttributes.getAttribute(ServletAttributes.java:56)
	at org.eclipse.jetty.util.Attributes$Wrapper.getAttribute(Attributes.java:84)
	at org.eclipse.jetty.server.AsyncAttributes.getAttribute(AsyncAttributes.java:63)
	at org.eclipse.jetty.server.ServletAttributes.getAttribute(ServletAttributes.java:56)
	at org.eclipse.jetty.util.Attributes$Wrapper.getAttribute(Attributes.java:84)
	at org.eclipse.jetty.server.AsyncAttributes.getAttribute(AsyncAttributes.java:63)
...

@joakime
Copy link
Contributor

joakime commented May 13, 2020

Test failure in org.eclipse.jetty.client.ClientConnectionCloseTest

2020-05-13 14:29:49.413:WARN:oejs.HttpChannel:server-66551: /
java.lang.ClassCastException: class org.eclipse.jetty.server.SecureRequestCustomizer$SslAttributes cannot be cast to class org.eclipse.jetty.server.ServletAttributes (org.eclipse.jetty.server.SecureRequestCustomizer$SslAttributes and org.eclipse.jetty.server.ServletAttributes are in unnamed module of loader 'app')
	at org.eclipse.jetty.server.Request.setAsyncAttributes(Request.java:2014)
	at org.eclipse.jetty.server.AsyncContextEvent.<init>(AsyncContextEvent.java:49)
	at org.eclipse.jetty.server.Request.startAsync(Request.java:2278)
	at org.eclipse.jetty.client.ClientConnectionCloseTest$2.handle(ClientConnectionCloseTest.java:117)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
	at org.eclipse.jetty.server.Server.handle(Server.java:501)
	at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:383)
	at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:556)
	at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:375)
	at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:273)
	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
	at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.onFillable(SslConnection.java:543)
	at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:398)
	at org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:161)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
	at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:336)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:313)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:171)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:129)
	at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:375)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:806)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:938)
	at java.base/java.lang.Thread.run(Thread.java:834)

@joakime
Copy link
Contributor

joakime commented May 13, 2020

A slightly different one of the last failure.
In test org.eclipse.jetty.client.HttpClientTest

2020-05-13 14:25:58.343:WARN:oejs.HttpChannel:server-379: /
java.lang.ClassCastException: org.eclipse.jetty.server.SecureRequestCustomizer$SslAttributes cannot be cast to org.eclipse.jetty.server.ServletAttributes
	at org.eclipse.jetty.server.Request.setAsyncAttributes(Request.java:2014)
	at org.eclipse.jetty.server.AsyncContextEvent.<init>(AsyncContextEvent.java:49)
	at org.eclipse.jetty.server.Request.startAsync(Request.java:2278)
	at org.eclipse.jetty.client.HttpClientTest$32.handle(HttpClientTest.java:1335)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
	at org.eclipse.jetty.server.Server.handle(Server.java:501)
	at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:383)
	at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:556)
	at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:375)
	at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:273)
	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
	at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.onFillable(SslConnection.java:543)
	at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:398)
	at org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:161)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
	at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:336)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:313)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:171)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:129)
	at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:375)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:806)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:938)
	at java.lang.Thread.run(Thread.java:748)

@lachlan-roberts lachlan-roberts merged commit 356b872 into jetty-9.4.x May 14, 2020
@lachlan-roberts lachlan-roberts deleted the jetty-9.4.x-4861-Attributes branch May 14, 2020 02:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants