-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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 #5499 - Reduce buffer allocations and copying from ByteAccumulator #5574
Conversation
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
@gregw @leonchen83 This branch tries to combine the work from PRs #5520 and #5536. I haven't written much testing or javadoc yet, just want to make sure we agree on the API before continuing. |
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good. I've added a bunch of comments but nothing way out of order... just suggestions really.
jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferAccumulator.java
Outdated
Show resolved
Hide resolved
jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferAccumulator.java
Outdated
Show resolved
Hide resolved
jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferOutputStream2.java
Outdated
Show resolved
Hide resolved
{ | ||
private final ByteBufferAccumulator _accumulator; | ||
private final ByteBufferPool _bufferPool; | ||
private ByteBuffer _combinedByteBuffer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there really a need to remember the combined ByteBuffer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to remember it so that we release it when close()
is called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is wrong for this class to manage the releasing of the combined buffer. Isn't that mostly passed along a chain and will be released when processing is complete. I still lean towards takeBuffer
semantics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this behaviour can be implemented in ByteBufferAccumulator
with 2 methods:
toBuffer
will create a single buffer of all the contents, it will release any buffers in the list and the list will end up containing just the new single buffer. Any subsequent calls totoBuffer
will get the same buffer back. Any calls to add more content will just allocate another buffer in the list. Buffer will be release withclose
takeBuffer
will create a single buffer of all the contents, it will release any buffers in the list and clear it. It is the callers responsibility to release the returned buffer.
jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferOutputStream2.java
Outdated
Show resolved
Hide resolved
jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferOutputStream2.java
Show resolved
Hide resolved
...on/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/ByteAccumulator.java
Show resolved
Hide resolved
jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferAccumulator.java
Outdated
Show resolved
Hide resolved
jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferAccumulator.java
Outdated
Show resolved
Hide resolved
{ | ||
private final ByteBufferAccumulator _accumulator; | ||
private final ByteBufferPool _bufferPool; | ||
private ByteBuffer _combinedByteBuffer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to remember it so that we release it when close()
is called.
jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferOutputStream2.java
Outdated
Show resolved
Hide resolved
jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferOutputStream2.java
Outdated
Show resolved
Hide resolved
...on/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/ByteAccumulator.java
Show resolved
Hide resolved
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferAccumulator.java
Outdated
Show resolved
Hide resolved
jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferOutputStream2.java
Outdated
Show resolved
Hide resolved
@sbordet can you tune into this PR whilst in draft form? It really has the feel of one of those that I push @lachlan-roberts all the way in one direction, only for you to come in during review with a totally different view point :) |
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
closer!
public ByteBuffer takeByteBuffer() | ||
{ | ||
int length = getLength(); | ||
ByteBuffer combinedBuffer = _bufferPool.acquire(length, false); | ||
for (ByteBuffer buffer : _buffers) | ||
{ | ||
combinedBuffer.put(buffer); | ||
} | ||
return combinedBuffer; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public ByteBuffer takeByteBuffer() | |
{ | |
int length = getLength(); | |
ByteBuffer combinedBuffer = _bufferPool.acquire(length, false); | |
for (ByteBuffer buffer : _buffers) | |
{ | |
combinedBuffer.put(buffer); | |
} | |
return combinedBuffer; | |
} | |
public ByteBuffer takeByteBuffer() | |
{ | |
int length = getLength(); | |
switch(length) | |
{ | |
case 0: | |
return null; // TODO or empty buffer from pool? | |
case 1: | |
return _buffers.remove(0); | |
default: | |
ByteBuffer combinedBuffer = _bufferPool.acquire(length, false); | |
for (ByteBuffer buffer : _buffers) | |
{ | |
combinedBuffer.put(buffer); | |
_bufferPool.release(buffer); | |
} | |
_buffers.clear(); | |
return combinedBuffer; | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really like the idea of returning null from here, it will introduce null checks everywhere this is used.
How can we get empty buffer from pool, will _bufferPool.aquire(0, false)
work, because that's what we are currently doing for 0 length.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also makes it so you can only call takeBuffer()
once, but you can call toBuffer()
multiple times and get the same buffer.
Why wouldn't we just release these buffers on close()
like we would do in other cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the idea of takeBuffer
is that you take the buffer and all it's data, hence it can't be called again.
The intent is that once you call takeBuffer
you will pass the resulting buffer along some further chain of events and then only release it once it is free.
freeing the buffer on close makes no sense for most uses, as we want to have a accumulator that lives as long as the connection, during which time it may create many buffers that are taken and passed on.
Happy not to return null.
_buffers.forEach(_bufferPool::release); | ||
_buffers.clear(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_buffers.forEach(_bufferPool::release); | |
_buffers.clear(); |
jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferOutputStream2.java
Outdated
Show resolved
Hide resolved
jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferOutputStream2.java
Outdated
Show resolved
Hide resolved
jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferOutputStream2.java
Show resolved
Hide resolved
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one final niggle...
...on/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/ByteAccumulator.java
Outdated
Show resolved
Hide resolved
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
@sbordet you wanted me to keep these I can also see some potential to use them in other places, for example ByteBufferOutputStream2 could easily be used in MultiPart if we could get the BufferPool there. Should I put them as public websocket classes or can I keep them in jetty-io? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks alright to me, but I can't help thinking that this could have been a lot simpler and more efficient if we didn't directly use ByteBuffer
s but some wrapper around them.
jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferAccumulator.java
Outdated
Show resolved
Hide resolved
* This will add up the remaining of each buffer in the accumulator. | ||
* @return the total length of the content in the accumulator. | ||
*/ | ||
public int getLength() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This length accumulation could potentially overflow Integer.MAX_VALUE
. We should handle this by either returning a long from here, or detecting the integer overflow and throwing an exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the accumulation needs to eventually fit into a single buffer, the int should do with overflow detection elsewhere.
An ultimate buffer abstraction could allow for chains of buffers that can be gather written for a long length, but not this abstraction
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
I'll be +1 once @lorban's overflow concerns are addressed |
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
BufferUtil.flipToFlush(buffer, position); | ||
|
||
accumulator.writeTo(buffer); | ||
close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
close()
method invoked by DeflateFrameExtension.java#L64
and PerMessageDeflateExtension.java#L81
transferTo
method no need add extra close
method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did this to preserve the behaviour of this class with the previous usage.
Before these changes it was not AutoCloseable, so it would have been used like this with no closing:
ByteAccumulator acc = new ByteAccumulator();
acc.copyChunk(something);
...
acc.transferTo(combinedBuffer);
So in this way we can be more lenient on the usage of ByteAccumulator
by supporting the deprecated way of using it, and now we have the replacement ByteBufferAccumulator
which is more strict and must be used with an explicit call to close()
.
Issue #5499
ByteBufferOutputStream2
andByteBufferAccumulator
.ByteAccumulator
with theByteBufferAccumulator
and make itAutoCloseable
.ByteAccumulator
, allowing you to write directly to the internal buffer instead of copying.