-
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
Cleanup of ByteBufferAccumulator
(approach 2)
#11094
Conversation
+ Added `isRetained()` to `Retainable` + Made `Chunk` is-a `RetainableByteBuffer` + retain rather than copy chunks where possible
+ Added `isRetained()` to `Retainable` + Made `Chunk` is-a `RetainableByteBuffer` + retain rather than copy chunks where possible
+ Added `isRetained()` to `Retainable` + Made `Chunk` is-a `RetainableByteBuffer` + retain rather than copy chunks where possible
+ Added `isRetained()` to `Retainable` + Made `Chunk` is-a `RetainableByteBuffer` + retain rather than copy chunks where possible + Merged aggregator into accumulator + Deleted ByteBufferAggregator.java
+ Added `isRetained()` to `Retainable` + Made `Chunk` is-a `RetainableByteBuffer` + retain rather than copy chunks where possible + Merged aggregator into accumulator + Deleted ByteBufferAggregator.java
+ Added `isRetained()` to `Retainable` + Made `Chunk` is-a `RetainableByteBuffer` + retain rather than copy chunks where possible + Merged aggregator into accumulator + Deleted ByteBufferAggregator.java
+ Added `isRetained()` to `Retainable` + Made `Chunk` is-a `RetainableByteBuffer` + retain rather than copy chunks where possible + Merged aggregator into accumulator + Deleted ByteBufferAggregator.java
+ Added `isRetained()` to `Retainable` + Made `Chunk` is-a `RetainableByteBuffer` + retain rather than copy chunks where possible + Merged aggregator into accumulator + Deprecated ByteBufferAggregator.java + Deprecated ByteBufferCallbackAccumulator
+ Added `isRetained()` to `Retainable` + Made `Chunk` is-a `RetainableByteBuffer` + retain rather than copy chunks where possible + Merged aggregator into accumulator + Deprecated ByteBufferAggregator.java + Deprecated ByteBufferCallbackAccumulator
+ Added append and writeTo methods to RetainableByteBuffer + Made Aggregator and Accumulator subtypes of RetainableByteBuffer
+ Added append and writeTo methods to RetainableByteBuffer + Made Aggregator and Accumulator subtypes of RetainableByteBuffer
…y-12/10541/byteBufferAccumulator2
+ Added append and writeTo methods to RetainableByteBuffer + Made Aggregator and Accumulator subtypes of RetainableByteBuffer
jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/RetainableByteBuffer.java
Outdated
Show resolved
Hide resolved
jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/RetainableByteBuffer.java
Outdated
Show resolved
Hide resolved
jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/AsyncContent.java
Outdated
Show resolved
Hide resolved
jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/RetainableByteBufferTest.java
Show resolved
Hide resolved
jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/RetainableByteBuffer.java
Outdated
Show resolved
Hide resolved
jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/RetainableByteBuffer.java
Outdated
Show resolved
Hide resolved
jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/RetainableByteBuffer.java
Outdated
Show resolved
Hide resolved
jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/RetainableByteBuffer.java
Outdated
Show resolved
Hide resolved
jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/RetainableByteBuffer.java
Outdated
Show resolved
Hide resolved
jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/content/BufferedContentSink.java
Outdated
Show resolved
Hide resolved
I do like the new But the API should be discussed as I would like it to be rock-solid as it's very central to a lot of potential problems. |
WIP on review feedback
@lorban I've acted on most of your feedback, but a couple of questions on some of it. However, I'm only partially available over the next few weeks, so feel free to contribute to this branch. I don't totally agree with the remaining API changes you suggest... but I don't totally disagree either, so he who does the work.... |
...e10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ResponseHeadersTest.java
Show resolved
Hide resolved
Use RBB.append rather than BU.append
@sbordet @lorban Firstly, I think it is too late for this PR in 12.0.8, let's punt to next cycle but PLEASE give this some consideration early in that cycle so we can have time to land it. I think there were a few bugs I found/fixed in this, so I might try to factor those out into another PR for 12.0.8. The idea of this PR is to move towards the model of having a fully functional buffer abstraction that hides details such as:
Ultimately we should be able to deprecate all the inconsistent BufferUtil APIs for append/put etc. and the RBB as the primary API that we use. It can hide all the flipping details as well various implementations. There can even be RBB impls optimized for filling, so the buffer is left in fill mode and only flipped when it is used! |
Use RBB.append rather than BU.append
I disagree, seems like you want to make RBB to be the jack of all trades, and it is not necessary at all. We obviously do not want to allow users to append to a Chunk, and you do not want to add an API to it, only to throw UnsupportedOperationException when they use it. |
This PR actually started as one just replacing the existing Aggregator/Accumulators with better implementations, which I put up for draft review. However, @lorban and I worked those a bit and soon realized that they didn't really solve the problems and that there is a better way, hence the current form of this PR. We currently have an API impedance between several key APIs:
The impedence between these APIs results in:
For example, we have tried to bridge the impedance from BB to RBB with multiple explicit aggregator and accumulator classes. It has not worked and too often developers write their own new aggregators/accumulator rather than using what is there. We now have multiple stale implementations. Even if we put in the effort to add even more new Aggregators/Accumulators that we now say have the "right" API, it doesn't work because they now suffer from the chunk to RBB impedence. The solution is not to write even more API adaptors/wrappers/convertors etc. that try to bridge this API impedance. The solution is to remove the impedance in the first place so that chunks can smoothly flow into RBBs, that can be used for IO to sinks or streams without falling back to BB APIs. This is achieved by having a good core buffer abstraction that provides all the commonly used methods directly on it, without the need to fall back to BB APIs and then to use BufferUtil or or BB based utility classes. We already have a good core read side buffer abstraction with RBB that is used in our low level network code. For some reason, we then break that abstraction with the nearly identical chunk API, only to then struggle to get it back to BB APIs by using various RBB based utility classes. If we just fleshed out the RBB abstraction a bit, then it can be RBBs all the way through with no impedance, no conversions, no wrappers, no copies (unless wanted). I agree that it is less than desirable have read/write APIs that throw |
Perhaps in jetty-12.1 we introduce a read-only |
@sbordet to understand the API impedance I am referring to, consider writing a buffered echo from a sink to a source. Currently, there are several point of impedance:
With this PR, there is no such impedance as our core buffer abstraction has a sufficiently powerful API and chunks are RBBs, so we can accumulate with |
@gregw wrote:
That is not accurate. Chunk is retainable, and in fact we already have
This was analyzed extensively in the past, and we decided it was a bad idea to have more than 1 API in Maybe there is an impedance, but we need minimal impedance for users, not for us. Is there a compelling use case you have found in the code and that I missed that cannot be done without copying? |
Looking at the current code base is not a good indication of what is necessary/good. We have multiple accumulator/aggregators, mostly predating RBB, so of course they are not used with As you say, chunk is
This PR just changes that to
What is
So we have previously identified that there is a problem, but were unable to find a fix it in the
Again, this PR is not proposing changing the Sink API and BB remains as part of the core Sink abstraction. But that should not mean that every other core abstraction has to devolve down to a single call to some
The compelling use-case is that the current code base has:
and more... all with APIs doing essentially the same job of copying bytes from a source to a destination, but all with slightly different APIs and often doing unnecessary copies. Because of API impedance, developers have had to adapt between our core APIs and the result is a mishmash of adaptions and utilities. This issue was opened as we recognised that problem, but initially thought that all we needed to do was introduce a few "better" utility classes, but this time with "better" APIs. We tried that, but it didn't fix the problem. The problem is that we should not need to adapt between our core abstractions in the first place. We should not need utilities and wrappers and adaptors to allow our input to efficiently be buffered and for our buffers to be efficiently written to our output. To quote Elon Musk, "the best part is no part". Rather than make a better utility/adaptor/wrapper, this PR removes the need for them in the first place. You appear to be arguing that BBs are the only common API that we can use between Sources and Sinks. But BB is a bad API. We've tried working around that with conventions and So some questions for you:
|
@lorban @sbordet after the call today, I think we agreed that there is a conceptual difference between the read-only chunk and the read-write RBB.... but that currently in the code base there is zero actual semantic difference between the APIs in this regards, and even some mistakes with making chunks look like they could be mutable. Thus I think the way forward is to make a real API distinction between mutable and immutable retainable buffers. Chunk would extend the immutable API, whilst the buffer pools would provide the mutable version. This is a much bigger change and probably a 12.1.0 thing (which the current PR was borderline being anyway). |
/** | ||
* @return an immutable version of this Chunk | ||
*/ | ||
default Chunk asReadOnly() |
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.
Unfortunately, I think this method can only be deprecated for removal, as it is currently part of the API.
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.
Ah good point
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.
... unless we make this PR a 12.1 thing.... in which case we can deprecate in 12.0.x and remove here.
Currently on the RBB interface there are only 2 methods that mutate the contents of the buffer: /**
* Copies the contents of the given byte buffer at the end of this buffer.
* @param bytes the byte buffer to copy from.
* @return true if all bytes of the given buffer were copied, false otherwise.
* @throws ReadOnlyBufferException if the buffer is read only
* @see BufferUtil#append(ByteBuffer, ByteBuffer)
*/
boolean append(ByteBuffer bytes) throws ReadOnlyBufferException;
/**
* Copies the contents of the given retainable byte buffer at the end of this buffer.
* @param bytes the retainable byte buffer to copy from.
* @return true if all bytes of the given buffer were copied, false otherwise.
* @throws ReadOnlyBufferException if the buffer is read only
* @see BufferUtil#append(ByteBuffer, ByteBuffer)
*/
boolean append(RetainableByteBuffer bytes) throws ReadOnlyBufferException; Both are clearly marked that they can throw To avoid these two methods, we would need to split RBB into two and do some difficult renaming. Options include : interface ImmutableRetainableByteBuffer extends Retainable
{
ByteBuffer getByteBuffer();
}
interface RetainableByteBuffer extends ImmutableRetainableByteBuffer
{
boolean(append(ImmutableRetainableByteBuffer);
} I don't like this style of naming, because a mutable interface RetainableByteBuffer extends extends Retainable
{
ByteBuffer getByteBuffer();
interface Mutable extends RetainableByteBuffer
{
boolean(append(RetainableByteBuffer);
}
} But now all our ByteBufferPools need to return interface Buffer extends extends Retainable
{
ByteBuffer getByteBuffer();
interface Mutable extends Buffer
{
boolean(append(Buffer);
}
} ByteBuffer pools would operate on It is a lot of disruption just to avoid 2 methods, that are semantic cousins of the |
I've had a go at the split to a I'll keep going with it, but it is a huge disruption for 2 append methods that are not different to the put methods on BB, that we have lived with forever! |
…10541/byteBufferAccumulator2
My feeling on this is that if we want to go down the route of splitting interface Buffer extends Retainable
{
void writeTo(Content.Sink, boolean, Callback);
void putTo(nio.ByteBuffer);
interface Mutable extends Buffer
{
boolean append(nio.ByteBuffer);
int fillFrom(EndPoint);
}
} That would require massive modifications as almost everywhere we rely on |
I agree... except perhaps not go away entirely, but be rendered rarely used and considered an antipattern. So all the commonly used I actually don't think this will be too much work... but we will see. I'll keep experimenting in the 3 branch. |
I have experimented with that in the 3 branch. There are a lot of good improvements... but then there are think some classes like HttpParser that need massive modifications.
I agree. If we had started out that way, then I think it would have been a good way to go.... but now it feels like a jetty-13 thing at best.
Also agree. Unless we rigorously address all usages to ensure that we don't have read-only BB inside read-write RBBs (and versa vice), then it is kind of pointless to split the API. It could be done, but I just don't see the benefit at this stage. Meanwhile, this PR, without the split API contains some significant improvements: both in code simplicity and in avoiding needless copies. The cost is 2 append methods on RBB that clearly indicate in their signatures that the RBB might contain a read-only BB. Which is no different from the situation we have today, but without the explicit throws clauses in the API. I don't think we should let this existing issue be a road block to these improvements! |
/** | ||
* Creates a copy of this RetainableByteBuffer that is entirely independent, but | ||
* backed by the same memory space, i.e.: modifying the ByteBuffer of the original | ||
* also modifies the ByteBuffer of the copy and vice-versa. | ||
* @return A copy of this RetainableByteBuffer | ||
*/ | ||
default RetainableByteBuffer copy() | ||
{ | ||
return new AbstractRetainableByteBuffer(BufferUtil.copy(getByteBuffer())) |
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.
Either the comment or the implementation is wrong. Comment implies shallow copy, but implementation is a deep copy!
@gregw said:
I'm going to contradict myself here a little bit.. and maybe there is a mid ground that will keep @sbordet happy. If we moved the mutating methods to a The cost would be some extra wrappers (or perhaps just a cast down) and the call to finish mutation... but it might be OK... The Chunk API would still have I'll experiment with that approach. |
I have essentially split this PR into:
|
Alternative to #11058 to fix #10541