Fix MQTT Inbound readyToSubscribeOnStart logic (#8557)

We cannot always reset `readyToSubscribeOnStart` to `false` in the `stop()`
since we may be connected to `ClientManager` and have `cleanStart` option,
so we `unsubscribe()` in the stop and then when we start there is no way to subscribe back

* Reset `readyToSubscribeOnStart` in the `AbstractMqttMessageDrivenChannelAdapter.doStop()`
only if we don't unsubscribe for a `cleanSession` reason.
* Also reset it in the `connectionLost`, so next `connectComplete` will take care about subscription
or `readyToSubscribeOnStart` state change
* Use `isActive()` instead of `isRunning()` in the `connectComplete()` since there is a race
condition when connection is established but `doStart()` has not returned yet, but already passed
`if (this.readyToSubscribeOnStart)` line
This commit is contained in:
Artem Bilan
2023-02-21 12:57:30 -05:00
committed by GitHub
parent ac577e9ef7
commit ee2b6b4098
3 changed files with 52 additions and 12 deletions

View File

@@ -215,6 +215,9 @@ public class MqttPahoMessageDrivenChannelAdapter
&& this.clientFactory.getConnectionOptions().isCleanSession())) {
this.client.unsubscribe(getTopic());
// Have to re-subscribe on next start if connection is not lost.
this.readyToSubscribeOnStart = true;
}
}
catch (MqttException ex1) {
@@ -341,6 +344,10 @@ public class MqttPahoMessageDrivenChannelAdapter
applicationEventPublisher.publishEvent(new MqttConnectionFailedEvent(this, cause));
}
}
else {
// The 'connectComplete()' re-subscribes or sets this flag otherwise.
this.readyToSubscribeOnStart = false;
}
}
@Override
@@ -404,7 +411,9 @@ public class MqttPahoMessageDrivenChannelAdapter
@Override
public void connectComplete(boolean reconnect, String serverURI) {
if (isRunning()) {
// The 'running' flag is set after 'doStart()', so possible a race condition
// when start is not finished yet, but server answers with successful connection.
if (isActive()) {
subscribe();
}
else {

View File

@@ -223,15 +223,17 @@ public class Mqttv5PahoMessageDrivenChannelAdapter
@Override
protected void doStop() {
this.readyToSubscribeOnStart = false;
this.topicLock.lock();
this.readyToSubscribeOnStart = false;
String[] topics = getTopic();
try {
if (this.mqttClient != null && this.mqttClient.isConnected()) {
if (this.connectionOptions.isCleanStart()) {
this.mqttClient.unsubscribe(topics).waitForCompletion(getCompletionTimeout());
}
// Have to re-subscribe on next start if connection is not lost.
this.readyToSubscribeOnStart = true;
}
if (getClientManager() == null) {
this.mqttClient.disconnectForcibly(getDisconnectCompletionTimeout());
}
@@ -331,10 +333,16 @@ public class Mqttv5PahoMessageDrivenChannelAdapter
@Override
public void disconnected(MqttDisconnectResponse disconnectResponse) {
MqttException cause = disconnectResponse.getException();
ApplicationEventPublisher applicationEventPublisher = getApplicationEventPublisher();
if (applicationEventPublisher != null) {
applicationEventPublisher.publishEvent(new MqttConnectionFailedEvent(this, cause));
if (isRunning()) {
MqttException cause = disconnectResponse.getException();
ApplicationEventPublisher applicationEventPublisher = getApplicationEventPublisher();
if (applicationEventPublisher != null) {
applicationEventPublisher.publishEvent(new MqttConnectionFailedEvent(this, cause));
}
}
else {
// The 'connectComplete()' re-subscribes or sets this flag otherwise.
this.readyToSubscribeOnStart = false;
}
}
@@ -358,7 +366,9 @@ public class Mqttv5PahoMessageDrivenChannelAdapter
@Override
public void connectComplete(boolean reconnect, String serverURI) {
if (isRunning()) {
// The 'running' flag is set after 'doStart()', so possible a race condition
// when start is not finished yet, but server answers with successful connection.
if (isActive()) {
subscribe();
}
else {

View File

@@ -60,12 +60,18 @@ public class ResubscribeAfterAutomaticReconnectTests implements MosquittoContain
@Autowired
private MqttConnectionOptions connectionOptions;
@Autowired
Mqttv5PahoMessageDrivenChannelAdapter pahoMessageDrivenChannelAdapter;
@Autowired
Config config;
@Test
void messageReceivedAfterResubscriptionOnLostConnection() throws InterruptedException {
GenericMessage<String> testMessage = new GenericMessage<>("test");
assertThat(this.config.subscribeFirstLatch.await(10, TimeUnit.SECONDS)).isTrue();
this.mqttOutFlowInput.send(testMessage);
assertThat(this.fromMqttChannel.receive(10_000)).isNotNull();
@@ -73,7 +79,16 @@ public class ResubscribeAfterAutomaticReconnectTests implements MosquittoContain
MOSQUITTO_CONTAINER.start();
connectionOptions.setServerURIs(new String[] {MosquittoContainerTest.mqttUrl()});
assertThat(this.config.subscribeLatch.await(10, TimeUnit.SECONDS)).isTrue();
assertThat(this.config.subscribeSecondLatch.await(10, TimeUnit.SECONDS)).isTrue();
this.mqttOutFlowInput.send(testMessage);
assertThat(this.fromMqttChannel.receive(10_000)).isNotNull();
// Re-subscription on channel adapter restart with cleanStart
this.pahoMessageDrivenChannelAdapter.stop();
this.pahoMessageDrivenChannelAdapter.start();
assertThat(this.config.subscribeThirdLatch.await(10, TimeUnit.SECONDS)).isTrue();
this.mqttOutFlowInput.send(testMessage);
assertThat(this.fromMqttChannel.receive(10_000)).isNotNull();
@@ -83,13 +98,18 @@ public class ResubscribeAfterAutomaticReconnectTests implements MosquittoContain
@EnableIntegration
public static class Config {
CountDownLatch subscribeLatch = new CountDownLatch(2);
CountDownLatch subscribeFirstLatch = new CountDownLatch(1);
CountDownLatch subscribeSecondLatch = new CountDownLatch(2);
CountDownLatch subscribeThirdLatch = new CountDownLatch(3);
@Bean
public MqttConnectionOptions mqttConnectOptions() {
return new MqttConnectionOptionsBuilder()
.serverURI(MosquittoContainerTest.mqttUrl())
.automaticReconnect(true)
.cleanStart(true)
.build();
}
@@ -105,7 +125,6 @@ public class ResubscribeAfterAutomaticReconnectTests implements MosquittoContain
public IntegrationFlow mqttInFlow(MqttConnectionOptions mqttConnectOptions) {
Mqttv5PahoMessageDrivenChannelAdapter messageProducer =
new Mqttv5PahoMessageDrivenChannelAdapter(mqttConnectOptions, "mqttInClient", "siTest");
return IntegrationFlow.from(messageProducer)
.channel(c -> c.queue("fromMqttChannel"))
.get();
@@ -113,7 +132,9 @@ public class ResubscribeAfterAutomaticReconnectTests implements MosquittoContain
@EventListener(MqttSubscribedEvent.class)
public void mqttEvents() {
this.subscribeLatch.countDown();
this.subscribeFirstLatch.countDown();
this.subscribeSecondLatch.countDown();
this.subscribeThirdLatch.countDown();
}
}