#2026·MQTT.js

[错误]: 在 handleMessage 中发布 qos1 消息会导致重新连接/超时

作者: leppaott创建于 2025年9月17日更新于 2026年9月19日
标签bugstale

MQTTjs Version 5.14.0 Broker vernemq Environment NodeJS Description Looks the same as very old: https://GitHub.com/mqttjs/MQTT.js/issues/838 It seems that if you publish a message on the handleMessage() topic handler, specifically a qos1 message (not qos0 could be due to: https://GitHub.com/mqttjs/MQTT.js/blob/ab2ed39e306c5be18e2ac73c05f758f677a1a0c6/src/lib/client.ts#L1018), the publish-callback will not return "immediately" but we get null error parameter and reconnection. While if we don't await the publish() we get undefined err immediately and no reconnection. Also noticed if you call the "DoneCallback" on the handleMessage before your own callback it works with awaiting publish() too but don't think it's supposed to be used like that. But hard to say since there is no async-examples available for the handleMessage() either. Looks like it's the keepalive that in the end timeouts the handler. This explains the https://GitHub.com/mqttjs/MQTT.js/issues/838 when keepalive likely wasn't implemente - it just hang awaiting for publish() to return. onKeepaliveTimeout :: calling _cleanUp with force true _cleanUp :: forced? true _cleanUp :: (my-client-id) :: destroying stream _cleanUp :: client not disconnecting/reconnecting. Clearing and resetting reconnect. _clearReconnect : clearing reconnect timer _setupReconnect :: emit `offline` state _setupReconnect :: set `reconnecting` to `true` _setupReconnect :: setting reconnectTimer for 1000 ms _destroyKeepaliveManager :: destroying keepalive manager What I'd expect is that publishing qos1 message works on the handler works as expected and returns to the topic-handler which can continue its work. To wrapup looks like qos1 ack handling isn't processed during handleMessage? Minimal Reproduction this.mqttClient.handleMessage = async ({ topic, payload }, done): Promise<void> => { await callback(topic, payload) done(); } async callback() { await new Promise((resolve, reject) => { this.mqttClient.publish(anotherTopic, message, opts, (err) => { if (err) { reject(err); } else { resolve(); } }) }) } Debug logs writable stream :: parsing buffer parser :: on packet push to packets array. work :: getting next packet in queue work :: packet pulled from queue _handlePacket :: emitting packetreceive handlePublish: packet Packet { cmd: 'publish', retain: false, qos: 0, dup: false, length: 388, topic: 'my-request-topic', payload: <Buffer ... 309 more bytes> } handlePublish: qos 0 publish :: message `{"data":{}}` to topic `my-response-topic` publish :: qos 1 MqttClient:publish: packet cmd: publish _sendPacket :: (my-client-id) :: start storeAndSend :: store packet with cmd publish to outgoingStore _removeTopicAliasAndRecoverTopicName :: alias NaN, topic 'my-response-topic' noop :: undefined _writePacket :: packet: { cmd: 'publish', topic: 'my-response-topic', payload: <Buffer ... 309 more bytes> }