Skip to content

Conversation

jrhee17
Copy link
Contributor

@jrhee17 jrhee17 commented Aug 28, 2025

Motivation:

Currently, once an HttpRequest and HttpResponse is completed, the underlying HTTP2 stream is cancelled using a RST_STREAM.
This makes sense for normal HTTP constructs since it indicates that we are no longer interested in the request, and we would like to release resources associated with it.

However, some protocols such as WebSockets implement their own graceful shutdown procedure.

In detail, Armeria's HttpRequest, HttpResponse implements WebSocket graceful shutdown and the reactive stream implementation is closed when a CLOSE frame is both sent and received.
However, although the websocket session is completed, the underlying HTTP2 stream may not necessarily be complete.
Protocol-wise, there is an inherent discrepancy between websocket session completion and HTTP2 stream completion.

The current implementation defaults to sending a RST_STREAM immediately once the corresponding HttpRequest and HttpResponse. For websockets, I propose that a delay is given so that the remote has a chance to end the stream.

Assuming that we will tie the lifecycle of inbound WebSockets with outbound WebSockets (so sending a close frame also closes the inbound) in #6357 , this option can be thought of similar to netty's forceCloseTimeoutMillis option. (which acts as a timeout since sending the CLOSE frame)

Modifications:

  • Added a closeHttp2StreamDelayMillis option to ServiceConfig and relevant implementations
  • WebSocketService sets a closeHttp2StreamDelayMillis of 10 seconds by default
  • HttpServerHandler decides when to send a RST_STREAM based on the closeHttp2StreamDelayMillis
  • Added Http2StreamLifecycleHandler which maintains the lifecycle of reset futures. This ensures that scheduled futures aren't leaked for servers with high throughput.
    • Every time a request/response is closed but the corresponding stream is alive, maybeResetStream is called.
    • Every time a stream is closed, notifyStreamClosed is called to clean up possibly scheduled futures.

Result:

  • Users can set a timeout for closing a websocket session
  • Fix a bug where closing a websocket session could send a RST_STREAM frame

@jrhee17 jrhee17 added the defect label Aug 28, 2025
@jrhee17 jrhee17 added this to the 1.34.0 milestone Aug 28, 2025
Copy link

codecov bot commented Aug 28, 2025

Codecov Report

❌ Patch coverage is 72.88136% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.09%. Comparing base (8150425) to head (16fa03e).
⚠️ Report is 179 commits behind head on main.

Files with missing lines Patch % Lines
...rp/armeria/server/Http2StreamLifecycleHandler.java 65.78% 8 Missing and 5 partials ⚠️
...va/com/linecorp/armeria/server/ServiceOptions.java 50.00% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #6375      +/-   ##
============================================
- Coverage     74.46%   74.09%   -0.37%     
- Complexity    22234    22984     +750     
============================================
  Files          1963     2061      +98     
  Lines         82437    86105    +3668     
  Branches      10764    11306     +542     
============================================
+ Hits          61385    63802    +2417     
- Misses        15918    16893     +975     
- Partials       5134     5410     +276     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jrhee17 jrhee17 force-pushed the bugfix/websocket-rst branch from 1b73e7f to 9cdd41d Compare August 28, 2025 10:07
@jrhee17 jrhee17 marked this pull request as ready for review August 29, 2025 01:00
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.

Looks nice. 👍

.requestTimeoutMillis(WebSocketUtil.DEFAULT_REQUEST_RESPONSE_TIMEOUT_MILLIS)
.maxRequestLength(WebSocketUtil.DEFAULT_MAX_REQUEST_RESPONSE_LENGTH)
.requestAutoAbortDelayMillis(WebSocketUtil.DEFAULT_REQUEST_AUTO_ABORT_DELAY_MILLIS)
.closeHttp2StreamDelayMillis(10_000) // follows netty's forceCloseTimeoutMillis default
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you put this value in WebSocketUtil?
Could you update the Javadoc?

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.

👍 👍

@jrhee17 jrhee17 merged commit 2f45bb8 into line:main Sep 16, 2025
14 of 16 checks passed
@ikhoon ikhoon modified the milestones: 1.34.0, 1.33.3 Sep 17, 2025
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.

3 participants