Skip to content

YARN-11845. WebAppProxy add Connection close header to prevent CLOSE_WAIT leaks - #8697

Open
sohurdc wants to merge 3 commits into
apache:trunkfrom
sohurdc:YARN-11845
Open

YARN-11845. WebAppProxy add Connection close header to prevent CLOSE_WAIT leaks#8697
sohurdc wants to merge 3 commits into
apache:trunkfrom
sohurdc:YARN-11845

Conversation

@sohurdc

Copy link
Copy Markdown

YARN-11845. WebAppProxy: add Connection: close header to prevent CLOSE_WAIT leaks

Description of PR

This PR fixes an accumulation of sockets stuck in CLOSE_WAIT state on the
ResourceManager host that runs the WebAppProxy.

WebAppProxyServlet.proxyLink() creates a brand-new HttpClient for every
proxied request (HttpClientBuilder.create()build()), sends the request,
and only calls base.releaseConnection() afterward. It never closes the
HttpClient itself.

Apache HttpClient enables connection pooling / keep-alive by default, so the
backend (Application Master or History Server) keeps the connection open after
returning the response, waiting to reuse it. But since the proxy throws away
the HttpClient right after each request, that pooled connection can never be
reused. The corresponding socket on the proxy side therefore lingers in
CLOSE_WAIT until the HttpClient is finally garbage collected.

The fix is to explicitly ask the backend to close the connection once the
response has been sent, by setting a Connection: close request header on the
outgoing request:

base.setHeader("Connection", "close");

Because Connection is not in PASS_THROUGH_HEADERS, a client-supplied
Connection: keep-alive header is never forwarded, so the proxy always sends
close and the backend closes the connection as soon as the response is
written.

How was this patch tested?

  • Added testWebAppProxyConnectionCloseHeader() in
    TestWebAppProxyServlet, which verifies that the proxied backend receives a
    Connection: close header in three cases:
    1. a plain GET request,
    2. a GET request where the client explicitly sends Connection: keep-alive
      (asserting the proxy overrides it with close),
    3. a PUT request (asserting the header is also set on the PUT path).
  • The existing testWebAppProxyPassThroughHeaders assertion (9 headers
    received by the backend) is unaffected: the Connection header was already
    among the counted headers, only its value changes from Keep-Alive to
    close.

For code changes

  • The title of this PR accurately describes the issue.
  • The code follows the project's style guidelines.
  • New tests are added, and existing tests still pass.
  • No user-facing API/behavior change is introduced (the proxy continues to
    return the same responses; it only instructs the backend to close the
    underlying connection).

Additional notes

  • Connection: close does not change the response returned to the client; it
    only affects the lifetime of the internal proxied connection, so it has no
    user-visible impact.
  • This is a one-line, low-risk change scoped to the web-proxy module.

Commit message

YARN-11845. WebAppProxy: add Connection: close header to prevent CLOSE_WAIT socket buildup. Contributed by weishao <weishao@sohu-inc.com>.
WebAppProxyServlet.proxyLink() creates a new HttpClient per request and never
closes it. With keep-alive enabled by default, the backend keeps the proxied
connection open and the proxy-side socket lingers in CLOSE_WAIT until GC.
Explicitly setting Connection: close makes the backend close the connection
after each response.

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR aims to prevent socket accumulation in CLOSE_WAIT on the YARN WebAppProxy host by forcing proxied backend requests (AM/History Server) to close the connection after each response.

Changes:

  • Add Connection: close request header to proxied requests in WebAppProxyServlet.proxyLink().
  • Extend TestWebAppProxyServlet to capture the backend-received Connection header and add a new test covering GET/PUT and client keep-alive override behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

FileDescription
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxyServlet.javaSets Connection: close on outgoing proxied requests to avoid backend keep-alive behavior contributing to socket retention.
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/TestWebAppProxyServlet.javaAdds a targeted test validating that the backend receives Connection: close and that client keep-alive is overridden; captures the header in the embedded backend servlet.
Suppressed comments (1)

hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/WebAppProxyServlet.java:320

  • HttpClientBuilder.build() returns a CloseableHttpClient, but proxyLink() never closes the client (or the response), only base.releaseConnection(). Adding Connection: close may reduce socket retention on the backend side, but it doesn’t address the underlying client lifecycle leak and can still leave resources tied to the per-request connection manager until GC.
 base.setHeader("Connection", "close");
String user = req.getRemoteUser();
if (user != null && !user.isEmpty()) {
base.setHeader("Cookie",
PROXY_USER_COOKIE_NAME + "=" + URLEncoder.encode(user, "ASCII"));

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +509 to +513
* HttpClient per request, so if the backend connection is kept alive the
* socket is left in CLOSE_WAIT state on the proxy host until GC reclaims
* it. Setting the 'Connection: close' request header makes the backend
* close the connection as soon as the response is sent (SOHU-HADOOP-11).
*/
assertEquals(HttpURLConnection.HTTP_OK, proxyConn.getResponseCode());
assertNotNull(proxiedConnectionHeader,
"The proxied server did not receive a Connection header at all");
assertEquals("close", proxiedConnectionHeader.trim().toLowerCase(),
Comment on lines +545 to +553
proxyConn = (HttpURLConnection) url.openConnection();
proxyConn.setRequestProperty("Cookie",
"checked_application_0_0000=true");
proxyConn.setRequestProperty("Connection", "keep-alive");
proxyConn.connect();
assertEquals(HttpURLConnection.HTTP_OK, proxyConn.getResponseCode());
assertEquals("close", proxiedConnectionHeader.trim().toLowerCase(),
"The proxy must override a client 'Connection: keep-alive' "
+ "request header with 'close' (SOHU-HADOOP-11)");
Comment on lines +556 to +569
proxyConn = (HttpURLConnection) url.openConnection();
proxyConn.setRequestMethod("PUT");
proxyConn.setDoOutput(true);
proxyConn.setRequestProperty("Cookie",
"checked_application_0_0000=true");
proxyConn.connect();
byte[] body = "SOHU-HADOOP-11".getBytes(StandardCharsets.UTF_8);
try (OutputStream os = proxyConn.getOutputStream()) {
os.write(body);
}
assertEquals(HttpURLConnection.HTTP_OK, proxyConn.getResponseCode());
assertEquals("close", proxiedConnectionHeader.trim().toLowerCase(),
"The proxy must send 'Connection: close' on PUT requests too "
+ "(SOHU-HADOOP-11)");
sohurdcand others added 2 commits August 21, 2026 10:45
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for freeto 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

@sohurdc@slfan1989