Problem
self.types: dict[str, list] and self.servers: dict[str, list] store the per-key membership as Python lists. _remove calls self.types[old_service_info.type.lower()].remove(info.key) and self.servers[old_service_info.server_key].remove(info.key), each of which is an O(n) linear scan. For deployments registering many services under the same _type._tcp.local. (e.g. Home Assistant printing ~100 devices, or Avahi-style mass advertisements), bulk async_remove([...]) of N services degrades to O(N²) — measurable at shutdown / restart, and exactly the moment users notice slow teardown. The _add side is fine (setdefault(...).append(...) is O(1)), so the asymmetry is purely on the removal hot path. Also: when the last entry under a type / server is removed, the empty list lingers in the dict forever (no del self.types[...] cleanup), so long-lived Zeroconf instances with churning type names grow these dicts unboundedly.
Why This Matters
The registry sits behind every service announcement and removal; quadratic removal is a real latency hit during shutdowns and at any large-scale rotation. The dict-leak is small but unbounded in the wrong workload.
Suggested Fix
Switch the value type to dict[str, set[str]] (or dict[str, dict[str, None]] if insertion-order iteration matters anywhere — check call sites of async_get_infos_type / async_get_infos_server). _add becomes self.types.setdefault(...).add(info.key); _remove becomes bucket = self.types[key]; bucket.discard(info.key); if not bucket: del self.types[key]. The same change for self.servers. Both set.discard and dict-key removal are O(1). _async_get_by_index keeps returning a list[ServiceInfo] — just iterate the set.
Details
|
|
| Severity |
🟡 Medium |
| Category |
optimization |
| Location |
src/zeroconf/_services/registry.py:38-112 |
| Effort |
⚡ Quick fix |
🤖 Created by Kōan from audit session
Problem
self.types: dict[str, list]andself.servers: dict[str, list]store the per-key membership as Python lists._removecallsself.types[old_service_info.type.lower()].remove(info.key)andself.servers[old_service_info.server_key].remove(info.key), each of which is an O(n) linear scan. For deployments registering many services under the same_type._tcp.local.(e.g. Home Assistant printing ~100 devices, or Avahi-style mass advertisements), bulkasync_remove([...])of N services degrades to O(N²) — measurable at shutdown / restart, and exactly the moment users notice slow teardown. The_addside is fine (setdefault(...).append(...)is O(1)), so the asymmetry is purely on the removal hot path. Also: when the last entry under a type / server is removed, the empty list lingers in the dict forever (nodel self.types[...]cleanup), so long-livedZeroconfinstances with churning type names grow these dicts unboundedly.Why This Matters
The registry sits behind every service announcement and removal; quadratic removal is a real latency hit during shutdowns and at any large-scale rotation. The dict-leak is small but unbounded in the wrong workload.
Suggested Fix
Switch the value type to
dict[str, set[str]](ordict[str, dict[str, None]]if insertion-order iteration matters anywhere — check call sites ofasync_get_infos_type/async_get_infos_server)._addbecomesself.types.setdefault(...).add(info.key);_removebecomesbucket = self.types[key]; bucket.discard(info.key); if not bucket: del self.types[key]. The same change forself.servers. Bothset.discardand dict-key removal are O(1)._async_get_by_indexkeeps returning alist[ServiceInfo]— just iterate the set.Details
src/zeroconf/_services/registry.py:38-112🤖 Created by Kōan from audit session