From 52da699313c25ab30a1a7d02e6d165fd9d9207e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 01:52:09 +0000 Subject: [PATCH 1/2] Initial plan From cfdb40844e9509ec273e8963d709ad0811200acf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 02:13:53 +0000 Subject: [PATCH 2/2] Add isClosed() and close() methods to PortForwardResult - Add isClosed() method to WebSocketStreamHandler to expose the closed state - Make PortForwardResult implement Closeable interface - Add isClosed() method to PortForwardResult to check handler state - Add close() method to PortForwardResult to close the underlying handler - Add tests for the new functionality Fixes kubernetes-client/java#2911 Co-authored-by: brendandburns <5751682+brendandburns@users.noreply.github.com> --- .../io/kubernetes/client/PortForward.java | 19 +++++++++- .../client/util/WebSocketStreamHandler.java | 9 +++++ .../io/kubernetes/client/PortForwardTest.java | 37 +++++++++++++++++++ .../client/WebsocketStreamHandlerTest.java | 18 +++++++++ 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/util/src/main/java/io/kubernetes/client/PortForward.java b/util/src/main/java/io/kubernetes/client/PortForward.java index 5320b019ad..3e8db8d412 100644 --- a/util/src/main/java/io/kubernetes/client/PortForward.java +++ b/util/src/main/java/io/kubernetes/client/PortForward.java @@ -133,7 +133,7 @@ public PortForwardResult forward(String namespace, String name, List po * PortForwardResult contains the result of an Attach call, it includes streams for stdout stderr * and stdin. */ - public static class PortForwardResult { + public static class PortForwardResult implements java.io.Closeable { private WebSocketStreamHandler handler; private HashMap streams; private List ports; @@ -151,6 +151,23 @@ public PortForwardResult(WebSocketStreamHandler handler, List ports) this.ports = ports; } + /** + * Check if the underlying handler is closed. + * + * @return true if the handler is closed, false otherwise. + */ + public boolean isClosed() { + return handler.isClosed(); + } + + /** + * Close the underlying WebSocketStreamHandler. + */ + @Override + public void close() { + handler.close(); + } + /** Initialize the connection. Must be called after the web socket has been opened. */ public void init() throws IOException { for (int i = 0; i < ports.size(); i++) { diff --git a/util/src/main/java/io/kubernetes/client/util/WebSocketStreamHandler.java b/util/src/main/java/io/kubernetes/client/util/WebSocketStreamHandler.java index f495a186e5..eabcd6f581 100644 --- a/util/src/main/java/io/kubernetes/client/util/WebSocketStreamHandler.java +++ b/util/src/main/java/io/kubernetes/client/util/WebSocketStreamHandler.java @@ -117,6 +117,15 @@ public Throwable getError() { return this.error; } + /** + * Check if the handler is closed. + * + * @return true if the handler is closed, false otherwise. + */ + public synchronized boolean isClosed() { + return state == State.CLOSED; + } + @Override public synchronized void close() { if (state != State.CLOSED) { diff --git a/util/src/test/java/io/kubernetes/client/PortForwardTest.java b/util/src/test/java/io/kubernetes/client/PortForwardTest.java index 16c787ef44..b341f2b9a8 100644 --- a/util/src/test/java/io/kubernetes/client/PortForwardTest.java +++ b/util/src/test/java/io/kubernetes/client/PortForwardTest.java @@ -190,4 +190,41 @@ void brokenPortPassing() throws IOException, InterruptedException { assertThat(thrownException).isInstanceOf(IOException.class); } + + @Test + void portForwardResultCloseable() throws IOException { + WebSocketStreamHandler handler = new WebSocketStreamHandler(); + List ports = new ArrayList<>(); + ports.add(80); + + final PortForwardResult result = new PortForwardResult(handler, ports); + + handler.open("wss", null); + + // Initially, handler should not be closed + assertThat(result.isClosed()).isFalse(); + + // Close the result + result.close(); + + // After closing, handler should be closed + assertThat(result.isClosed()).isTrue(); + } + + @Test + void portForwardResultTryWithResources() throws IOException { + WebSocketStreamHandler handler = new WebSocketStreamHandler(); + List ports = new ArrayList<>(); + ports.add(80); + + handler.open("wss", null); + + try (PortForwardResult result = new PortForwardResult(handler, ports)) { + // Handler should be open inside try block + assertThat(result.isClosed()).isFalse(); + } + + // Handler should be closed after try-with-resources block + assertThat(handler.isClosed()).isTrue(); + } } diff --git a/util/src/test/java/io/kubernetes/client/WebsocketStreamHandlerTest.java b/util/src/test/java/io/kubernetes/client/WebsocketStreamHandlerTest.java index 61448c9c6c..d901ba6d23 100644 --- a/util/src/test/java/io/kubernetes/client/WebsocketStreamHandlerTest.java +++ b/util/src/test/java/io/kubernetes/client/WebsocketStreamHandlerTest.java @@ -168,6 +168,24 @@ void handlerReceivingClosed() throws IOException { }).isInstanceOf(IOException.class); } + @Test + void handlerIsClosed() throws IOException { + WebSocketStreamHandler handler = new WebSocketStreamHandler(); + MockWebSocket mockWebSocket = new MockWebSocket(); + + handler.open(testProtocol, mockWebSocket); + + // Initially, handler should not be closed + assertThat(handler.isClosed()).isFalse(); + + // Close the handler + handler.close(); + + // After closing, handler should be closed + assertThat(handler.isClosed()).isTrue(); + assertThat(mockWebSocket.closed).isTrue(); + } + private static class MockWebSocket implements WebSocket { byte[] data; private boolean closed = false;