Fix MQTT retained message not being re-dispatched#12004
Fix MQTT retained message not being re-dispatched#12004balloob merged 21 commits intohome-assistant:devfrom
Conversation
There was a problem hiding this comment.
I'm using wanted_topics here, because it wouldn't make any sense to call the paho-mqtt subscribe method as it will soon be called anyway.
There was a problem hiding this comment.
I don't know too much about Home Assistant's style preferences concerning usage of tuples. Creating a namedtuple/struct/... here would probably be unnecessary since it's only really used in a single other place.
|
Easy to reproduce with a test:
|
balloob
left a comment
There was a problem hiding this comment.
You wrote the one and only correct solution to this problem in #12003: don't re-use subscriptions but let paho-mqtt handle this.
If we handle it ourselves instead of paho-mqtt, we need to duplicate ALL retain logic that paho-mqtt already contains. That's not our responsibility.
|
@balloob You're right - at first when I tried the "clean" solution with the paho client, I think I saw a subscription not being re-sent and I didn't look into it any further. Though now I've tested this in more detail and the clean solution seems to work fine. |
balloob
left a comment
There was a problem hiding this comment.
How is unsubscription handled on the server? If we subscribe twice to the same topic, do we need to unsubscribe twice to not receive messages anymore? Or do we still need to handle that inside Home Assistant?
Right now if we subscribe a second time to the same topic, the retained message will also be sent to the existing subscriber. That is not correct.
|
@balloob Good catch! Looking at the code, this probably also means that, in the current Home Assistant, if we make two consecutive subscriptions to a topic and one of them is then unsubscribed, the remaining subscription is not re-established on a reconnect. I will look into this tomorrow. |
|
Correct, that is also a bug. Good thing that we don't unsubscribe a lot yet (will come when we start adding "unload component" functionality. Can you add some test cases to test these situations? |
|
I've tried to characterize
1. Unsubscribe behavior
For example, if we do 2. Message callback behavior
So even though 3. Subscription behavior
This is caused by 1&2. Comments
Is this really a problem? I mean, currently all retained messages are re-sent to all subscribers on My idea right now would have been to only allow unsubscribing through the return value of Test cases: Working on that right now. Unfortunately I know very little of python's unittest test framework so I first need to get a hang of it. |
There was a problem hiding this comment.
line too long (80 > 79 characters)
|
I guess it's not a problem for retained messages to be sent twice. It should not happen too often. |
|
That's good. I now also added some test cases that should cover the previous bug(s) - please tell me if I should change something there for example how I used the mocks. |
There was a problem hiding this comment.
Please don't include these "style" fixes in PRs. It makes it difficult to see what this PR actually adds.
If you want to do those, do it in a separate PR. Just know that generally, style fixes best case will make the code more readable, worst case introduce bugs that didn't exist before.
There was a problem hiding this comment.
For this PR it's fine, just don't do it in the future please.
There was a problem hiding this comment.
Sure. I'll try to remember this.
There was a problem hiding this comment.
Use a guard clause to save on indentation:
if subscriptions:
return
# Only unsubscribe …There was a problem hiding this comment.
It's messy that now we have 2 places where subscriptions live, that feels weird.
Probably out of scope, but it would almost make more sense to get rid of the dispatcher and move topic matching and calling listeners into our MQTT client.
Something like this:
sub_info = namedtuple('SubscriptionInfo', 'callback,qos,topic,encoding')
for sub in self._subscriptions.items():
if not _match_topic(sub.topic, topic):
return
…
self.hass.async_run_job(sub.callback, topic, payload, qos)There was a problem hiding this comment.
Yes, it's probably a good time to clean up the subscription model a bit. 🎨
There was a problem hiding this comment.
Silent failure (not raising when something is wrong) is usually a source of future bugs. The application clearly got into a wrong state or has a bug if we try to unsubscribe a subscription twice.
I know the old code did it too, but I think we should remove it. What do you think?
There was a problem hiding this comment.
You're completely right - this has annoyed me with other components too so I don't know why I repeated this mistake. It's definitely better to fail fast than to have some unexpected behavior around - so I suppose raising an exception would be good to indicate a failed state.
Anyway, this could also be fixed by introducing a Subscription object as you mentioned. Working on that 🛠
|
Your tests look great 👍 |
|
Thanks for the detailed review! |
"Try to avoid brackets and additional quotes around the output to make it easier for users to parse the log." - https://home-assistant.io/developers/development_guidelines/
Tests still need to be updated
... And fix issues Accessing the config manually at runtime isn't ideal
* Updated usage of Mocks * Moved tests that were testing subscriptions out of the MQTTComponent test, because of how mock.patch was used * Adjusted the remaining tests for the MQTT clients new behavior - e.g. self.progress was removed * Updated the async_fire_mqtt_message helper
* Re-introduce the MQTT subscriptions through the dispatcher for tests - quite ugly though... 🚧 * Update fixtures to use our new MQTT mock 🎨
Apparently test_mqtt_json.py and test_mqtt_template.py were written on Windows. In order to not mess up the diff, I'll just redo the carriage return.
What's very interesting is that 3.4 didn't fail on travis...
1f4a349 to
997b09d
Compare
|
Done 🎉 |
Description:
Fix MQTT retained message not being re-dispatched. See #12003 for a detailed explanation.
Fixes #12003
Unfortunately, I have very limited knowledge of the test framework Home Assistant uses and I haven't found a way to create tests for this PR - if somebody with more knowledge could help out that would be great.
Checklist:
If the code communicates with devices, web services, or third-party tools: