Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enforce loop timeout to be bigger than socket timeout #200

Merged
merged 4 commits into from
Jan 29, 2024

Conversation

vladak
Copy link
Contributor

@vladak vladak commented Jan 26, 2024

The timeout argument of loop() depends on the socket timeout. With some extra debug logging:

diff --git a/adafruit_minimqtt/adafruit_minimqtt.py b/adafruit_minimqtt/adafruit_minimqtt.py
index 2ec0100..98f68cf 100644
--- a/adafruit_minimqtt/adafruit_minimqtt.py
+++ b/adafruit_minimqtt/adafruit_minimqtt.py
@@ -1074,12 +1074,16 @@ class MQTT:
             try:
                 res = self._sock_exact_recv(1)
             except self._socket_pool.timeout:
+                self.logger.debug(f"socket timeout [1]: {self.get_monotonic_time()}")
                 return None
         else:  # socketpool, esp32spi
             try:
                 res = self._sock_exact_recv(1)
             except OSError as error:
                 if error.errno in (errno.ETIMEDOUT, errno.EAGAIN):
+                    self.logger.debug(
+                        f"socket timeout [2]: {self.get_monotonic_time()}"
+                    )
                     # raised by a socket timeout if 0 bytes were present
                     return None
                 raise MMQTTException from error
@@ -1164,7 +1168,9 @@ class MQTT:
             # CPython/Socketpool Impl.
             rc = bytearray(bufsize)
             mv = memoryview(rc)
+            self.logger.debug(f"-> recv_into: {self.get_monotonic_time()}")
             recv_len = self._sock.recv_into(rc, bufsize)
+            self.logger.debug(f"{recv_len} <- recv_into: {self.get_monotonic_time()}")
             to_read = bufsize - recv_len
             if to_read < 0:
                 raise MMQTTException(f"negative number of bytes to read: {to_read}")
@@ -1180,6 +1186,7 @@ class MQTT:
                     )
         else:  # ESP32SPI Impl.
             # This will timeout with socket timeout (not keepalive timeout)
+            self.logger.debug(f"-> recv: {self.get_monotonic_time()}")
             rc = self._sock.recv(bufsize)
             if not rc:
                 self.logger.debug("_sock_exact_recv timeout")

The following code:

#!/usr/bin/env python3

import adafruit_minimqtt.adafruit_minimqtt as MQTT
import ssl
import adafruit_logging as logging
import socketpool
import wifi

try:
    from secrets import secrets
except ImportError:
    print(
        "WiFi credentials are kept in secrets.py, please add them there!"
    )
    raise


def connect_hook(client, user_data, result, code):
    print(f"Connect: {user_data} {result} {code}")


def main():
    # logging.basicConfig()
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)

    # Connect to Wi-Fi
    logger.info("Connecting to wifi")
    wifi.radio.connect(secrets["ssid"], secrets["password"], timeout=10)
    logger.info(f"Connected to {secrets['ssid']}")
    logger.debug(f"IP: {wifi.radio.ipv4_address}")

    # Create a socket pool
    pool = socketpool.SocketPool(wifi.radio)  # pylint: disable=no-member

    broker = "172.40.0.3"
    port = 1883

    mqtt_client = MQTT.MQTT(
        broker=broker,
        port=port,
        socket_pool=pool,
        ssl_context=ssl.create_default_context(),
	socket_timeout=3,
    )

    mqtt_client.logger = logger

    mqtt_client.on_connect = connect_hook
    mqtt_client.connect()

    while True:
        try:
            logger.debug("loop")
            mqtt_client.loop(0.5)
        except UnicodeError:
            pass
        except OSError as e:
            if e.errno == errno.ETIMEDOUT:
                logger.info("got timeout")
                pass
            else:
                raise

try:
    main()
except Exception as e:
    print(f"Got exception: {e}")

running on Adafruit CircuitPython 8.0.0-beta.4 on 2022-10-30; Adafruit QT Py ESP32S2 with ESP32S2 produces this output:

