eclipse / paho.mqtt.python

paho.mqtt.python
Other
2.17k stars 723 forks source link

loop_write(), loop_misc(), loop_read(), none of these could not keep alive, loop() could. last will will send out. #667

Closed wasdee closed 2 years ago

wasdee commented 2 years ago

as you might know that, the lib recommend us to use loop_*() instead of loop()

def loop(self, timeout=1.0, max_packets=1):
        """Process network events.

        It is strongly recommended that you use loop_start(), or
        loop_forever(), or if you are using an external event loop using
        loop_read(), loop_write(), and loop_misc(). Using loop() on it's own is
        no longer recommended.

        This function must be called regularly to ensure communication with the
        broker is carried out. It calls select() on the network socket to wait
        for network events. If incoming data is present it will then be
        processed. Outgoing commands, from e.g. publish(), are normally sent
        immediately that their function is called, but this is not always
        possible. loop() will also attempt to send any remaining outgoing
        messages, which also includes commands that are part of the flow for
        messages with QoS>0.
        """

I try to incorporate loop_*() as much as possible in by code. However,

Reproduce

mqtt_client = Client()
mqtt_client.will_set( 'topicX', 'offline', 1, True)
mqtt_client.connect(keepalive=60) # takes 3 loop max
mqtt_client.publish('topicX', 'online', qos=1, retain=True)
mqtt_client.loop_write()

while True:
    sleep(20)
    # keep mqtt connection alive and not send last will
    mqtt_client.loop_write() 
    mqtt_client.loop_read() 
    mqtt_client.loop_misc() 

after 1-2 min, the will will send out, the client could not keep alive.

Workaround

mqtt_client = Client()
mqtt_client.will_set( 'topicX', 'offline', 1, True)
mqtt_client.connect(keepalive=60) # takes 3 loop max
mqtt_client.publish('topicX', 'online', qos=1, retain=True)
mqtt_client.loop_write()

while True:
    sleep(20)
    # keep mqtt connection alive 
    mqtt_client.loop() 

this will work as expect.

ralight commented 2 years ago

In your first example you aren't using an event loop, so you aren't responding to the requirements of the socket.

Please use one of:

mqtt_client = Client()
mqtt_client.will_set( 'topicX', 'offline', 1, True)
mqtt_client.connect(keepalive=60) # takes 3 loop max
mqtt_client.publish('topicX', 'online', qos=1, retain=True)

mqtt_client.loop_forever()
# This part is "never" reached

Or:

mqtt_client = Client()
mqtt_client.will_set( 'topicX', 'offline', 1, True)
mqtt_client.connect(keepalive=60) # takes 3 loop max
mqtt_client.publish('topicX', 'online', qos=1, retain=True)

mqtt_client.loop_start()

while True:
    time.sleep(20)
    # Do other things
wasdee commented 2 years ago

I have a real-time application (msec precision). I found that it is better to manage when the loop will happen since Python has GIL. unless other thread is IO-blocking, the callback or publish is not guaranteed.

@ralight do you have recommendation for this?

I use asyncio in my code. but i don't put it here since I don't think it is applicable at first. Basically, if I want to use loop*(). you suggest to follow this example, right?

ralight commented 2 years ago

In that case I would take a look at this example: https://github.com/eclipse/paho.mqtt.python/blob/master/examples/loop_asyncio.py

wasdee commented 2 years ago

Thank you for your advice.

I would use https://github.com/sbtinstruments/asyncio-mqtt . It looks zen to me. I tested, it works just fine.