Properly tear down group entities on membership changes and group removal - #849
Properly tear down group entities on membership changes and group removal#849TheJulianJES wants to merge 2 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #849 +/- ##
==========================================
+ Coverage 97.19% 97.20% +0.01%
==========================================
Files 57 57
Lines 10543 10558 +15
==========================================
+ Hits 10247 10263 +16
+ Misses 296 295 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@zigpy-review-bot Review and check if anything else is needed, or if this should be merged as is. Push any changes needed. Consider the linked HA Core PR home-assistant/core#162307 and this branch: home-assistant/core@dev...TheJulianJES:core:zigpy-bot/zha-group-devices. Check if that is complete as well. Do review rounds on both this PR and the HA Core PR, but with Opus agent (not Fable) and also with Copilot: 5.6 Sol. You can also push more changes to the linked HA Core branch, but do not force-push that one. If needed, create another branch based off that one. You can force-push this ZHA PR if needed though (or create other issues or PRs for other things discovered along the way). Do verify everything with review rounds before pushing. You can also update this PR description (but again: verify before). Also, is this PR required for the Core PR? |
…oval Group entity teardown was previously skipped in three places, leaking member entity subscriptions (member entities kept references to dead group entities and kept invoking their debounced updates): - discover_group_entities cleared group_entities with a bare .clear() when a group dropped below 2 members, without calling on_remove() or unregister_group_entity(), and never removed a platform's group entity when that platform alone dropped below 2 eligible members (the stale entity remained registered and functional with a single backing member) - Gateway.group_removed popped the group without calling Group.on_remove(), unlike shutdown() and device_removed() - Group.on_remove() only removed the group entities themselves, leaving the member entity subscriptions in _entity_unsubs dangling unregister_group_entity() now also verifies the exact entity instance is registered, so a delayed removal task cannot unregister a recreated group entity with the same unique id.
- `Group.on_remove()` now unregisters a group entity even when its own `on_remove()` raised, so `_group_entities` and `_entity_unsubs` cannot desync (a later `unregister_group_entity()` used to raise `KeyError`) - `unregister_group_entity()` tolerates a missing unsubscribe callback - iterate eligible platforms in a deterministic order - add tests that actually exercise `Gateway.group_removed`, the `unregister_group_entity()` identity check, the member-subscription cleanup in `Group.on_remove()`, and its entity-failure path
361987c to
f450763
Compare
|
Reviewed both, pushed changes to both, and ran review rounds until clean — an Opus agent ( Your three questionsIs this PR required for the Core PR? No. Home Assistant rebuilds its group entity set from the Should this be merged as is? It needed three fixes first, which are now pushed. With those, both reviewers call it ready. Is the HA Core branch complete? It wasn't — it had two hard breaks against current #849 — rebased onto
|
Group entity teardown was skipped in several places, leaking member entity subscriptions: member entities kept references to dead group entities and kept invoking their debounced updates.
What was broken
discover_group_entities()clearedgroup_entitieswith a bare.clear()when a group dropped below 2 members, without callingon_remove()orunregister_group_entity()— so theDebouncerand every member subscription stayed alive.Gateway.group_removed()popped the group without callingGroup.on_remove(), unlikeshutdown()anddevice_removed().Group.on_remove()only removed the group entities themselves, leaving the member entity subscriptions in_entity_unsubsdangling.What this changes
discover_group_entities()now tears stale group entities down properly —unregister_group_entity()plus a scheduledon_remove()— both when the whole group drops below 2 members and when a single platform loses eligibility. Coordinator members are already excluded from the per-platform counts, so a "coordinator + one light" group now correctly loses its group entity too.Gateway.group_removed()schedulesGroup.on_remove(), so removing a group tears down its entities even when the members were not removed first.Group.on_remove()releases any remaining member entity subscriptions, and unregisters a group entity even if that entity's ownon_remove()raised — otherwise_group_entitiesand_entity_unsubscould desync and a laterunregister_group_entity()would raiseKeyError.unregister_group_entity()verifies the exact entity instance is registered, so a delayed removal cannot unregister a recreated group entity that has the same unique id.Notes
_remove_device()does not remove the device's endpoints from groups, so nogroup_member_removedevent fires andGroup.memberskeeps its cached value. That is pre-existing and a different code path (Gateway.device_removed), so it is deliberately out of scope here.GroupInfo.entitiessnapshot on every group event, and that snapshot is correct either way. The two are complementary: without this PR, a platform that drops below two eligible members keeps a stale group entity that Home Assistant faithfully re-adds.