ruby-mqtt icon indicating copy to clipboard operation
ruby-mqtt copied to clipboard

Add support for asynchronous QOS 1 publish (later also 2)

Open Xasin opened this issue 6 years ago • 4 comments

With the current implementation, the client blocks when sending a qos 1 packet, waiting for the response. This is fine for most applications, however for one of my projects it would be beneficial to be able to send out larger "batches" of qos 1 messages, communicating with various separate electronics. If each single publish blocks and waits for a server response, this can pile up to a fairly large amount of wait time.

It would be nice to have the option of offloading this puback wait to a separate thread, which would run in the background and would handle the puback processing (presumably by polling). This thread could also re-send packets if they time out, instead of throwing a Timeout::Error if no puback is received in time.

(Note: Maybe the puback packet processing could be done directly in the handle_packet function, while a separate thread could poll and detect puback packet timeouts)

That way, a lot of qos > 0 messages could be sent at once, none of which would block the main thread.

Xasin avatar Nov 29 '17 08:11 Xasin

I think that packets should be sent in order so if you have QOS=1 you have to wait for ACK before sending the next one. Also there is no queue for outgoing packets so implementing this would require many changes. So actually QOS 1 isn't really supported.

pgouv avatar Nov 29 '17 10:11 pgouv

Well, for my problem you don't really need packets to arrive in order, but I see how this could be an important feature for other applications.

But if packet order is not important, I am not sure why you would need a queue for outgoing packets. Sending a packet only takes a fraction of the process time that the total of a QOS 1 message requires, so it could still be done synchronously. Instead of the main thread waiting on the puback though, you could just have a hash storing the not-yet-confirmed packets (with their packet ID as keys maybe). The handle_packet function would automatically clear the hash entries of matching puback packets. In parallel, you could have a single thread polling maybe once a second. It could run through the unconfirmed packet hash, look for packets that have timed out, and re-send those.

Although you are correct, @parhs. With the requirement of packet order, things do get a lot more complicated. That is something I would leave for QOS 2 applications though, as QOS 1 only specifies one or more packet transmissions, which does not really guarantee packet order.

Maybe I should make a PR for this feature myself, but I am not sure how to set up documentation, commenting, etc...

We'll see.

Xasin avatar Nov 29 '17 11:11 Xasin

I have found writing asynchronous code in ruby challenging and it is hard to know when to block and how much to use threads.

I wrote ruby-em-mqtt in order to deal with asynchronous pubsub with ruby. Although it could probably do with some more work. EventMachine works in a similar way to node.js and provides mechanisms for handling asynchronous code.

I think the alternative would be to use an event loop based MQTT client. Maybe the paho mqtt client would suit you better? https://github.com/eclipse/paho.mqtt.ruby

njh avatar Mar 09 '20 18:03 njh

Hm... For now I'll keep using your code. It does work plenty fine and I've had only minor problems, and as long as the internet connection isn't laggy, QOS 1/2 don't give that much benefit.

I personally would just use a ruby Queue to store data to be sent and have a blocking thread that handles sending, one for receiving... But since I never wrote a communication code like this I might be underestimating the challenges here.

Thank you a lot for pointing out the Paho MQTT client, it looks like a good alternative that I must have missed somehow!

I suppose that settles this issue for me, but maybe you'd like to keep it open in case you do want to tackle it someday?

Xasin avatar Mar 09 '20 21:03 Xasin