Azure / Azure/azure-iot-sdk-node

Module twin stops working completely when edgeHub was stopped and removed

Open
#1,255 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
JavaScript
Stars
268
Forks
228
PR merge metrics
No merged PRs in 30d

Description

# Context

- **OS and version used:** Alpine Linux Container on a Linux distribution running Azure IoT Edge.
- **Node.js version:** 24
- **npm version:** 11.16.0
- **list of installed packages:**
+-- azure-iot-device-mqtt@1.16.4
+-- azure-iot-device@1.18.4

## Description of the issue
The module twin functionality of the Azure IoT SDK stops working completely when edgeHub is stopped and removed on the background. **This only seems to occur with the MQTT transport**. We have tested this on AMQP as well and it does not occur there.

We have adjusted the sample code to reproduce the issue.

The sample code updates the twin whenever a direct method is received. A counter is incremented each time the twin gets updated. This works fine on an initial start. When invoking the direct method a couple of times the logs state:
```
09:17:24 test TestModule: test called
09:17:24 test TestModule: Successfully sent method response
09:17:24 test TestModule: Updating reported properties with counter: 0
09:17:24 test TestModule: Reported properties successfully updated with counter: 0
09:17:26 test TestModule: test called
09:17:26 test TestModule: Successfully sent method response
09:17:26 test TestModule: Updating reported properties with counter: 1
09:17:26 test TestModule: Reported properties successfully updated with counter: 1
```

Then on the background we stop and remove edgeHub (for example `docker stop edgeHub && docker rm edgeHub`.
After trying to invoke the direct methods again:
```
09:18:09 test TestModule: Connected
09:18:25 test TestModule: test called
09:18:25 test TestModule: Successfully sent method response
09:18:29 test TestModule: test called
09:18:29 test TestModule: Successfully sent method response
```
Notice that there are no logs regarding anything twin related anymore.

## Code sample exhibiting the issue

Sample code

```javascript
'use strict';

const Protocol = require('azure-iot-device-mqtt').Mqtt;
const ModuleClient = require('azure-iot-device').ModuleClient;

let counter = 0;

ModuleClient.fromEnvironment(Protocol, function (err, client) {
if (err) {
console.error("Could not create client: " + err.toString());
process.exit(-1);
} else {
console.log('got client');

client.on('error', function (err) {
console.error(err.message);
});

client.on('connect', function () {
console.log("Connected");
})

client.on('disconnect', function () {
console.log("Disconnected");
})

// connect to the edge instance
client.open(function (err) {
if (err) {
console.error('could not open IotHub client');
} else {
console.log('client opened');

client.onMethod('test', function(request, response) {
console.log('test called')
// Create device Twin
client.getTwin(function (err, twin) {
if (err) {
console.error('could not get twin');
} else {
console.log(`Updating reported properties with counter: ${counter}`)
twin.properties.reported.update({counter: counter}, function (err) {
if (err) throw err;
console.log(`Reported properties successfully updated with counter: ${counter}`);
counter+=1
});
}
});

response.send(204, {}, function(err) {
if (err) {
console.error(err)
} else {
console.log("Successfully sent method response")
}
})
});
}
});
}
});
```

## Console log of the issue

Spend a considerable amount of time going through the logs and noticed that we never seem to get a response to a call for getting the twin. The debug logs state:
```
09:35:20 test TestModule[773]: 2026-06-29T09:35:20.091Z mqttjs:client publish :: message ` ` to topic `$iothub/twin/GET/?$rid=0dea13f2-8648-41d5-bd49-4171c33434c6`
09:35:20 test TestModule[773]: 2026-06-29T09:35:20.091Z mqttjs:client publish :: qos 0
09:35:20 test TestModule[773]: 2026-06-29T09:35:20.091Z mqttjs:client MqttClient:publish: packet cmd: publish
09:35:20 test TestModule[773]: 2026-06-29T09:35:20.091Z mqttjs:client _sendPacket :: (test/TestModule) :: start
09:35:20 test TestModule[773]: 2026-06-29T09:35:20.091Z mqttjs:client sendPacket :: packet: {
09:35:20 test TestModule[773]: cmd: 'publish',
09:35:20 test TestModule[773]: topic: '$iothub/twin/GET/?$rid=0dea13f2-8648-41d5-bd49-4171c33434c6',
09:35:20 test TestModule[773]: payload: ' ',
09:35:20 test TestModule[773]: qos: 0,
09:35:20 test TestModule[773]: retain: false,
09:35:20 test TestModule[773]: messageId: 0,
09:35:20 test TestModule[773]: dup: false
09:35:20 test TestModule[773]: }
```

We would expect the following to be logged but it never comes.
```
mqtt-packet:parser _parseString: result: $iothub/twin/res/200/?$rid=0dea13f2-8648-41d5-bd49-4171c33434c6
```
When looking at edgeHub debug logs the response **is** actually written so the SDK does not receive it somehow.

It seems that the internal mqtt twin client (`mqtt_twin_client.js`) does not resubscribe on the response topic of the twin upon a complete disconnect. The internal mqtt twin client seems to think it is still subscribed to the response topic even though it is not. We verified this by manually adding the following to the `moduleClient.on('connect')`.

```javascript
client.on('connect', function () {
console.log("Connected");
client._transport._twinClient._topicFsm.unsubscribe(moduleClient._transport._twinClient._responseTopic, () => {
console.log("unsubscribed on response topic");
})
})
```
This forces the internal state machine of the twin client to unsubscribe on the topic so when a subsequent call comes in for `getTwin` or anything twin related, it will actually subscribe to the topic again.

## Workaround

Force the internal state machine of the mqtt twin client to unsubscribe on a connect. Any subsequent operation on the twin afterwards should subscribe to it again, solving the issue. This is very far from ideal, but it does seem to work and solve the issue.

```javascript
client.on('connect', function () {
client._transport._twinClient._topicFsm.unsubscribe(moduleClient._transport._twinClient._responseTopic, () => {
console.log("unsubscribed on response topic");
})
})
```

Contributor guide

Open the contributing guide

Research direction

Start with the internal mqtt_twin_client.js and its _topicFsm response-topic handling, then reproduce the failure by stopping and removing edgeHub before invoking getTwin again. Done means the MQTT twin client resubscribes after a complete disconnect and receives the twin response without requiring the private unsubscribe workaround.

Written by the indexing model from the issue text.

Assessment

Tech stack
azure, javascript, node.js
Domain
api, networking
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.