Bound MQTT broker operations with a per-node timeout

Without one, aiomqtt's disconnect acknowledgement has no deadline, so a
subscriber cancelled while its socket is dead never finishes unwinding and
teardown abandons the task. The knob is per node because brokers differ.
This commit is contained in:
2026-08-28 11:46:40 +02:00
parent 93e45274d0
commit 192999f178
2 changed files with 46 additions and 0 deletions
+16
View File
@@ -83,6 +83,8 @@ class MqttNode(Node):
- ``qos`` (int): Quality of Service level 0, 1, or 2 (default: 0)
- ``retain`` (bool): Retain flag for published messages (default: False)
- ``keepalive`` (int): Keepalive interval in seconds (default: 60)
- ``timeout`` (float): Deadline for a broker operation in seconds
(default: 10.0)
:type params: dict
:param name: Optional name for the node.
:type name: str | None
@@ -151,6 +153,7 @@ class MqttNode(Node):
"qos",
"retain",
"keepalive",
"timeout",
"json_keys",
"_topic_to_ports",
"_wildcards",
@@ -175,6 +178,15 @@ class MqttNode(Node):
qos: int = 0
retain: bool = False
keepalive: int = 60
# Bounds every broker operation: subscribe, publish, and the
# disconnect acknowledgement on the way out. Without one a client
# whose socket died waits for that ack forever, and the task never
# finishes unwinding. Brokers differ, so it is per node.
timeout: float = Field(
default=10.0,
gt=0,
description="Give up on a broker operation after this many seconds.",
)
# Which key to lift out of a JSON object payload. A device that wraps
# its reading — Victron's ``{"value": 5}`` — is otherwise a Python node
# per port. One key for every port, or a per-port mapping.
@@ -247,6 +259,7 @@ class MqttNode(Node):
self.qos = cfg.qos
self.retain = cfg.retain
self.keepalive = cfg.keepalive
self.timeout = cfg.timeout
# Runtime state
self._subscription_task: asyncio.Task[None] | None = None
@@ -372,6 +385,7 @@ class MqttNode(Node):
password=self.password,
identifier=self.client_id,
keepalive=self.keepalive,
timeout=self.timeout,
) as client:
self.report_health("ok")
while True:
@@ -393,6 +407,7 @@ class MqttNode(Node):
password=self.password,
identifier=self.client_id,
keepalive=self.keepalive,
timeout=self.timeout,
) as client:
await self._publish_with(client, data)
@@ -556,6 +571,7 @@ class MqttNode(Node):
password=self.password,
identifier=self.client_id,
keepalive=self.keepalive,
timeout=self.timeout,
) as client:
# Subscribe to every unique topic
for topic in self._topic_to_ports: