This is an automated email from the ASF dual-hosted git repository. jamesbognar pushed a commit to branch release-manager in repository https://gitbox.apache.org/repos/asf/juneau.git
commit edd032e1d3e5c3b20701841d56d15668879c5c7c Author: James Bognar <[email protected]> AuthorDate: Tue Aug 18 13:40:32 2026 -0400 Close subscription leaks in RunStateBroadcaster/SseLogServlet tests RunStateBroadcaster.subscribe() already returns an AutoCloseable; these tests discarded it. Switched to try-with-resources. No production code changed; adds one Sonar resource-suppression rationale on a Mockito stub that owns nothing. --- .../juneau/releng/log/RunStateBroadcasterTest.java | 26 +++++----- .../juneau/releng/log/SseLogServletTest.java | 58 ++++++++++++---------- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/test/java/org/apache/juneau/releng/log/RunStateBroadcasterTest.java b/src/test/java/org/apache/juneau/releng/log/RunStateBroadcasterTest.java index 40c7300dbb..1f67abc587 100644 --- a/src/test/java/org/apache/juneau/releng/log/RunStateBroadcasterTest.java +++ b/src/test/java/org/apache/juneau/releng/log/RunStateBroadcasterTest.java @@ -25,17 +25,16 @@ import org.junit.jupiter.api.Test; class RunStateBroadcasterTest { @Test - void publishDeliversToEverySubscriber() { + void publishDeliversToEverySubscriber() throws Exception { var bc = new RunStateBroadcaster(); var a = new ArrayList<String>(); var b = new ArrayList<String>(); - bc.subscribe(a::add); - bc.subscribe(b::add); + try (var s1 = bc.subscribe(a::add); var s2 = bc.subscribe(b::add)) { + bc.publish("{\"status\":\"RUNNING\"}"); - bc.publish("{\"status\":\"RUNNING\"}"); - - assertEquals(List.of("{\"status\":\"RUNNING\"}"), a); - assertEquals(List.of("{\"status\":\"RUNNING\"}"), b); + assertEquals(List.of("{\"status\":\"RUNNING\"}"), a); + assertEquals(List.of("{\"status\":\"RUNNING\"}"), b); + } } @Test @@ -52,17 +51,16 @@ class RunStateBroadcasterTest { } @Test - void aThrowingSubscriberDoesNotBreakOthers() { + void aThrowingSubscriberDoesNotBreakOthers() throws Exception { var bc = new RunStateBroadcaster(); var seen = new ArrayList<String>(); - bc.subscribe(s -> { + try (var s1 = bc.subscribe(s -> { throw new RuntimeException("dead client"); - }); - bc.subscribe(seen::add); - - bc.publish("snapshot"); + }); var s2 = bc.subscribe(seen::add)) { + bc.publish("snapshot"); - assertEquals(List.of("snapshot"), seen); + assertEquals(List.of("snapshot"), seen); + } } @Test diff --git a/src/test/java/org/apache/juneau/releng/log/SseLogServletTest.java b/src/test/java/org/apache/juneau/releng/log/SseLogServletTest.java index bc1b79af39..2e182daf95 100644 --- a/src/test/java/org/apache/juneau/releng/log/SseLogServletTest.java +++ b/src/test/java/org/apache/juneau/releng/log/SseLogServletTest.java @@ -58,6 +58,9 @@ class SseLogServletTest { return req; } + @SuppressWarnings({ + "resource" // Mockito stub: resp.getWriter() returns the caller-owned PrintWriter; no resource is opened here. + }) private HttpServletResponse responseWriting(PrintWriter out) throws IOException { var resp = mock(HttpServletResponse.class); when(resp.getWriter()).thenReturn(out); @@ -133,33 +136,34 @@ class SseLogServletTest { var initialSeen = new CountDownLatch(1); var updateSeen = new CountDownLatch(1); var writer = new FailableWriter(initialSeen, updateSeen); - var out = new PrintWriter(writer); - var bc = new RunStateBroadcaster(); - var servlet = new SseLogServlet((v, s) -> Optional.empty(), (v, s) -> Optional.empty(), - v -> Optional.of("INITIAL_SNAPSHOT"), v -> Optional.of(bc)); - var req = requestFor("/9.2.1/state"); - var resp = responseWriting(out); - - var worker = new Thread(() -> servlet.doGet(req, resp)); - worker.setDaemon(true); - worker.start(); - - assertTrue(initialSeen.await(2, TimeUnit.SECONDS), "the current snapshot must be sent on connect"); - // tail() subscribes to bc slightly after the initial send completes; re-publish until the worker - // thread's subscription has actually landed rather than racing a single publish against it. - var deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(2); - while (!updateSeen.await(20, TimeUnit.MILLISECONDS)) { - bc.publish("UPDATED_SNAPSHOT"); - if (System.nanoTime() > deadline) - fail("a published snapshot must be tailed live"); + try (var out = new PrintWriter(writer)) { + var bc = new RunStateBroadcaster(); + var servlet = new SseLogServlet((v, s) -> Optional.empty(), (v, s) -> Optional.empty(), + v -> Optional.of("INITIAL_SNAPSHOT"), v -> Optional.of(bc)); + var req = requestFor("/9.2.1/state"); + var resp = responseWriting(out); + + var worker = new Thread(() -> servlet.doGet(req, resp)); + worker.setDaemon(true); + worker.start(); + + assertTrue(initialSeen.await(2, TimeUnit.SECONDS), "the current snapshot must be sent on connect"); + // tail() subscribes to bc slightly after the initial send completes; re-publish until the worker + // thread's subscription has actually landed rather than racing a single publish against it. + var deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(2); + while (!updateSeen.await(20, TimeUnit.MILLISECONDS)) { + bc.publish("UPDATED_SNAPSHOT"); + if (System.nanoTime() > deadline) + fail("a published snapshot must be tailed live"); + } + + writer.failNext = true; + bc.publish("nudge-the-blocked-poll-loop"); // wake tail()'s poll so its next write hits the failure + worker.join(2000); + assertFalse(worker.isAlive(), "the tail loop must exit once writes to the client start failing"); + + assertEquals(SseLogServlet.sse("INITIAL_SNAPSHOT") + SseLogServlet.sse("UPDATED_SNAPSHOT"), + writer.captured.toString()); } - - writer.failNext = true; - bc.publish("nudge-the-blocked-poll-loop"); // wake tail()'s poll so its next write hits the failure - worker.join(2000); - assertFalse(worker.isAlive(), "the tail loop must exit once writes to the client start failing"); - - assertEquals(SseLogServlet.sse("INITIAL_SNAPSHOT") + SseLogServlet.sse("UPDATED_SNAPSHOT"), - writer.captured.toString()); } }
