Azure / azure-iot-sdk-node

A Node.js SDK for connecting devices to Microsoft Azure IoT services
https://docs.microsoft.com/en-us/azure/iot-hub/
Other
262 stars 227 forks source link

client reconnection throws DeviceMessageLockLostError #1138

Closed yungfox closed 2 years ago

yungfox commented 2 years ago

Context

Description of the issue

I am listening for messages from a device on IoT Hub and forwarding them to a Vue.js front-end app through websockets. The connection to the IoT Hub device is istantiated at the connection of the front-end app to the Node back-end, all is working perfectly fine until the front-end page is refreshed, where the connection is established successfully both between IoT Hub and Node and between Node and Vue but the connection to IoT Hub crashes as soon as a new message is received, throwing the exception stack shown below. Not really sure what to do with it, keep in mind I'm not familiar with the library at all but I couldn't find anything regarding this online, hence me opening this issue. I am aware I might be doing something fundamentally wrong with the code, so feel free to tell me so if that's the case.

Code sample exhibiting the issue

const websocket = require('ws')
const connectionString = process.env.AZURE_CONNECTION_STRING
const device = require('azure-iot-device').Client
const protocol = require('azure-iot-device-amqp').Amqp
const client = device.fromConnectionString(connectionString, protocol)
const wss = new websocket.Server({ noServer: true })

wss.on('connection', socket => {
    console.log('client connected')

    try {
        client.on('message', message => {
            let body = message.getBytes().toString('ascii')
            console.log(`new message: ${body}`)
            socket.send(body)
            client.complete(message)
        })

        client.on('disconnect', () => {
            client.removeAllListeners()
            console.log('client disconnected')
        })

        client.open()
        .then(() => console.log('connected to iothub!'))
        .catch(() => client.close())
    } 
    catch(err) {
        console.log(err)
    }
})

const server = app.listen(3000, () => console.log('app listening on port 3000'))

server.on('upgrade', (req, socket, head) => {
    wss.handleUpgrade(req, socket, head, connsocket => {
        wss.emit('connection', connsocket, req)
    })
})

Console log of the issue

...\node_modules\azure-iot-amqp-base\dist\receiver_link.js:233
                            this._safeCallback(callback, new azure_iot_common_1.errors.DeviceMessageLockLostError());
                                                         ^

DeviceMessageLockLostError
    at constructor.accept (...\node_modules\azure-iot-amqp-base\dist\receiver_link.js:233:58)
    at constructor.handle (...\node_modules\machina\lib\machina.js:613:25)
    at constructor.Fsm.<computed> [as handle] (...\node_modules\machina\lib\machina.js:466:63)
    at ReceiverLink.accept (...\node_modules\azure-iot-amqp-base\dist\receiver_link.js:363:19)
    at ReceiverLink.complete (...\node_modules\azure-iot-amqp-base\dist\receiver_link.js:370:14)
    at Amqp.complete (...\node_modules\azure-iot-device-amqp\dist\amqp.js:666:27)
    at ...\node_modules\azure-iot-device\dist\internal_client.js:206:33
    at retryOperation (...\node_modules\azure-iot-common\dist\retry_operation.js:45:13)
    at RetryOperation.retry (...\node_modules\azure-iot-common\dist\retry_operation.js:77:9)
    at ...\node_modules\azure-iot-device\dist\internal_client.js:205:21 {
  message: undefined
}
yungfox commented 2 years ago

Update - turns out the problem was with the ws library not disposing correctly of the old connection to IoT Hub when the client refreshed the page and opened a new connection... I fixed this using the socket.io library which handles the client disconnection a lot more easily and allows to properly close and dispose of the old connection to IoT Hub.

io.on('connection', socket => {
    client.on('message', message => {
        let body = message.getBytes().toString('ascii')
        console.log(`new message: ${body}`)
        socket.emit('data', body)
        client.complete(message)
    })

    // socket.io can detect client disconnection, this allows the proper disposal of the previous connection
    socket.on('disconnect', () => {
        console.log('client disconnected')
        client.removeAllListeners('message')
    })

    client.open()
    .then(() => console.log('connected to iothub!'))
    .catch(() => {
        console.log('disconnected from iothub')
        client.removeAllListeners('message')
        client.close()
    })
})

Full working example here