Skip to content

Commit aec95b6

Browse files
committed
Updated examples and add tests
1 parent a5368e0 commit aec95b6

File tree

2 files changed

+241
-1
lines changed

2 files changed

+241
-1
lines changed

examples/src/main/java/com/hivemq/client/mqtt/examples/ReconnectStrategy.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ public class ReconnectStrategy {
3535
public static void main(final String[] args) throws InterruptedException {
3636
// defaultReconnect();
3737
// customizedReconnect();
38-
completelyCustom();
38+
// completelyCustom();
39+
gracefulDisconnectExample();
3940
}
4041

4142
public static void defaultReconnect() {
@@ -109,4 +110,52 @@ private static CompletableFuture<byte[]> getOAuthToken() {
109110
return new byte[] {1, 2, 3};
110111
});
111112
}
113+
114+
/**
115+
* Demonstrates graceful disconnect functionality.
116+
* This example shows how to use disconnectGracefully() to cleanly shut down
117+
* a client even when automatic reconnection is enabled and the client is
118+
* in a reconnecting state.
119+
*/
120+
public static void gracefulDisconnectExample() throws InterruptedException {
121+
System.out.println("=== Graceful Disconnect Example ===");
122+
123+
final Mqtt5BlockingClient client = Mqtt5Client.builder()
124+
.serverHost("broker.hivemq.com")
125+
.automaticReconnect()
126+
.initialDelay(1, TimeUnit.SECONDS)
127+
.maxDelay(2, TimeUnit.SECONDS)
128+
.applyAutomaticReconnect()
129+
.addConnectedListener(context -> System.out.println("Connected: " + LocalTime.now()))
130+
.addDisconnectedListener(context -> System.out.println("Disconnected: " + LocalTime.now() +
131+
" (Source: " + context.getSource() + ")"))
132+
.buildBlocking();
133+
134+
try {
135+
// Connect the client
136+
System.out.println("Connecting...");
137+
client.connect();
138+
System.out.println("Connected successfully!");
139+
140+
// Simulate network issues by turning off network (in real scenario)
141+
System.out.println("Simulating network issues...");
142+
System.out.println("Client state: " + client.getState());
143+
144+
// Wait a bit to let reconnection attempts start
145+
TimeUnit.SECONDS.sleep(3);
146+
System.out.println("Client state after network issues: " + client.getState());
147+
148+
// Now demonstrate graceful disconnect
149+
System.out.println("Calling disconnectGracefully()...");
150+
client.disconnectGracefully();
151+
System.out.println("Graceful disconnect completed!");
152+
System.out.println("Final client state: " + client.getState());
153+
154+
} catch (final Exception e) {
155+
System.err.println("Error during graceful disconnect example: " + e.getMessage());
156+
e.printStackTrace();
157+
}
158+
159+
System.out.println("=== End Graceful Disconnect Example ===");
160+
}
112161
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/*
2+
* Copyright 2018-present HiveMQ and the HiveMQ Community
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.hivemq.client.mqtt.lifecycle;
18+
19+
import com.hivemq.client.mqtt.MqttClient;
20+
import com.hivemq.client.mqtt.MqttClientState;
21+
import com.hivemq.client.mqtt.mqtt3.Mqtt3BlockingClient;
22+
import com.hivemq.client.mqtt.mqtt3.Mqtt3Client;
23+
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.api.Timeout;
25+
26+
import java.util.concurrent.CountDownLatch;
27+
import java.util.concurrent.TimeUnit;
28+
import java.util.concurrent.atomic.AtomicReference;
29+
30+
import static org.junit.jupiter.api.Assertions.*;
31+
32+
/**
33+
* Test for graceful disconnect functionality in MQTT 3 clients.
34+
*
35+
* This test addresses the issue described in GitHub issue #675 where
36+
* disconnect() fails with MqttClientStateException when automatic
37+
* reconnection is enabled and the client is in a reconnecting state.
38+
*
39+
* @since 1.4.0
40+
*/
41+
class Mqtt3ClientGracefulDisconnectTest {
42+
43+
@Test
44+
@Timeout(30)
45+
void disconnectGracefully_whenNotConnected_shouldSucceed() {
46+
final Mqtt3BlockingClient client = Mqtt3Client.builder()
47+
.serverHost("localhost")
48+
.serverPort(1883)
49+
.buildBlocking();
50+
51+
// Should not throw any exception
52+
assertDoesNotThrow(() -> client.disconnectGracefully());
53+
assertEquals(MqttClientState.DISCONNECTED, client.getState());
54+
}
55+
56+
@Test
57+
@Timeout(30)
58+
void disconnectGracefully_whenConnected_shouldSucceed() throws Exception {
59+
final Mqtt3BlockingClient client = Mqtt3Client.builder()
60+
.serverHost("broker.hivemq.com")
61+
.serverPort(1883)
62+
.buildBlocking();
63+
64+
try {
65+
// Connect the client
66+
client.connect();
67+
assertEquals(MqttClientState.CONNECTED, client.getState());
68+
69+
// Graceful disconnect should succeed
70+
assertDoesNotThrow(() -> client.disconnectGracefully());
71+
assertEquals(MqttClientState.DISCONNECTED, client.getState());
72+
} catch (Exception e) {
73+
// If connection fails (network issues), graceful disconnect should still work
74+
assertDoesNotThrow(() -> client.disconnectGracefully());
75+
assertEquals(MqttClientState.DISCONNECTED, client.getState());
76+
}
77+
}
78+
79+
@Test
80+
@Timeout(30)
81+
void disconnectGracefully_withAutomaticReconnect_shouldCancelReconnection() throws Exception {
82+
final CountDownLatch disconnectedLatch = new CountDownLatch(1);
83+
final AtomicReference<MqttClientState> finalState = new AtomicReference<>();
84+
85+
final Mqtt3BlockingClient client = Mqtt3Client.builder()
86+
.serverHost("broker.hivemq.com")
87+
.serverPort(1883)
88+
.automaticReconnect()
89+
.initialDelay(1, TimeUnit.SECONDS)
90+
.maxDelay(2, TimeUnit.SECONDS)
91+
.applyAutomaticReconnect()
92+
.addDisconnectedListener(context -> {
93+
System.out.println("Disconnected: " + context.getSource());
94+
finalState.set(MqttClientState.DISCONNECTED);
95+
disconnectedLatch.countDown();
96+
})
97+
.buildBlocking();
98+
99+
try {
100+
// Connect the client
101+
client.connect();
102+
assertEquals(MqttClientState.CONNECTED, client.getState());
103+
104+
// Disconnect to trigger reconnection
105+
client.disconnect();
106+
107+
// Wait a bit for reconnection to start
108+
Thread.sleep(500);
109+
110+
// Now call graceful disconnect - this should cancel reconnection
111+
assertDoesNotThrow(() -> client.disconnectGracefully());
112+
113+
// Wait for disconnection to complete
114+
assertTrue(disconnectedLatch.await(5, TimeUnit.SECONDS));
115+
116+
// Final state should be DISCONNECTED, not DISCONNECTED_RECONNECT
117+
assertEquals(MqttClientState.DISCONNECTED, client.getState());
118+
assertEquals(MqttClientState.DISCONNECTED, finalState.get());
119+
120+
} catch (Exception e) {
121+
// If connection fails (network issues), graceful disconnect should still work
122+
assertDoesNotThrow(() -> client.disconnectGracefully());
123+
assertEquals(MqttClientState.DISCONNECTED, client.getState());
124+
}
125+
}
126+
127+
@Test
128+
@Timeout(30)
129+
void disconnectGracefully_async_shouldSucceed() throws Exception {
130+
final Mqtt3BlockingClient client = Mqtt3Client.builder()
131+
.serverHost("broker.hivemq.com")
132+
.serverPort(1883)
133+
.buildBlocking();
134+
135+
try {
136+
// Connect the client
137+
client.connect();
138+
assertEquals(MqttClientState.CONNECTED, client.getState());
139+
140+
// Test async graceful disconnect
141+
final var future = client.toAsync().disconnectGracefully();
142+
assertNotNull(future);
143+
144+
// Wait for completion
145+
future.get(5, TimeUnit.SECONDS);
146+
assertEquals(MqttClientState.DISCONNECTED, client.getState());
147+
148+
} catch (Exception e) {
149+
// If connection fails (network issues), graceful disconnect should still work
150+
final var future = client.toAsync().disconnectGracefully();
151+
assertNotNull(future);
152+
future.get(5, TimeUnit.SECONDS);
153+
assertEquals(MqttClientState.DISCONNECTED, client.getState());
154+
}
155+
}
156+
157+
@Test
158+
@Timeout(30)
159+
void disconnectGracefully_comparedToRegularDisconnect() throws Exception {
160+
final Mqtt3BlockingClient client = Mqtt3Client.builder()
161+
.serverHost("broker.hivemq.com")
162+
.serverPort(1883)
163+
.automaticReconnect()
164+
.initialDelay(1, TimeUnit.SECONDS)
165+
.maxDelay(2, TimeUnit.SECONDS)
166+
.applyAutomaticReconnect()
167+
.buildBlocking();
168+
169+
try {
170+
// Connect the client
171+
client.connect();
172+
assertEquals(MqttClientState.CONNECTED, client.getState());
173+
174+
// Disconnect to trigger reconnection
175+
client.disconnect();
176+
177+
// Wait a bit for reconnection to start
178+
Thread.sleep(500);
179+
180+
// Regular disconnect might throw MqttClientStateException in reconnecting state
181+
// Graceful disconnect should not throw any exception
182+
assertDoesNotThrow(() -> client.disconnectGracefully());
183+
assertEquals(MqttClientState.DISCONNECTED, client.getState());
184+
185+
} catch (Exception e) {
186+
// If connection fails (network issues), graceful disconnect should still work
187+
assertDoesNotThrow(() -> client.disconnectGracefully());
188+
assertEquals(MqttClientState.DISCONNECTED, client.getState());
189+
}
190+
}
191+
}

0 commit comments

Comments
 (0)