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

make ByteAccumulator recyclable to fix issue #5499 #5520

Closed
wants to merge 1 commit into from

Conversation

leonchen83
Copy link
Contributor

this PR let the ByteAccumulator recyclable. after invoke ByteAccumulator.transferTo method
we can invoke ByteAccumulator.recycle method to reuse byte[] via ByteAccumulator.newByteArray method

Signed-off-by: Baoyi Chen chen.bao.yi@qq.com

@lachlan-roberts
Copy link
Contributor

@leonchen83 Thanks for the PR!

The ECA check has failed validation for some reason, take a look here for more info:
https://www.eclipse.org/jetty/documentation/current/contributing-patches.html

@leonchen83
Copy link
Contributor Author

@lachlan-roberts
I do some change just right now to fix test failure and check style, please be informed

@leonchen83 leonchen83 force-pushed the jetty-9.4.x branch 2 times, most recently from 10051a6 to 103653f Compare October 28, 2020 09:13
Copy link
Contributor

@joakime joakime left a comment

Choose a reason for hiding this comment

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

Remove the ThreadLocal.

Also, fix your ECA, otherwise we cannot merge this.

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.

@leonchen83 @lachlan-roberts I think this is more or less the right approach, but I think more can be done as there is still a fair bit of copying, plus we might end up with lots of max size buffers in the list.

A couple of ideas....

The algorithm in copyChunk could be:

  • if the byte array passed is the last one returned by newByteArray
    • If there are no chunks in the list OR there is not enough free space in last item in list
      • just add that array to the list.
      • do not let the buffer be returned again by newByteArray
    • else there is room in the last buffer in the list
      • copy the data into the free space in the last entry.
      • allow the passed buffer to be returned again by next call to newByteArray
  • else
    • throw IllegalArgumentException

The other thing that could greatly improve this class is to pass the BufferPool to it, so that rather than creation byte arrays, it creates a ByteBuffers with arrays. If the final list only has a single entry, then it can be used as a ByteBuffer rather than with a transferTo

@lachlan-roberts can you work with @leonchen83 to improve this... net necessary as per my suggestions, but do consider them.

@leonchen83
Copy link
Contributor Author

leonchen83 commented Oct 28, 2020

@joakime
hi, thanks for review code.
I think it is necessary to use ThreadLocal<ByteAccumulator> accumulator
I want to create only 1 ByteAccumulator object per thread. so we can reuse that ByteAccumulator(reuse in thread level) via recycle method
It's not related about websocket thread model.

@gregw
Copy link
Contributor

gregw commented Oct 28, 2020

@lachlan-roberts is it possible/easy to associate a ByteAccumulator with a connection?

Copy link
Contributor

@joakime joakime left a comment

Choose a reason for hiding this comment

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

Fix your ECA.
Don't make unrelated changes to this PR please.

@joakime
Copy link
Contributor

joakime commented Oct 28, 2020

@joakime
hi, thanks for review code.
I think it is necessary to use ThreadLocal<ByteAccumulator> accumulator
I want to create only 1 ByteAccumulator object per thread. so we can reuse that ByteAccumulator(reuse in thread level) via recycle method
It's not related about websocket thread model.

