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

Cancel write semantic #12727

Open
wants to merge 33 commits into
base: jetty-12.1.x
Choose a base branch
from
Open

Cancel write semantic #12727

wants to merge 33 commits into from

Conversation

gregw
Copy link
Contributor

@gregw gregw commented Jan 22, 2025

Added a cancel write/send semantic so that buffers for failed writes need not be removed from the pool

gregw added 2 commits January 22, 2025 17:41
Provide a write cancel mechanism so that removing pooled buffers can be avoided.
@gregw gregw requested review from sbordet and lorban January 22, 2025 08:09
@gregw
Copy link
Contributor Author

gregw commented Jan 22, 2025

@sbordet can you implement the cancel semantic for http2. @lorban ditto for http3

gregw and others added 6 commits January 22, 2025 12:48
Comment on lines 64 to 83
public Callback cancel(Throwable cause, Callback callback)
{
Callback nested = new Callback.Nested(callback)
{
@Override
public void succeeded()
{
super.failed(cause);
}

@Override
public void failed(Throwable x)
{
ExceptionUtil.addSuppressedIfNotAssociated(cause, x);
super.failed(cause);
}
};
reset(HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), cause);
return nested;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@lorban that looks simple enough. Same as h2. Any idea how to test this?

Copy link
Contributor

Choose a reason for hiding this comment

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

This is the big question: the implementation looks fairly easy but I've been scratching my head trying to find a way to reliably test that.

Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment as HTTP/2.

@gregw
Copy link
Contributor Author

gregw commented Jan 29, 2025

@sbordet @lorban nudge for actual review

Copy link
Contributor

@sbordet sbordet left a comment

Choose a reason for hiding this comment

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

I am dubious about the H2/h3 implementation; we should talk about it.

@@ -13,6 +13,7 @@

package org.eclipse.jetty.http2.frames;

@Deprecated (forRemoval = true, since = "12.1.0")
Copy link
Contributor

Choose a reason for hiding this comment

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

Why deprecating this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

because it is not used

@gregw gregw requested review from lorban and sbordet January 31, 2025 15:53
@sbordet
Copy link
Contributor

sbordet commented Jan 31, 2025

@gregw another issue is the use of CountingCallback: it has the semantic to count for successes, but it fails on the first failure.

So it has not the right semantic for the usage that was done in H2 and H3.

@gregw
Copy link
Contributor Author

gregw commented Feb 4, 2025

@gregw another issue is the use of CountingCallback: it has the semantic to count for successes, but it fails on the first failure.

So it has not the right semantic for the usage that was done in H2 and H3.

@sbordet See new static List<Callback> from(Callback callback, Throwable cause, int count) method that replaces CountingCallback

Signed-off-by: Ludovic Orban <[email protected]>
Copy link
Contributor

@lorban lorban left a comment

Choose a reason for hiding this comment

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

I believe the mechanism is sane, but I can't wrap my head around the newly introduced API and how this should all work.

I've attempted to write a test for this functionality (I've pushed it to this PR) and that left me puzzled. We should probably discuss it.

@gregw
Copy link
Contributor Author

gregw commented Feb 12, 2025

@sbordet @lorban have you looked at this recently? Any updates for h2 or h3?

@lorban
Copy link
Contributor

lorban commented Feb 12, 2025

@gregw I've been investigating #12776 which happens to be a user-reported bug that is related to the cancel semantic and it appears that 11.0.x, 12.0.x and 12.1.x are buggy.

Cancelling was implemented in 12.0.x with a semantic close to 11.0.x (remove the buffer from the pool) but the mechanism is buggy. Then it was decided to implement it correctly for 12.1.x (hence the changes around IteratingCallback.abort()) so removing the buffers from the pool wouldn't be needed but that was not complete as we figured out more work is needed, and the buffer removing logic was partially added.

So we now have:

  • buggy 10/11 branches with a known regression and potential fix to restore the buffer removal code (Jetty 10.0.x: avoid buffer use after repooling #12786)
  • a buggy 12.0.x branch which uses the buffer removal mechanism that seems to be racy
  • a buggy 12.1.x branch with a currently broken write cancelling mechanism

@gregw
Copy link
Contributor Author

gregw commented Feb 12, 2025

@lorban can you re-review this branch and tell me exactly what you think are the current "broken write cancelling mechanism"? I've forgotten what we discuss last time we hungout on this.

@lorban
Copy link
Contributor

lorban commented Feb 12, 2025

@gregw just to be sure: I was speaking about jetty-12.1.x and not this branch when I wrote "currently broken write cancelling mechanism".

Here is what can happen for H2 that I call broken:

  1. App calls Response.write(false, buffer, callback), which calls ChannelResponse.write(false, buffer, callback), which calls HttpStream.send(), which creates a DataFrame (which is a wrapper for the buffer) then calls HTTP2Session.data(h2stream, dataFrame, callback) which creates a HTTP2Session.Entry (which is a wrapper for both DataFrame and Callback) then appends it into its HTTP2Flusher instance.
  2. Iterating the HTTP2Flusher calls HTTP2Session.Entry.generate(RBB.Mutable), which eventually calls DataGenerator.generateFrame() which calls RetainableByteBuffer.DynamicCapacity.add() to keep a ref onto the HTTP2Session.Entry instance. Eventually, the RetainableByteBuffer.DynamicCapacity is written to the HTTP2Session's EndPoint.
  3. While the write is in progress, a H2 reset arrives; HTTP2Stream.onReset() is called which eventually calls HttpChannel.onRemoteFailure(x), which calls ChannelResponse.lockedFailWrite(x).
  4. lockedFailWrite() calls _writeCallback.fail() (_writeCallback being the callback passed to Response.write()); this signals that the buffer passed to Response.write() can be released.
  5. There is now a buffer that is both back into the pool and in an endpoint's flusher, possibly given to an in-progress SocketChannel.write(ByteBuffer[]) call.

Signed-off-by: Ludovic Orban <[email protected]>
Signed-off-by: Ludovic Orban <[email protected]>
Signed-off-by: Ludovic Orban <[email protected]>
Signed-off-by: Ludovic Orban <[email protected]>
Signed-off-by: Ludovic Orban <[email protected]>
Signed-off-by: Ludovic Orban <[email protected]>
Signed-off-by: Ludovic Orban <[email protected]>
Signed-off-by: Ludovic Orban <[email protected]>
Signed-off-by: Ludovic Orban <[email protected]>
Signed-off-by: Ludovic Orban <[email protected]>
@lorban lorban self-requested a review February 14, 2025 13:34
@lorban
Copy link
Contributor

lorban commented Feb 14, 2025

@gregw The H1 implementation has been reworked and now contains tests.

What this PR lacks before it can be considered complete is some tests for the H2 impl, handling a set of TODOs, javadoc, naming and minor potential improvements.

The H3 implementation has been deferred to after #12742 is merged, as that other PR changes too drastically the QUIC/H3 interactions so any work on the current impl would need to be completely re-done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 👀 In review
Development

Successfully merging this pull request may close these issues.

ByteBufferPool - all ongoing requests fail when single request is cancelled on HTTP/2
3 participants