From 3dc9682b735dff436bf7c0ef5f3499f016658c95 Mon Sep 17 00:00:00 2001 From: daguimu Date: Fri, 27 Mar 2026 11:27:16 +0800 Subject: [PATCH 1/3] fix(remoting): stop ReconnectTimerTask when client is closed Fixes #15880 --- .../support/header/AbstractTimerTask.java | 6 + .../support/header/HeaderExchangeClient.java | 2 +- .../support/header/ReconnectTimerTask.java | 6 + .../support/header/AbstractTimerTaskTest.java | 112 ++++++++++++++++++ .../header/HeaderExchangeClientTest.java | 25 ++++ .../header/ReconnectTimerTaskTest.java | 42 +++++++ 6 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTaskTest.java diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTask.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTask.java index 53dcbdcb05e..d9b5b58548b 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTask.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTask.java @@ -85,11 +85,17 @@ private synchronized void reput(Timeout timeout) { @Override public synchronized void run(Timeout timeout) throws Exception { Collection channels = channelProvider.getChannels(); + boolean allChannelsClosed = true; for (Channel channel : channels) { if (!channel.isClosed()) { + allChannelsClosed = false; doTask(channel); } } + if (allChannelsClosed) { + cancel(); + return; + } reput(timeout); } diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClient.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClient.java index e654b327c09..7f467b41a2f 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClient.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClient.java @@ -150,7 +150,7 @@ public void send(Object message, boolean sent) throws RemotingException { @Override public boolean isClosed() { - return channel.isClosed(); + return channel.isClosed() || client.isClosed(); } @Override diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/ReconnectTimerTask.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/ReconnectTimerTask.java index 0ce40b593c5..3046dc438cc 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/ReconnectTimerTask.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/ReconnectTimerTask.java @@ -44,6 +44,12 @@ public ReconnectTimerTask( @Override protected void doTask(Channel channel) { try { + if (channel instanceof Client && ((Client) channel).isClosed()) { + logger.info("Client " + channel + " has been closed, cancel reconnect task."); + cancel(); + return; + } + Long lastRead = lastRead(channel); Long now = now(); diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTaskTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTaskTest.java new file mode 100644 index 00000000000..e24440ac7f8 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTaskTest.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.exchange.support.header; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.timer.HashedWheelTimer; +import org.apache.dubbo.remoting.Channel; + +import java.util.Collections; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.apache.dubbo.remoting.Constants.HEARTBEAT_CHECK_TICK; + +class AbstractTimerTaskTest { + + private HashedWheelTimer timer; + private MockChannel channel; + private AtomicInteger taskExecutionCount; + + @BeforeEach + public void setup() { + long tickDuration = 1000; + timer = new HashedWheelTimer(tickDuration / HEARTBEAT_CHECK_TICK, TimeUnit.MILLISECONDS); + channel = new MockChannel() { + @Override + public URL getUrl() { + return URL.valueOf("dubbo://localhost:20880"); + } + }; + taskExecutionCount = new AtomicInteger(0); + } + + @AfterEach + public void teardown() { + timer.stop(); + } + + @Test + void testAutoCancelWhenAllChannelsClosed() throws Exception { + long tick = 1000 / HEARTBEAT_CHECK_TICK; + AbstractTimerTask task = new AbstractTimerTask( + () -> Collections.singleton(channel), timer, tick) { + @Override + protected void doTask(Channel channel) { + taskExecutionCount.incrementAndGet(); + } + }; + task.start(); + + // Let the task run a few times while channel is open + Thread.sleep(1500L); + Assertions.assertTrue(taskExecutionCount.get() > 0, "Task should have executed at least once"); + + int countBeforeClose = taskExecutionCount.get(); + + // Close the channel + channel.close(); + + // Wait for the task to detect closure and auto-cancel + Thread.sleep(1500L); + + int countAfterClose = taskExecutionCount.get(); + + // Task should not have executed after channel was closed + Assertions.assertEquals(countBeforeClose, countAfterClose, + "Task should not execute after all channels are closed"); + + // Verify the task was cancelled + Assertions.assertTrue(task.cancel, "Task should be cancelled when all channels are closed"); + } + + @Test + void testTaskContinuesWhenChannelIsOpen() throws Exception { + long tick = 1000 / HEARTBEAT_CHECK_TICK; + AbstractTimerTask task = new AbstractTimerTask( + () -> Collections.singleton(channel), timer, tick) { + @Override + protected void doTask(Channel channel) { + taskExecutionCount.incrementAndGet(); + } + }; + task.start(); + + Thread.sleep(2000L); + + Assertions.assertTrue(taskExecutionCount.get() > 1, + "Task should keep executing when channel is open"); + Assertions.assertFalse(task.cancel, "Task should not be cancelled when channel is open"); + + task.cancel(); + } +} diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClientTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClientTest.java index 7fad611e7f9..41aa141a2b4 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClientTest.java +++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClientTest.java @@ -37,4 +37,29 @@ void testReconnect() { Assertions.assertFalse(headerExchangeClient.shouldReconnect(URL.valueOf("localhost?reconnect=false"))); Assertions.assertFalse(headerExchangeClient.shouldReconnect(URL.valueOf("localhost?reconnect=FALSE"))); } + + @Test + void testIsClosedWhenUnderlyingClientClosed() { + Client mockClient = Mockito.mock(Client.class); + // Underlying client is closed, but HeaderExchangeChannel is not + Mockito.when(mockClient.isClosed()).thenReturn(true); + Mockito.when(mockClient.getUrl()).thenReturn(URL.valueOf("dubbo://localhost:20880")); + + HeaderExchangeClient headerExchangeClient = new HeaderExchangeClient(mockClient, false); + + Assertions.assertTrue(headerExchangeClient.isClosed(), + "HeaderExchangeClient should report closed when underlying client is closed"); + } + + @Test + void testIsNotClosedWhenBothOpen() { + Client mockClient = Mockito.mock(Client.class); + Mockito.when(mockClient.isClosed()).thenReturn(false); + Mockito.when(mockClient.getUrl()).thenReturn(URL.valueOf("dubbo://localhost:20880")); + + HeaderExchangeClient headerExchangeClient = new HeaderExchangeClient(mockClient, false); + + Assertions.assertFalse(headerExchangeClient.isClosed(), + "HeaderExchangeClient should report open when both channel and client are open"); + } } diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/ReconnectTimerTaskTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/ReconnectTimerTaskTest.java index 6c758b372a9..93debcec5de 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/ReconnectTimerTaskTest.java +++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/ReconnectTimerTaskTest.java @@ -80,4 +80,46 @@ void testReconnect() throws Exception { Thread.sleep(2000L); Assertions.assertTrue(channel.getReconnectCount() > 1); } + + @Test + void testStopReconnectWhenClientClosed() throws Exception { + url = url.addParameter(DUBBO_VERSION_KEY, "2.1.1"); + + // Let the task attempt reconnection while client is open and disconnected + Thread.sleep(1500L); + int reconnectCountBeforeClose = channel.getReconnectCount(); + Assertions.assertTrue(reconnectCountBeforeClose > 0, + "Should have attempted reconnection while channel is not connected"); + + // Close the client - simulates provider going offline and client being destroyed + channel.close(); + + // Wait for the timer to fire again + Thread.sleep(1500L); + + // After closing, reconnect count should not increase + Assertions.assertEquals(reconnectCountBeforeClose, channel.getReconnectCount(), + "Should stop reconnecting after client is closed"); + + // The timer task should have been cancelled + Assertions.assertTrue(reconnectTimerTask.cancel, + "Timer task should be cancelled when client is closed"); + } + + @Test + void testReconnectContinuesWhenNotClosed() throws Exception { + url = url.addParameter(DUBBO_VERSION_KEY, "2.1.1"); + + // Channel is disconnected but not closed - reconnection should continue + Thread.sleep(2000L); + int count1 = channel.getReconnectCount(); + Assertions.assertTrue(count1 > 0, "Should attempt reconnection when disconnected but not closed"); + + Thread.sleep(1500L); + int count2 = channel.getReconnectCount(); + Assertions.assertTrue(count2 > count1, "Should keep trying to reconnect"); + + Assertions.assertFalse(reconnectTimerTask.cancel, + "Timer task should not be cancelled when client is still open"); + } } From 10788d7783ea5474d333511dc70c00e155d2a6a5 Mon Sep 17 00:00:00 2001 From: daguimu Date: Fri, 27 Mar 2026 11:42:16 +0800 Subject: [PATCH 2/3] fix: address code review findings - Guard against empty channel collection in AbstractTimerTask.run() to prevent spurious auto-cancel on server-side timer tasks - Remove redundant dead-code check in ReconnectTimerTask.doTask() - Add javadoc explaining HeaderExchangeClient.isClosed() semantics - Add test for empty channel collection edge case --- .../support/header/AbstractTimerTask.java | 2 +- .../support/header/HeaderExchangeClient.java | 5 ++++ .../support/header/ReconnectTimerTask.java | 6 ----- .../support/header/AbstractTimerTaskTest.java | 26 +++++++++++++++++++ 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTask.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTask.java index d9b5b58548b..18dfdacd5c2 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTask.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTask.java @@ -85,7 +85,7 @@ private synchronized void reput(Timeout timeout) { @Override public synchronized void run(Timeout timeout) throws Exception { Collection channels = channelProvider.getChannels(); - boolean allChannelsClosed = true; + boolean allChannelsClosed = !channels.isEmpty(); for (Channel channel : channels) { if (!channel.isClosed()) { allChannelsClosed = false; diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClient.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClient.java index 7f467b41a2f..b96f0937898 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClient.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClient.java @@ -148,6 +148,11 @@ public void send(Object message, boolean sent) throws RemotingException { channel.send(message, sent); } + /** + * Check both HeaderExchangeChannel and underlying transport client, + * because the transport client may be closed independently (e.g., by protocol destroy) + * without going through HeaderExchangeClient.close(). + */ @Override public boolean isClosed() { return channel.isClosed() || client.isClosed(); diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/ReconnectTimerTask.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/ReconnectTimerTask.java index 3046dc438cc..0ce40b593c5 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/ReconnectTimerTask.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/ReconnectTimerTask.java @@ -44,12 +44,6 @@ public ReconnectTimerTask( @Override protected void doTask(Channel channel) { try { - if (channel instanceof Client && ((Client) channel).isClosed()) { - logger.info("Client " + channel + " has been closed, cancel reconnect task."); - cancel(); - return; - } - Long lastRead = lastRead(channel); Long now = now(); diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTaskTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTaskTest.java index e24440ac7f8..7289cac9ca3 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTaskTest.java +++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTaskTest.java @@ -20,6 +20,7 @@ import org.apache.dubbo.common.timer.HashedWheelTimer; import org.apache.dubbo.remoting.Channel; +import java.util.ArrayList; import java.util.Collections; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -109,4 +110,29 @@ protected void doTask(Channel channel) { task.cancel(); } + + @Test + void testTaskNotCancelledWhenChannelCollectionIsEmpty() throws Exception { + long tick = 1000 / HEARTBEAT_CHECK_TICK; + // Server-side scenario: ChannelProvider returns empty collection when no clients are connected + AbstractTimerTask task = new AbstractTimerTask( + ArrayList::new, timer, tick) { + @Override + protected void doTask(Channel channel) { + taskExecutionCount.incrementAndGet(); + } + }; + task.start(); + + // Let the task run several ticks with empty channel collection + Thread.sleep(2000L); + + // Task should NOT be cancelled — empty collection is not the same as all-closed + Assertions.assertFalse(task.cancel, + "Task should not be cancelled when channel collection is empty"); + Assertions.assertEquals(0, taskExecutionCount.get(), + "doTask should not be called when there are no channels"); + + task.cancel(); + } } From 94d98b6af8ef18abfc45e548d2cc47519a0fd2c2 Mon Sep 17 00:00:00 2001 From: daguimu Date: Fri, 27 Mar 2026 15:53:05 +0800 Subject: [PATCH 3/3] style: fix code formatting per spotless rules --- .../support/header/AbstractTimerTaskTest.java | 22 +++++++------------ .../header/HeaderExchangeClientTest.java | 6 +++-- .../header/ReconnectTimerTaskTest.java | 15 +++++++------ 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTaskTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTaskTest.java index 7289cac9ca3..998853f4a03 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTaskTest.java +++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/AbstractTimerTaskTest.java @@ -59,8 +59,7 @@ public void teardown() { @Test void testAutoCancelWhenAllChannelsClosed() throws Exception { long tick = 1000 / HEARTBEAT_CHECK_TICK; - AbstractTimerTask task = new AbstractTimerTask( - () -> Collections.singleton(channel), timer, tick) { + AbstractTimerTask task = new AbstractTimerTask(() -> Collections.singleton(channel), timer, tick) { @Override protected void doTask(Channel channel) { taskExecutionCount.incrementAndGet(); @@ -83,8 +82,8 @@ protected void doTask(Channel channel) { int countAfterClose = taskExecutionCount.get(); // Task should not have executed after channel was closed - Assertions.assertEquals(countBeforeClose, countAfterClose, - "Task should not execute after all channels are closed"); + Assertions.assertEquals( + countBeforeClose, countAfterClose, "Task should not execute after all channels are closed"); // Verify the task was cancelled Assertions.assertTrue(task.cancel, "Task should be cancelled when all channels are closed"); @@ -93,8 +92,7 @@ protected void doTask(Channel channel) { @Test void testTaskContinuesWhenChannelIsOpen() throws Exception { long tick = 1000 / HEARTBEAT_CHECK_TICK; - AbstractTimerTask task = new AbstractTimerTask( - () -> Collections.singleton(channel), timer, tick) { + AbstractTimerTask task = new AbstractTimerTask(() -> Collections.singleton(channel), timer, tick) { @Override protected void doTask(Channel channel) { taskExecutionCount.incrementAndGet(); @@ -104,8 +102,7 @@ protected void doTask(Channel channel) { Thread.sleep(2000L); - Assertions.assertTrue(taskExecutionCount.get() > 1, - "Task should keep executing when channel is open"); + Assertions.assertTrue(taskExecutionCount.get() > 1, "Task should keep executing when channel is open"); Assertions.assertFalse(task.cancel, "Task should not be cancelled when channel is open"); task.cancel(); @@ -115,8 +112,7 @@ protected void doTask(Channel channel) { void testTaskNotCancelledWhenChannelCollectionIsEmpty() throws Exception { long tick = 1000 / HEARTBEAT_CHECK_TICK; // Server-side scenario: ChannelProvider returns empty collection when no clients are connected - AbstractTimerTask task = new AbstractTimerTask( - ArrayList::new, timer, tick) { + AbstractTimerTask task = new AbstractTimerTask(ArrayList::new, timer, tick) { @Override protected void doTask(Channel channel) { taskExecutionCount.incrementAndGet(); @@ -128,10 +124,8 @@ protected void doTask(Channel channel) { Thread.sleep(2000L); // Task should NOT be cancelled — empty collection is not the same as all-closed - Assertions.assertFalse(task.cancel, - "Task should not be cancelled when channel collection is empty"); - Assertions.assertEquals(0, taskExecutionCount.get(), - "doTask should not be called when there are no channels"); + Assertions.assertFalse(task.cancel, "Task should not be cancelled when channel collection is empty"); + Assertions.assertEquals(0, taskExecutionCount.get(), "doTask should not be called when there are no channels"); task.cancel(); } diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClientTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClientTest.java index 41aa141a2b4..fdc11688dae 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClientTest.java +++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClientTest.java @@ -47,7 +47,8 @@ void testIsClosedWhenUnderlyingClientClosed() { HeaderExchangeClient headerExchangeClient = new HeaderExchangeClient(mockClient, false); - Assertions.assertTrue(headerExchangeClient.isClosed(), + Assertions.assertTrue( + headerExchangeClient.isClosed(), "HeaderExchangeClient should report closed when underlying client is closed"); } @@ -59,7 +60,8 @@ void testIsNotClosedWhenBothOpen() { HeaderExchangeClient headerExchangeClient = new HeaderExchangeClient(mockClient, false); - Assertions.assertFalse(headerExchangeClient.isClosed(), + Assertions.assertFalse( + headerExchangeClient.isClosed(), "HeaderExchangeClient should report open when both channel and client are open"); } } diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/ReconnectTimerTaskTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/ReconnectTimerTaskTest.java index 93debcec5de..72413959811 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/ReconnectTimerTaskTest.java +++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/ReconnectTimerTaskTest.java @@ -88,8 +88,8 @@ void testStopReconnectWhenClientClosed() throws Exception { // Let the task attempt reconnection while client is open and disconnected Thread.sleep(1500L); int reconnectCountBeforeClose = channel.getReconnectCount(); - Assertions.assertTrue(reconnectCountBeforeClose > 0, - "Should have attempted reconnection while channel is not connected"); + Assertions.assertTrue( + reconnectCountBeforeClose > 0, "Should have attempted reconnection while channel is not connected"); // Close the client - simulates provider going offline and client being destroyed channel.close(); @@ -98,12 +98,13 @@ void testStopReconnectWhenClientClosed() throws Exception { Thread.sleep(1500L); // After closing, reconnect count should not increase - Assertions.assertEquals(reconnectCountBeforeClose, channel.getReconnectCount(), + Assertions.assertEquals( + reconnectCountBeforeClose, + channel.getReconnectCount(), "Should stop reconnecting after client is closed"); // The timer task should have been cancelled - Assertions.assertTrue(reconnectTimerTask.cancel, - "Timer task should be cancelled when client is closed"); + Assertions.assertTrue(reconnectTimerTask.cancel, "Timer task should be cancelled when client is closed"); } @Test @@ -119,7 +120,7 @@ void testReconnectContinuesWhenNotClosed() throws Exception { int count2 = channel.getReconnectCount(); Assertions.assertTrue(count2 > count1, "Should keep trying to reconnect"); - Assertions.assertFalse(reconnectTimerTask.cancel, - "Timer task should not be cancelled when client is still open"); + Assertions.assertFalse( + reconnectTimerTask.cancel, "Timer task should not be cancelled when client is still open"); } }