Open BenUniqcode opened 23 hours ago
Hi @BenUniqcode , I added a small fix to check available handlers. I ignore messages with no handler set. PUBACK
or PUBREC
will not be sent in this case. I hope this resolves the problem.
BTW: I tried to reproduce the problem using test.mosquitto.org
broker but it doesn't send anything until client resubscribes to the topic.
Hi. First of all, thanks for this library, it's mostly been very reliable and I love the configurability and control.
However I've found a problem that can result in incoming MQTT messages being permanently lost.
I am using the latest release v1.5.1 in my project.
The issue arises when all of the following apply:
cleansession=false
and reusing an existing client id.When a client is reconnecting a session with
cleansession=false
, the broker does not wait until the client resubscribes to a topic to send it messages for that topic; as soon as it connects, it immediately starts to send any queued messages for the client's subscriptions from its previous session.ArduinoMqtt handles this ok if the client only ever subscribes to one topic, because there is no opportunity between
connect()
andsubscribe()
for the incoming messages to be processed. So it callssubscribe()
for the topic, sets up the message handler, and then processes the incoming messages, no problem.But if there is more than one subscribed topic, or the topic has changed, when
subscribe()
is called for the first time it will start to receive and process any queued messages for other topics as well. It acknowledges receipt of each message, and then callsdeliverMessage()
- which finds that there is not (yet) a handler defined for that topic, so it just says "Unexpected message" and throws it away!There is a workaround, I think: instead of waiting until subscribe() to set up the message handlers, one can set them up beforehand, by explicitly calling
mqttMessageHandlers->set(topic, callback)
for each topic. However the necessity of doing this is not obvious, and it makes the requirement to specify the same callback tosubscribe()
as well seem a bit extraneous.To fix this properly would, I think, need a breaking change - but that might be justified to raise awareness and prevent others falling victim to this issue. It could be done by, for example, requiring
subscribe()
is called before connecting. It would set up the message handlers and keep a list of topics to be subscribed to, and the actual subscription requests would be sent on connect. Or, remove thecbk
argument fromsubscribe()
, and require that all message handler callbacks are registered before connect or before the first call tosubscribe()
- but that seems harder to enforce.Regardless, I think
processPacket()
must not send a PUBACK or PUBREC for an incoming PUBLISH packet if there is no message handler defined for the topic. This is a general matter of safety, not specific to this issue - acknowledging receipt of a message only to then throw it away without delivering it, is not the behaviour most people would expect. If you can't deliver it (because there's no handler, or because of any other temporary error) then don't ack it, so the broker will try again. If the user wants to discard certain messages, they can choose to do that, but the library should never do so by default! Making this fix should not break anything, and would at least ensure that the broker should attempt redelivery later, giving us time to complete all the subscriptions - however, it's possible that if we only make short-lived MQTT connections, the broker will simply keep trying to deliver the message at the wrong time, and so although it isn't lost as such, we will never receive it properly.I hope that all makes sense. If you can think of a better solution I'm all ears. I was going to simply make a PR but I thought this probably warrants some discussion first about the right approach.
Cheers Ben