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

Avoid leaking request contexts from server-side #6166

Merged
merged 11 commits into from
Apr 7, 2025

Conversation

jrhee17
Copy link
Contributor

@jrhee17 jrhee17 commented Mar 18, 2025

Motivation:

It has been pointed out that server-side may hold strong references to the ServiceRequestContext in #6108.

In general, a server-side HttpRequest may be closed either 1) by the client 2) or by the user.
When the request is closed by the client, the connection or stream state is reflected to the HttpRequest - hence the two are in sync.
However, if the HttpRequest is closed by the user the state is not always reflected to the connection or stream.
For instance, setting ServerBuilder#requestAutoAbortDelayMillis will close the request but not the underlying connection or stream.

The handling of ContentTooLargeException attempts to handle this issue. However, if setShouldResetOnlyIfRemoteIsOpen is set after the response is fully written, then the underlying stream may still never be closed.

Proposal
I propose the following:

  • HTTP1:
    • If there is a protocol failure, the Http1RequestDecoder is responsible for closing the connection
    • Otherwise, the underlying stream will be completed once the remote completes the request. Because pipelining is rarely used, HTTP1 connections will have a constraint of 1 concurrent request which probably won't impose a memory burden.
  • HTTP2:
    • Once the HttpRequest/HttpResponse is completed, the underlying stream state is checked. If the underlying stream is not closed yet, a RST_STREAM is sent to sync the stream state with the HttpRequest/HttpResponse state.
      • Note that this behavior relies on the fact that Http2Stream#State is updated synchronously when Http2FrameWriter#writeHeaders is called.

Modifications:

  • Introduced ServerHttp2ObjectEncoder#maybeResetStream which sends a RST_STREAM if the underlying state is not complete.
    • ServerHttp2ObjectEncoder#maybeResetStream is called when the protocol is HTTP2 and the request/response is complete.
  • Removed resetOnlyIfRemoteIsOpen since maybeResetStream replaces this functionality.

Result:

  • Users no longer observe memory pressure due to incomplete HTTP2 streams.

@jrhee17 jrhee17 marked this pull request as ready for review March 19, 2025 00:31
@jrhee17 jrhee17 added the defect label Mar 19, 2025
@jrhee17 jrhee17 added this to the 1.33.0 milestone Mar 19, 2025
Copy link
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

The new approach looks nice. 👍

@@ -842,6 +843,11 @@ private void handleRequestOrResponseComplete() {
unfinishedRequests.remove(req);
}

if (responseEncoder instanceof ServerHttp2ObjectEncoder) {
((ServerHttp2ObjectEncoder) responseEncoder)
.maybeResetStream(req.streamId(), Http2Error.CANCEL);
Copy link
Contributor

Choose a reason for hiding this comment

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

question:
Can we do this only when needsDisconnection is false?

Copy link
Member

@trustin trustin left a comment

Choose a reason for hiding this comment

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

Simplicity 👍

@ikhoon ikhoon modified the milestones: 1.33.0, 1.32.4 Mar 31, 2025
Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

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

👍👍

@ikhoon ikhoon merged commit 50343cd into line:main Apr 7, 2025
12 of 14 checks passed
ikhoon pushed a commit to ikhoon/armeria that referenced this pull request Apr 8, 2025
Motivation:

It has been pointed out that server-side may hold strong references to
the `ServiceRequestContext` in
line#6108.

In general, a server-side `HttpRequest` may be closed either 1) by the
client 2) or by the user.
When the request is closed by the client, the connection or stream state
is reflected to the `HttpRequest` - hence the two are in sync.
However, if the `HttpRequest` is closed by the user the state is not
always reflected to the connection or stream.
For instance, setting `ServerBuilder#requestAutoAbortDelayMillis` will
close the request but not the underlying connection or stream.

The handling of `ContentTooLargeException` attempts to handle this
issue. However, if `setShouldResetOnlyIfRemoteIsOpen` is set after the
response is fully written, then the underlying stream may still never be
closed.

https://github.com/line/armeria/blob/6dbed576fe8f0774f445716c74c01c7572799ee6/core/src/main/java/com/linecorp/armeria/server/AbstractHttpResponseSubscriber.java#L389

**Proposal**
I propose the following:
- HTTP1:
- If there is a protocol failure, the `Http1RequestDecoder` is
responsible for closing the connection
- Otherwise, the underlying stream will be completed once the remote
completes the request. Because pipelining is rarely used, HTTP1
connections will have a constraint of 1 concurrent request which
probably won't impose a memory burden.
- HTTP2:
- Once the `HttpRequest`/`HttpResponse` is completed, the underlying
stream state is checked. If the underlying stream is not closed yet, a
`RST_STREAM` is sent to sync the stream state with the
`HttpRequest`/`HttpResponse` state.
- Note that this behavior relies on the fact that `Http2Stream#State` is
updated synchronously when `Http2FrameWriter#writeHeaders` is called.

Modifications:

- Introduced `ServerHttp2ObjectEncoder#maybeResetStream` which sends a
`RST_STREAM` if the underlying state is not complete.
- `ServerHttp2ObjectEncoder#maybeResetStream` is called when the
protocol is HTTP2 and the request/response is complete.
- Removed `resetOnlyIfRemoteIsOpen` since `maybeResetStream` replaces
this functionality.

Result:

- Users no longer observe memory pressure due to incomplete HTTP2
streams.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants