fix: close Audio Router self-echo PUBACK/subscription-delivery race
publish_router_mode registered its self-echo correlation marker before publishing but removed it in a finally block the instant the QoS 1 publish() call observed its PUBACK. A broker may legally deliver a Controller's own subscription copy of that same publish after the PUBACK, since MQTT does not order the two; when that happened, the Controller mistook its own late echo for live operator intent and relinquished automatic restoration ownership. Keep the marker claimable for a short, bounded mqtt.router_echo_grace_seconds window (default 2s) after the PUBACK instead of clearing it immediately. Genuine external/operator commands are unaffected: once the grace window elapses, a matching payload is still correctly treated as operator intent and ownership is relinquished as before. Verified with deterministic fake-clock unit tests plus a real Mosquitto broker integration test (tests/test_router_echo_mosquitto_integration.py, skipped automatically when Docker is unavailable).
This commit is contained in:
@@ -33,6 +33,7 @@ class MqttConfig:
|
||||
reconnect_max_seconds: int = 30
|
||||
command_queue_size: int = 512
|
||||
publish_ack_timeout_seconds: float = 2.0
|
||||
router_echo_grace_seconds: float = 2.0
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -219,6 +220,11 @@ def load_config(
|
||||
"mqtt.publish_ack_timeout_seconds",
|
||||
minimum=0.1,
|
||||
),
|
||||
router_echo_grace_seconds=_number(
|
||||
mqtt_raw.get("router_echo_grace_seconds", 2.0),
|
||||
"mqtt.router_echo_grace_seconds",
|
||||
minimum=0.0,
|
||||
),
|
||||
)
|
||||
if mqtt.reconnect_max_seconds < mqtt.reconnect_min_seconds:
|
||||
raise ConfigError("mqtt.reconnect_max_seconds must be >= reconnect_min_seconds")
|
||||
|
||||
Reference in New Issue
Block a user