Skip to content

optimization: ServiceRegistry removal is O(n) per call because types/servers are lists #1781

@bluetoothbot

Description

@bluetoothbot

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions