From 69ea6fa56853323d26070e0b27b1d15a32c907bd Mon Sep 17 00:00:00 2001 From: Sean Kim Date: Thu, 9 Jul 2026 15:47:16 -0700 Subject: [PATCH] Remove nameless collectors from the registry on unregister When a collector is registered under support_collectors_without_names with no metric names, register() appends it to _collectors_without_names in addition to the usual bookkeeping. unregister() only cleaned up _collector_to_names and _names_to_collectors, so the collector stayed in _collectors_without_names and kept being collected. A normal collect() no longer returned the collector's metrics, but RestrictedRegistry.collect() -- which seeds its collector set from _collectors_without_names -- still did, so an unregistered collector's samples reappeared under restricted_registry(). unregister() now also drops the collector from that list. Add test_unregister_removes_no_names_collector, asserting a nameless collector is no longer collected by a restricted registry after it has been unregistered. Signed-off-by: Sean Kim --- prometheus_client/registry.py | 2 ++ tests/test_core.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/prometheus_client/registry.py b/prometheus_client/registry.py index d4cfc273..63f8ab03 100644 --- a/prometheus_client/registry.py +++ b/prometheus_client/registry.py @@ -61,6 +61,8 @@ def unregister(self, collector: Collector) -> None: for name in self._collector_to_names[collector]: del self._names_to_collectors[name] del self._collector_to_names[collector] + if collector in self._collectors_without_names: + self._collectors_without_names.remove(collector) def _get_names(self, collector): """Get names of timeseries the collector produces and clashes with.""" diff --git a/tests/test_core.py b/tests/test_core.py index f339b6df..b4d36d07 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -960,6 +960,21 @@ def test_unregister_works(self): registry.unregister(s) Gauge('s_count', 'help', registry=registry) + def test_unregister_removes_no_names_collector(self): + registry = CollectorRegistry(support_collectors_without_names=True) + + class NamelessCollector: + def collect(self): + return [GaugeMetricFamily('foo', 'help', value=42)] + + collector = NamelessCollector() + registry.register(collector) + registry.unregister(collector) + # A nameless collector must be removed from the collectors-without-names + # list too, otherwise a restricted registry keeps collecting it after it + # was unregistered. + self.assertEqual([], list(registry.restricted_registry(['foo']).collect())) + def custom_collector(self, metric_family, registry): class CustomCollector: def collect(self):