First, fix your ECA. (I cannot stress this enough, don't ignore this).

Test this on slow connections, with larger messages split over many frames, on busy ThreadPools and you'll start to see why we don't use ThreadLocal ourselves.

@leonchen83
Copy link
Contributor Author

@joakime
hi, thanks for review code.
I think it is necessary to use ThreadLocal<ByteAccumulator> accumulator
I want to create only 1 ByteAccumulator object per thread. so we can reuse that ByteAccumulator(reuse in thread level) via recycle method
It's not related about websocket thread model.

First, fix your ECA. (I cannot stress this enough, don't ignore this).

Test this on slow connections, with larger messages split over many frames, on busy ThreadPools and you'll start to see why we don't use ThreadLocal ourselves.

Hi I'm digging how to fix ECA, sorry but maybe need a little time.

@joakime
Copy link
Contributor

joakime commented Oct 28, 2020

@lachlan-roberts is it possible/easy to associate a ByteAccumulator with a connection?

The Extensions, like these Compress Extensions, are already 1 unique instance per connection.
Since the ByteAccumulator in this PR is only used in the Extensions, isn't that effectively the same thing as a ByteAccumulator per connection already?

@gregw
Copy link
Contributor

gregw commented Oct 28, 2020

The Extensions, like these Compress Extensions, are already 1 unique instance per connection.
Since the ByteAccumulator in this PR is only used in the Extensions, isn't that effectively the same thing as a ByteAccumulator per connection already?

Ah yes! so no need to pool the BAs in a thread local or anywhere else. Just create one in the connection and the buffers will be reused at least within that connection. A good simple saving without the risk of sharing buffers between connections.

@joakime
Copy link
Contributor

joakime commented Oct 28, 2020

The Extensions, like these Compress Extensions, are already 1 unique instance per connection.
Since the ByteAccumulator in this PR is only used in the Extensions, isn't that effectively the same thing as a ByteAccumulator per connection already?

Ah yes! so no need to pool the BAs in a thread local or anywhere else. Just create one in the connection and the buffers will be reused at least within that connection. A good simple saving without the risk of sharing buffers between connections.

And if you have 2 extensions present that both need a ByteAccumulator then the current non-ThreadLocal version is still preferred.

@leonchen83 leonchen83 force-pushed the jetty-9.4.x branch 2 times, most recently from c04a934 to a98265c Compare October 29, 2020 04:33
@leonchen83
Copy link
Contributor Author

Hi
@gregw @joakime @lachlan-roberts
I can't find a way to create ByteAccumulator in non-ThreadLocal way. could you help me to fix that code in this PR?

today I changed following code in this PR

  1. change self define ByteArray to java standard ByteBuffer
  2. optimize recycle mehtod that avoid retain too many elements.
  3. optimize CompressExtension.decompress mehtod so that avoid copy not full byte[] to chunk

@lachlan-roberts
Copy link
Contributor

@leonchen83 instead of using the ThreadLocal you can store the ByteAccumulator as a field on the CompressExtension, this way it can used and recycled for all frames on a single websocket connection.

@leonchen83
Copy link
Contributor Author

@lachlan-roberts
Hi I have done the ThreadLocal fix. please help review the code

Copy link
Contributor

@lachlan-roberts lachlan-roberts left a comment

Choose a reason for hiding this comment

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

We also have access to the ByteBufferPool from CompressExtension so this could be used in the ByteAccumulator.

I think you could pass in the bufferPool in the constructor, then the newByteBuffer method could use the bufferPool, and all the buffers should be released back to the pool when it is recycled.

@leonchen83
Copy link
Contributor Author

@lachlan-roberts
pass in the bufferPool to ByteAccumulator 's constructor. should change the declaration of constructor, that could break compatibility. you mean add another constructor to ByteAccumulator ?

@lachlan-roberts
Copy link
Contributor

@joakime would you consider ByteAccumulator public API? Do we need to worry about compatibility for this.
It might be difficult to use the ByteBufferPool and reduce the copying without at least changing some of the method signatures.

@leonchen83
Copy link
Contributor Author

@lachlan-roberts
I do some change right now. please help review

Copy link
Contributor

@lachlan-roberts lachlan-roberts left a comment

Choose a reason for hiding this comment

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

I still think there are some problems with this approach. We should discuss further with @gregw and @joakime before continuing.

System.arraycopy(buf, offset, copy, 0, length);

chunks.add(copy);
chunks.add(ByteBuffer.wrap(buf, offset, length));
Copy link
Contributor

Choose a reason for hiding this comment

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

This copyChunk(byte[] buf, int offset, int length) method doesn't really work with the ByteBufferPool. This ByteBuffer will eventually be returned to the pool but we have created it right here. I think this will give an error if it is used.

@leonchen83 leonchen83 force-pushed the jetty-9.4.x branch 2 times, most recently from 95f7c04 to 0b22a92 Compare October 29, 2020 10:55
@leonchen83
Copy link
Contributor Author

@lachlan-roberts
yes, I totally agree to further discuss this PR before continuing.

@leonchen83 leonchen83 force-pushed the jetty-9.4.x branch 2 times, most recently from dd0c63c to 64a4738 Compare October 30, 2020 04:32
@leonchen83
Copy link
Contributor Author

after fix
the memory allocation as following
image

@leonchen83 leonchen83 force-pushed the jetty-9.4.x branch 5 times, most recently from 2da50c1 to 7cbe501 Compare October 30, 2020 15:58
this PR let the ByteAccumulator recyclable. after invoke ByteAccumulator.transferTo method
we can invoke ByteAccumulator.recycle method to reuse byte[] via ByteAccumulator.newByteBuffer method

Signed-off-by: Baoyi Chen <chen.bao.yi@qq.com>
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.

This still needs more thought/work.
The bytebuffer pools are really very sensitive as ifa buffer is put in there incorrectly it can have bad security implications, so we need something that is really REALLY certain.

I'm thinking that perhaps we should write a new ByteBufferAccumulator class from scratch rather than try to make the current one less allocating. We can then come up with a much better API contract to ensure that we don't mess up the pools.

}

public void copyChunk(ByteBuffer buf)
{
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 we need to check that the buf here was the one last returned from newByteBuffer


if (buf.hasRemaining())
{
chunks.add(buf);
Copy link
Contributor

Choose a reason for hiding this comment

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

If we always add to the chunks list, then we can end up with a lot of buffers in the list, all max size, but each with just a few bytes in them.

@lachlan-roberts
Copy link
Contributor

@leonchen83 I will pull in your commits into a new branch and combine it with my efforts on PR #5536.
I will open a new PR and ask you to review and also test how much improves the memory allocations for you.

@leonchen83
Copy link
Contributor Author

@lachlan-roberts
thank you for your help. if done, I m grad to review that code.

@lachlan-roberts
Copy link
Contributor

This work is being continued in PR #5574.

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.

None yet

4 participants