1
0

Fix InFlightGauge typing to allow upgrading to prometheus_client 0.24 (#19379)

Fixes #19375 

`prometheus_client` 0.24 makes `Collector` a generic type. 
Previously, `InFlightGauge` inherited from both `Generic[MetricsEntry]`
and `Collector`, resulting in the error `TypeError: cannot create a
consistent MRO` when using `prometheus_client` >= 0.24. This behaviour
of disallowing multiple `Generic` inheritance is more strictly enforced
starting with python 3.14, but can still lead to issues with earlier
versions of python.

This PR separates runtime and typing inheritance for `InFlightGauge`:
- Runtime: `InFlightGauge` inherits only from `Collector`
- Typing: `InFlightGauge` is generic

This preserves static typing, avoids MRO conflicts, and supports both
`prometheus_client` <0.24 and >=0.24.

I have tested these changes out locally with `prometheus_client` 0.23.1
& 0.24 on python 3.14 while sending a bunch of messages over federation
and watching a grafana dashboard configured to show
`synapse_util_metrics_block_in_flight_total` &
`synapse_util_metrics_block_in_flight_real_time_sum` (the only metric
setup to use `InFlightGauge`) and things are working in each case.
a1e9abc7df/synapse/util/metrics.py (L112-L119)

### Pull Request Checklist

<!-- Please read
https://element-hq.github.io/synapse/latest/development/contributing_guide.html
before submitting your pull request -->

* [X] Pull request is based on the develop branch
* [X] Pull request includes a [changelog
file](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#changelog).
The entry should:
- Be a short description of your change which makes sense to users.
"Fixed a bug that prevented receiving messages from other servers."
instead of "Moved X method from `EventStore` to `EventWorkerStore`.".
  - Use markdown where necessary, mostly for `code blocks`.
  - End with either a period (.) or an exclamation mark (!).
  - Start with a capital letter.
- Feel free to credit yourself, by adding a sentence "Contributed by
@github_username." or "Contributed by [Your Name]." to the end of the
entry.
* [X] [Code
style](https://element-hq.github.io/synapse/latest/code_style.html) is
correct (run the
[linters](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#run-the-linters))
This commit is contained in:
Devon Hudson
2026-01-16 20:35:30 +00:00
committed by GitHub
parent cb376ee73b
commit 8b36740bad
5 changed files with 44 additions and 6 deletions

1
changelog.d/19379.bugfix Normal file
View File

@@ -0,0 +1 @@
Fix `InFlightGauge` typing to allow upgrading to `prometheus_client` 0.24.

View File

@@ -133,6 +133,7 @@ prometheus_metric_fullname_to_label_arg_map: Mapping[str, ArgLocation | None] =
"prometheus_client.metrics.Info": ArgLocation("labelnames", 2),
"prometheus_client.metrics.Enum": ArgLocation("labelnames", 2),
"synapse.metrics.LaterGauge": ArgLocation("labelnames", 2),
"synapse.metrics._InFlightGaugeRuntime": ArgLocation("labels", 2),
"synapse.metrics.InFlightGauge": ArgLocation("labels", 2),
"synapse.metrics.GaugeBucketCollector": ArgLocation("labelnames", 2),
"prometheus_client.registry.Collector": None,

View File

@@ -27,6 +27,8 @@ import platform
import threading
from importlib import metadata
from typing import (
TYPE_CHECKING,
Any,
Callable,
Generic,
Iterable,
@@ -262,8 +264,12 @@ shutdown.
MetricsEntry = TypeVar("MetricsEntry")
class InFlightGauge(Generic[MetricsEntry], Collector):
"""Tracks number of things (e.g. requests, Measure blocks, etc) in flight
class _InFlightGaugeRuntime(Collector):
"""
Runtime class for InFlightGauge. Contains all actual logic.
Does not inherit from Generic to avoid method resolution order (MRO) conflicts.
Tracks number of things (e.g. requests, Measure blocks, etc) in flight
at any given time.
Each InFlightGauge will create a metric called `<name>_total` that counts
@@ -292,16 +298,20 @@ class InFlightGauge(Generic[MetricsEntry], Collector):
# Create a class which have the sub_metrics values as attributes, which
# default to 0 on initialization. Used to pass to registered callbacks.
self._metrics_class: type[MetricsEntry] = attr.make_class(
self._metrics_class = attr.make_class(
"_MetricsEntry",
attrs={x: attr.ib(default=0) for x in sub_metrics},
slots=True,
)
# Counts number of in flight blocks for a given set of label values
self._registrations: dict[
tuple[str, ...], set[Callable[[MetricsEntry], None]]
] = {}
# `Callable` should be of type `Callable[[MetricsEntry], None]`, but
# `MetricsEntry` has no meaning in this context without the higher level
# `InFlightGauge` typing information.
# Instead, the typing is enforced by having `_registrations` be private and all
# accessor functions have proper `Callable[[MetricsEntry], None]` type
# annotations.
self._registrations: dict[tuple[str, ...], set[Callable[[Any], None]]] = {}
# Protects access to _registrations
self._lock = threading.Lock()
@@ -398,6 +408,17 @@ class InFlightGauge(Generic[MetricsEntry], Collector):
yield gauge
if TYPE_CHECKING:
class InFlightGauge(_InFlightGaugeRuntime, Generic[MetricsEntry]):
"""
Typing-only generic wrapper.
Provides InFlightGauge[T] support to type checkers.
"""
else:
InFlightGauge = _InFlightGaugeRuntime
class GaugeHistogramMetricFamilyWithLabels(GaugeHistogramMetricFamily):
"""
Custom version of `GaugeHistogramMetricFamily` from `prometheus_client` that allows

View File

@@ -19,6 +19,13 @@
#
#
# These imports are necessary for python <= 3.13 in order for the `InFlightGauge` type
# annotations not to be evaluated at runtime.
# Starting with python 3.14, annotations are lazily evaluated by default, which is the
# behaviour we desire.
# More info here: https://docs.python.org/3/reference/compound_stmts.html#annotations
from __future__ import annotations
import logging
from functools import wraps
from types import TracebackType

View File

@@ -18,6 +18,14 @@
# [This file includes modifications made by New Vector Limited]
#
#
# These imports are necessary for python <= 3.13 in order for the `InFlightGauge` type
# annotations not to be evaluated at runtime.
# Starting with python 3.14, annotations are lazily evaluated by default, which is the
# behaviour we desire.
# More info here: https://docs.python.org/3/reference/compound_stmts.html#annotations
from __future__ import annotations
from typing import NoReturn, Protocol
from prometheus_client.core import Sample