GH-3697: Various lifecycle fixed for MQTT v5 CAs

Fixes https://github.com/spring-projects/spring-integration/issues/3697
SO: https://stackoverflow.com/questions/70374046/spring-integration-mqtt-failed-to-start-app-when-the-network-is-disconnected

* Add `mqttClient.disconnect()` to `Mqttv5PahoMessageDrivenChannelAdapter.doStop()` - the `doStart()` does `connect()`
* Add `Mqttv5PahoMessageDrivenChannelAdapter.destroy()` impl to close `mqttClient`
* Fix `Mqttv5PahoMessageHandler.doStart()` to not re-throw an exception on connection.
Emit an `MqttConnectionFailedEvent` and log error instead
* Fix `Mqttv5PahoMessageHandler.destroy()` to call `mqttClient.close(true)` for better resources clean up
* Improve MQTT v5 components Javadocs and add a reconnect note into `mqtt.adoc`
This commit is contained in:
Artem Bilan
2021-12-16 12:22:35 -05:00
committed by Gary Russell
parent 30bb8467b4
commit 8c57cb7869
4 changed files with 31 additions and 7 deletions

View File

@@ -59,7 +59,7 @@ import org.springframework.util.Assert;
*
* It is recommended to have the {@link MqttConnectionOptions#setAutomaticReconnect(boolean)}
* set to true to let an internal {@link IMqttAsyncClient} instance to handle reconnects.
* Otherwise, the manual restart of this component can only handle reconnects, e.g. via
* Otherwise, only the manual restart of this component can handle reconnects, e.g. via
* {@link MqttConnectionFailedEvent} handling on disconnection.
*
* See {@link #setPayloadType} for more information about type conversion.
@@ -190,6 +190,7 @@ public class Mqttv5PahoMessageDrivenChannelAdapter extends AbstractMqttMessageDr
String[] topics = getTopic();
try {
this.mqttClient.unsubscribe(topics).waitForCompletion(getCompletionTimeout());
this.mqttClient.disconnect().waitForCompletion(getCompletionTimeout());
}
catch (MqttException ex) {
logger.error(ex, () -> "Error unsubscribing from " + Arrays.toString(topics));
@@ -199,6 +200,17 @@ public class Mqttv5PahoMessageDrivenChannelAdapter extends AbstractMqttMessageDr
}
}
@Override
public void destroy() {
super.destroy();
try {
this.mqttClient.close(true);
}
catch (MqttException ex) {
logger.error(ex, "Failed to close 'MqttAsyncClient'");
}
}
@Override
public void addTopic(String topic, int qos) {
this.topicLock.lock();

View File

@@ -50,6 +50,12 @@ import org.springframework.util.Assert;
/**
* The {@link AbstractMqttMessageHandler} implementation for MQTT v5.
*
* It is recommended to have the {@link MqttConnectionOptions#setAutomaticReconnect(boolean)}
* set to true to let an internal {@link IMqttAsyncClient} instance to handle reconnects.
* Otherwise, only the manual restart of this component can handle reconnects, e.g. via
* {@link MqttConnectionFailedEvent} handling on disconnection.
*
*
* @author Artem Bilan
*
* @since 5.5.5
@@ -159,7 +165,11 @@ public class Mqttv5PahoMessageHandler extends AbstractMqttMessageHandler
this.mqttClient.connect(this.connectionOptions).waitForCompletion(getCompletionTimeout());
}
catch (MqttException ex) {
throw new IllegalStateException("Cannot connect 'MqttAsyncClient' for: " + getComponentName(), ex);
ApplicationEventPublisher applicationEventPublisher = getApplicationEventPublisher();
if (applicationEventPublisher != null) {
applicationEventPublisher.publishEvent(new MqttConnectionFailedEvent(this, ex));
}
logger.error(ex, "MQTT client failed to connect. Will retry if 'ConnectionOptions.isAutomaticReconnect()'.");
}
}
@@ -177,7 +187,7 @@ public class Mqttv5PahoMessageHandler extends AbstractMqttMessageHandler
public void destroy() {
super.destroy();
try {
this.mqttClient.close();
this.mqttClient.close(true);
}
catch (MqttException ex) {
logger.error(ex, "Failed to close 'MqttAsyncClient'");

View File

@@ -56,7 +56,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
* @author Gary Russell
* @author Artem Bilan
*
* @since 4.0
* @since 5.5.5
*
*/
@SpringJUnitConfig
@@ -164,8 +164,7 @@ public class Mqttv5BackToBackTests implements MosquittoContainerTest {
messageProducer.setMessageConverter(mqttStringToBytesConverter());
messageProducer.setManualAcks(true);
return IntegrationFlows.from(
messageProducer)
return IntegrationFlows.from(messageProducer)
.channel(c -> c.queue("fromMqttChannel"))
.get();
}

View File

@@ -494,4 +494,7 @@ public IntegrationFlow mqttInFlow() {
IMPORTANT: The `org.springframework.integration.mqtt.support.MqttMessageConverter` cannot be used with the `Mqttv5PahoMessageDrivenChannelAdapter` since its contract is aimed only for the MQTT v3 protocol.
See more information in the `Mqttv5PahoMessageDrivenChannelAdapter` javadocs and its superclass.
See more information in the `Mqttv5PahoMessageDrivenChannelAdapter` javadocs and its superclass.
IMPORTANT: It is recommended to have the `MqttConnectionOptions#setAutomaticReconnect(boolean)` set to true to let an internal `IMqttAsyncClient` instance to handle reconnects.
Otherwise, only the manual restart of these channel adapters can handle reconnects, e.g. via `MqttConnectionFailedEvent` handling on disconnection.