4076.899: INFO - Connecting to wifi
4081.922: INFO - Connected to XXX
4081.924: DEBUG - IP: 172.40.0.20
4081.929: DEBUG - Attempting to connect to MQTT broker (attempt #0)
4081.931: DEBUG - Attempting to establish MQTT connection...
4081.934: INFO - Establishing an INSECURE connection to 172.40.0.3:1883
4084.936: DEBUG - Sending CONNECT to broker...
4084.938: DEBUG - Fixed Header: bytearray(b'\x10\x13')
4084.940: DEBUG - Variable Header: bytearray(b'\x00\x04MQTT\x04\x02\x00<')
4084.946: DEBUG - Receiving CONNACK packet from broker
4084.950: DEBUG - -> recv_into: 4084.95
4084.954: DEBUG - 1 <- recv_into: 4084.95
4084.958: DEBUG - Got message type: 0x20 pkt: 0x20
4084.960: DEBUG - -> recv_into: 4084.96
4084.963: DEBUG - 3 <- recv_into: 4084.96
Connect: None 0 0
4084.967: DEBUG - Resetting reconnect backoff
4084.968: DEBUG - loop
4084.971: DEBUG - waiting for messages for 0.5 seconds
4084.974: DEBUG - -> recv_into: 4084.97
4087.976: DEBUG - socket timeout [2]: 4087.98
4087.979: DEBUG - Loop timed out after 0.5 seconds
4087.979: DEBUG - loop
4087.982: DEBUG - waiting for messages for 0.5 seconds
4087.985: DEBUG - -> recv_into: 4087.99
4090.987: DEBUG - socket timeout [2]: 4090.99
4090.989: DEBUG - Loop timed out after 0.5 seconds
4090.991: DEBUG - loop
4090.993: DEBUG - waiting for messages for 0.5 seconds
4090.997: DEBUG - -> recv_into: 4091.0
4094.000: DEBUG - socket timeout [2]: 4094.0
4094.002: DEBUG - Loop timed out after 0.5 seconds
...

making this problem evident - the wait_for_msg() will return only after socket timeout which is bigger than the loop timeout, thus breaking the promise of loop() semantics when there is no traffic.

Copy link
Contributor

@FoamyGuy FoamyGuy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. I tested the new exception with a slightly modified version of the provided code.

Thank you @vladak

@FoamyGuy FoamyGuy merged commit e19ece6 into adafruit:main Jan 29, 2024
1 check passed
adafruit-adabot added a commit to adafruit/Adafruit_CircuitPython_Bundle that referenced this pull request Jan 30, 2024
Updating https://github.com/adafruit/Adafruit_CircuitPython_Wiznet5k to 5.0.6 from 5.0.5:
  > Merge pull request adafruit/Adafruit_CircuitPython_Wiznet5k#125 from FoamyGuy/recv_timeout

Updating https://github.com/adafruit/Adafruit_CircuitPython_HTTPServer to 4.5.1 from 4.5.0:
  > Merge pull request adafruit/Adafruit_CircuitPython_HTTPServer#76 from michalpokusa/routes-refactor

Updating https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT to 7.5.8 from 7.5.6:
  > Merge pull request adafruit/Adafruit_CircuitPython_MiniMQTT#199 from vladak/loop_vs_keep_alive
  > Merge pull request adafruit/Adafruit_CircuitPython_MiniMQTT#200 from vladak/loop_timeout_vs_socket_timeout
  > Merge pull request adafruit/Adafruit_CircuitPython_MiniMQTT#184 from rjauquet/rej-fix-loop-blocking

Updating https://github.com/adafruit/Adafruit_CircuitPython_PIOASM to 1.0.0 from 0.8.2:
  > Merge pull request adafruit/Adafruit_CircuitPython_PIOASM#61 from Gebhartj/Gebhartj-patch-1

Updating https://github.com/adafruit/Adafruit_CircuitPython_Bundle/circuitpython_library_list.md to NA from NA:
  > Updated download stats for the libraries
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants