quartiq / minimq

Minimal no_std MQTT v5.0 client implementation
MIT License
143 stars 16 forks source link

Add `async` support #156

Open ryan-summers opened 5 months ago

ryan-summers commented 5 months ago

It would be really nice to be able to use minimq as an async MQTT client as well. We should investigate what it would take. Ideally, we would be able to reuse most of the logic and parsing here with minimal code duplication to be able to have both async and synchronous versions of the client.

ryan-summers commented 5 months ago

I think the simplest approach here would be to simply pull the NetworkStack out of the MqttClient struct. Then, we could essentially have the MqttClient operate at a packet level. When a packet is read from the stack, it gets fed into the client for processing. Similarly, when it generates a packet for transmission, it returns the slice for transmission on the network. This would make the client entirely synchronous regardless of the transport (async or sync) and would allow us to use the same core logic for both async and sync implementations.

I think this would essentially result in us moving some of the MqttClient APIs outwards to the Minimq structure. The main impact of this would be that a user could not publish or subscribe within the current closure of Minimq::poll().

However, I think it may make sense to update poll() to similarly not actually process publications but simply buffer them for future processing. I'm still unsure how one would go about generating a Publication or Subscribe in response to an inbound message without copying it though.


Edit: I think the fix here is to have the MqttClient serialize into its TX buffer and essentially flag to the Minimq structure that it has something to send at a future time (i.e. in poll()).

jordens commented 5 months ago

Might be wise to analyze how rust-mqtt et al. do it.

ryan-summers commented 5 months ago

From what I've seen, all of the crates either provide an async or a sync interface, but not both.

jordens commented 5 months ago

Wrong button

Ddystopia commented 5 months ago

Might be wise to analyze how rust-mqtt et al. do it.

rust-mqtt is not supporting simultaneous listen and publish - method corresponding for listening is taking &mut self and is not cancel safe, i. e. you cannot stop listening (not polling anymore) via select etc, because data might be lost.

But rumqttc, while an std+tokio based library, is doing things quite well. They also do have an async method that should be continuously polled, but they have an eventloop based structure, so requests to publish a message a queued and do not interfere with reading, which is done via Framed tokio util to keep cancellation safety.

Ddystopia commented 5 months ago

with minimal code duplication

One option may be to go with async only - blocking is very plarform specific, while asynchronism is a language level concept, independent of os etc. If user would want a sync version, it is only a matter of semaphore to block on future. There is std crate pollster that uses a condvar, but it is trivial to make something like that for custom embedded use case and without any heap allocations.