[SPARK-58932][SS] Close the accepted socket in TransformWithStateInPySparkStateServer - #58205
[SPARK-58932][SS] Close the accepted socket in TransformWithStateInPySparkStateServer#58205j1wonpark wants to merge 2 commits into
Conversation
…SparkStateServer ### What changes were proposed in this pull request? Run the request loop of `TransformWithStateInPySparkStateServer.run()` inside `Utils.tryWithResource` so the socket returned by `stateServerSocket.accept()` is always closed. ### Why are the changes needed? `run()` accepts a connection from the Python worker but never closes it. The task completion listener closes only the listening `ServerSocketChannel`, so the accepted connection's file descriptor is held until the executor exits. `initStateServer()` binds an ephemeral port per task, so a long-running streaming query leaks one socket per task. Once the ephemeral port range is exhausted, every new connection on that executor fails with `java.net.BindException: Cannot assign requested address`, which also takes down unrelated connections such as the Kafka source. The loop has several early returns, so the close has to be tied to the scope. ### Does this PR introduce _any_ user-facing change? No. ### How was this patch tested? Two tests added to `TransformWithStateInPySparkStateServerSuite`, covering the path where the request loop is never entered and the EOF early-return path. Both fail without the fix. The suite previously had no test calling `run()`. ### Was this patch authored or co-authored using generative AI tooling? Yes. Generated-by: Claude Opus 5 Signed-off-by: Jiwon Park <jpark92@outlook.kr>
2334021 to
9f7e7ba
Compare
|
Thank you @j1wonpark! |
|
@j1wonpark |
…socket-leak Signed-off-by: Jiwon Park <jpark92@outlook.kr> # Conflicts: # sql/core/src/main/scala/org/apache/spark/sql/execution/python/streaming/TransformWithStateInPySparkStateServer.scala # sql/core/src/test/scala/org/apache/spark/sql/execution/python/streaming/TransformWithStateInPySparkStateServerSuite.scala
9e01469 to
119cf85
Compare
|
@HeartSaVioR Thanks! Merged the latest master, keeping the exception handling from SPARK-58977 while closing the accepted socket via |
| Utils.tryWithResource(listeningSocket)(serveRequests) | ||
| } | ||
|
|
||
| private def serveRequests(listeningSocket: SocketChannel): Unit = { |
There was a problem hiding this comment.
Neat approach to avoid indentation :) Thanks!
…SparkStateServer ### What changes were proposed in this pull request? Run the request loop of `TransformWithStateInPySparkStateServer.run()` inside `Utils.tryWithResource` so the socket returned by `stateServerSocket.accept()` is always closed. ### Why are the changes needed? `run()` accepts a connection from the Python worker but never closes it. The task completion listener closes only the listening `ServerSocketChannel`, so the accepted connection's file descriptor is held until the executor exits. `initStateServer()` binds an ephemeral port per task, so a long-running streaming query leaks one socket per task. Once the ephemeral port range is exhausted, every new connection on that executor fails with `java.net.BindException: Cannot assign requested address`, which also takes down unrelated connections such as the Kafka source. The loop has several early returns, so the close has to be tied to the scope. ### Does this PR introduce _any_ user-facing change? No. ### How was this patch tested? Two tests added to `TransformWithStateInPySparkStateServerSuite`, covering the path where the request loop is never entered and the EOF early-return path. Both fail without the fix. The suite previously had no test calling `run()`. ### Was this patch authored or co-authored using generative AI tooling? Yes. Generated-by: Claude Opus 5 Closes #58205 from j1wonpark/tws-state-server-socket-leak. Authored-by: Jiwon Park <jpark92@outlook.kr> Signed-off-by: Jungtaek Lim <kabhwan.opensource@gmail.com> (cherry picked from commit c5fbdf7) Signed-off-by: Jungtaek Lim <kabhwan.opensource@gmail.com>
…SparkStateServer ### What changes were proposed in this pull request? Run the request loop of `TransformWithStateInPySparkStateServer.run()` inside `Utils.tryWithResource` so the socket returned by `stateServerSocket.accept()` is always closed. ### Why are the changes needed? `run()` accepts a connection from the Python worker but never closes it. The task completion listener closes only the listening `ServerSocketChannel`, so the accepted connection's file descriptor is held until the executor exits. `initStateServer()` binds an ephemeral port per task, so a long-running streaming query leaks one socket per task. Once the ephemeral port range is exhausted, every new connection on that executor fails with `java.net.BindException: Cannot assign requested address`, which also takes down unrelated connections such as the Kafka source. The loop has several early returns, so the close has to be tied to the scope. ### Does this PR introduce _any_ user-facing change? No. ### How was this patch tested? Two tests added to `TransformWithStateInPySparkStateServerSuite`, covering the path where the request loop is never entered and the EOF early-return path. Both fail without the fix. The suite previously had no test calling `run()`. ### Was this patch authored or co-authored using generative AI tooling? Yes. Generated-by: Claude Opus 5 Closes #58205 from j1wonpark/tws-state-server-socket-leak. Authored-by: Jiwon Park <jpark92@outlook.kr> Signed-off-by: Jungtaek Lim <kabhwan.opensource@gmail.com> (cherry picked from commit c5fbdf7) Signed-off-by: Jungtaek Lim <kabhwan.opensource@gmail.com>
|
@j1wonpark |
|
@HeartSaVioR Opened backport PRs for the older version lines: #58308 (branch-4.2) and #58309 (branch-4.1). Both branches lack SPARK-58977, so the cherry-pick keeps |
…eInPySparkStateServer ### What changes were proposed in this pull request? Backport of #58205 to branch-4.2: run the request loop of `TransformWithStateInPySparkStateServer.run()` inside `Utils.tryWithResource` so the accepted socket is always closed. This branch does not have SPARK-58977, so unlike master the `accept()` call is unchanged. ### Why are the changes needed? The accepted socket is never closed, leaking one file descriptor per task until the executor exhausts the ephemeral port range (`java.net.BindException`). See #58205 for details. ### Does this PR introduce _any_ user-facing change? No. ### How was this patch tested? The two tests added in #58205 are included; `TransformWithStateInPySparkStateServerSuite` passes locally on this branch. ### Was this patch authored or co-authored using generative AI tooling? Yes. Generated-by: Claude Opus 5 Closes #58308 from j1wonpark/SPARK-58932-4.2. Authored-by: Jiwon Park <jpark92@outlook.kr> Signed-off-by: Jungtaek Lim <kabhwan.opensource@gmail.com>
…eInPySparkStateServer ### What changes were proposed in this pull request? Backport of #58205 to branch-4.2: run the request loop of `TransformWithStateInPySparkStateServer.run()` inside `Utils.tryWithResource` so the accepted socket is always closed. This branch does not have SPARK-58977, so unlike master the `accept()` call is unchanged. ### Why are the changes needed? The accepted socket is never closed, leaking one file descriptor per task until the executor exhausts the ephemeral port range (`java.net.BindException`). See #58205 for details. ### Does this PR introduce _any_ user-facing change? No. ### How was this patch tested? The two tests added in #58205 are included; `TransformWithStateInPySparkStateServerSuite` passes locally on this branch. ### Was this patch authored or co-authored using generative AI tooling? Yes. Generated-by: Claude Opus 5 Closes #58308 from j1wonpark/SPARK-58932-4.2. Authored-by: Jiwon Park <jpark92@outlook.kr> Signed-off-by: Jungtaek Lim <kabhwan.opensource@gmail.com> (cherry picked from commit 6cb5216) Signed-off-by: Jungtaek Lim <kabhwan.opensource@gmail.com>
What changes were proposed in this pull request?
Run the request loop of
TransformWithStateInPySparkStateServer.run()insideUtils.tryWithResourceso the socket returned bystateServerSocket.accept()is always closed.Why are the changes needed?
run()accepts a connection from the Python worker but never closes it. The task completion listener closes only the listeningServerSocketChannel, so the accepted connection's file descriptor is held until the executor exits.initStateServer()binds an ephemeral port per task, so a long-running streaming query leaks one socket per task. Once the ephemeral port range is exhausted, every new connection on that executor fails withjava.net.BindException: Cannot assign requested address, which also takes down unrelated connections such as the Kafka source.The loop has several early returns, so the close has to be tied to the scope.
Does this PR introduce any user-facing change?
No.
How was this patch tested?
Two tests added to
TransformWithStateInPySparkStateServerSuite, covering the path where the request loop is never entered and the EOF early-return path. Both fail without the fix. The suite previously had no test callingrun().Was this patch authored or co-authored using generative AI tooling?
Yes. Generated-by: Claude Opus 5