Commit Graph
179 Commits
Author SHA1 Message Date
Erik JohnstonandClaude Opus 5.5 ef18e9ebc7 Build a new room's events in one Rust call
Behind `experimental_features.rust_room_creation: on`, the room handler
collects the events that follow the create event (the creator's join,
power levels, join rules, history visibility, the initial state, name,
topic) as `(type, state_key, content)`, and `LocalEventSigner` builds,
hashes and signs them all in one call. Each event's prev event, depth
and auth events follow from the one before, as `create_event` gives
them for a batch.

Python keeps every check: the requester checks ran when the create
event was built, and each built event still goes through
`validate_builder`, `validate_new` and the relation check before
joining the batch, which persists through `handle_new_client_event` as
before. Invites are still built by `create_event`.

Rooms the Rust builder does not cover are built in Python and counted
as fallbacks: v1 and v2 rooms, MSC4242 state DAG rooms, appservice
senders, modules with `check_event_allowed`, initial `m.room.aliases`
events, and `m.call.invite` or `m.room.create` in the initial state. If
Rust fails, the events are built in Python. If Python rejects them too,
the request was bad and gets Python's error. Otherwise it counts as a
Rust error. In tests,
`SYNAPSE_TEST_RUST_ROOM_CREATION=on` turns the builder on and makes a
failure fail the test.

Python orders auth events by iterating a set, so the order changes with
the process's hash seed. Rust keeps the order of the auth types. A test
creates rooms of several configurations and room versions in Python,
rebuilds their events in Rust from the same inputs, and compares them
with the auth events taken as a set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PVLsNh6akt4ifEizY4D1ta
2026-10-18 17:45:44 +00:00
Erik JohnstonandClaude Opus 5.5 9b6e901fa5 Write batched state groups in Rust on the native driver
Rust could read state groups but not write them. A new
`store_state_group_chain` on the `RustStore` writes the chain
`store_state_deltas_for_batched` stores: an optional full group for the
state before the first event, then one delta group per state event. It
runs in one native transaction, allocating from `state_group_id_seq` as
Python does. A `prev_group` is locked against deletion and its pending
deletion pushed back, and a missing one fails with Python's message.

When the state groups share the main database and that uses the native
driver, `store_state_deltas_for_batched` calls it instead of running a
Python transaction. The context bookkeeping and the cache prefill are
shared by both paths. Room creation, and anything else sending a batch,
saves one Python transaction.

A twin test stores the same batches through both writers and compares
the rows and the groups the contexts get.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PVLsNh6akt4ifEizY4D1ta
2026-10-18 17:24:19 +00:00
Erik JohnstonandClaude Opus 5.5 4dd87ff58a Add /createRoom benchmarks and fix the template DB under the Rust driver
`tests/rest/client/bench_createroom.py` times room creation per room in
the trial harness, with optional cProfile dumps and per-room counts of
database interactions and SQL statements. It skips unless
`SYNAPSE_BENCH_CREATEROOM` is set.

`scripts-dev/createroom_bench.py` times it against a real server, reusing
`persist_bench.Server`. That server now lifts the room-creation rate limit
and can run on psycopg2 instead of the native driver.

The test template database's maintenance connection now always uses
psycopg2, as `setup_test_homeserver` does. The Rust driver's `postgres`
module has no `connect`, so it failed under `SYNAPSE_TEST_RUST_DRIVER=1`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PVLsNh6akt4ifEizY4D1ta
2026-10-18 16:09:03 +00:00
Erik JohnstonandClaude Fable 5.1 cac962a4a7 Keep the test reactor's pending calls sorted incrementally
`twisted.internet.task.Clock` re-sorts its whole list of pending calls on every
`callLater` and after every call it runs. A test homeserver has dozens of
pending calls and the threadless database pool schedules a `callLater(0, ...)`
per transaction, so this showed up as ~10% of a test's time in profiles.

Insert new calls in order with `bisect.insort` and only re-sort after a call
was rescheduled with `reset` or `delay`.

Together with the previous commit, `tests.rest.client.test_rooms` goes from
44.9s to 32.7s and `tests.handlers` from 146s to 114s single-process on
SQLite; a 147-test Postgres subset from 85.9s to 74.4s.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PVLsNh6akt4ifEizY4D1ta
2026-10-18 13:14:42 +00:00
Erik JohnstonandClaude Fable 5.1 5815f79a73 Apply the background updates to the test template database
Every test homeserver runs the background updates when it starts, and with a
fresh copy of the template database there are dozens to run: 29ms per test
on SQLite and 99ms on Postgres, against a median test time of ~140ms.

Run them once per process on a throwaway homeserver and copy the result back
over the template (`backup()` for the in-memory SQLite template, `CREATE
DATABASE ... TEMPLATE` for the Postgres base database), so each test's
homeserver finds nothing left to do.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PVLsNh6akt4ifEizY4D1ta
2026-10-18 13:14:42 +00:00
Erik JohnstonandClaude Fable 5.1 8609f5e8d7 Free each test's homeserver in the per-test gen-0 collection
Trial worker memory grew steadily over a run because `HomeServer`s outlived
their test. Each one is a ~10k-object reference cycle; GC is disabled while a
test runs and the tearDown only does a gen-0 collection, which frees the
homeserver only if nothing older still points at it. Four things did:

- `ThreadedMemoryReactorClock.__init__` installs `tls._get_default_clock =
  lambda: self` into the `twisted.protocols.tls` module. That held the test
  reactor, whose never-removed shutdown triggers hold handlers, and so the
  homeserver, until the next test's reactor replaced it. Hold the reactor
  weakly instead.
- `synapse.config.cache._CACHES` maps cache names to bound `set_cache_factor`
  methods, so an old homeserver lived until a later test registered a cache
  with the same name. Hold bound methods through `weakref.WeakMethod`.
- `_CacheContext._cache_context_objects` is a class-level `WeakValueDictionary`
  keyed on `(cache, key)`: the key strongly holds the cache, which holds the
  homeserver, for as long as any context exists. Key on `id(cache)`; the id
  cannot be reused while an entry exists since the context holds the cache.
- trial keeps finished `TestCase` instances around (for example in its list of
  skipped tests), and those keep `self.hs`, `self.store`, ... Drop every
  attribute a test added from setUp onwards before the gen-0 collection.

The fake reactor's `callWhenRunning` and shutdown hooks are emptied once the
homeserver has shut down: they never fire in tests, and the Rust runtime holds
the reactor through a reference the garbage collector cannot see, so a reactor
that still pointed at the handlers kept the homeserver alive.

With this the homeserver dies in the existing gen-0 collection. A full
collection per test was ruled out: it costs ~100ms per test. RSS over
`tests.rest.client.test_rooms` goes from 148→337 MB to 148→176 MB, and over
`tests.handlers` from 170→498 MB to 170→278 MB.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PVLsNh6akt4ifEizY4D1ta
2026-10-18 13:14:32 +00:00
Erik JohnstonandClaude Opus 5.5 832be1634d Merge erikj/rust-sss-queries into the Rust event persistence branch
Brings Rust sliding sync (erikj/rust-sss-queries) together with Rust
event persistence (erikj/rust-persist-validate). Both branch from
a78b02b55c.

The conflicts were both sides adding parallel entries:
- the Complement and test environment switches
- the `RustHandlers` fields and getters
- the `_BlockingRustCalls` types

These keep both. `RustHandlers` builds the persister and the sliding sync
handler in one pass over the native pool. `SyncReader::begin` keeps the
persist side's flag-before-BEGIN and the sliding sync side's transaction
count and `begin_write`. Both sides had added the `invite_room_state`
and `knock_room_state` accessors to `Unsigned`, so one copy is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PVLsNh6akt4ifEizY4D1ta
2026-10-18 10:54:17 +00:00
Erik JohnstonandClaude Opus 5.5 940c43b8fd Prepare chunks in Rust at the shadow and prepare levels
`Persister.prepare_chunk` runs the steps of the previous commits for a
chunk: the extremities and state delta, then the sliding sync table
changes and sticky events, then the chain cover index. The chain cover
step comes last as the only one that writes, so a failure in an earlier
step leaves nothing for Python to find. Sliding sync and sticky events
now come before the stream orderings are allocated, as nothing there
reads them. Each step reads the clock when it needs the time.

`_persist_event_batch` dispatches on `rust_event_persistence`:

- `shadow`: Rust runs first with its chain cover write rolled back, then
  Python. The results are compared and Python's are used. The comparison
  ignores orders Python leaves to chance and the IDs of new chains. Both
  rejecting the chunk with the same error is a match.
- `prepare` and above: Rust's results are used. A `SynapseError` is
  raised as Python would raise it. Python computes the chunk when Rust
  cannot resolve state in the room, or on any other Rust error.

Trial runs strict by default (`SYNAPSE_TEST_RUST_PERSIST_STRICT`), so a
mismatch or Rust error fails the test. Rust's counts and state res stats
are recorded by Python once it uses the result.

At `prepare`, the `_calculate_state_and_extrem` and
`calculate_chain_cover_index_for_events` timings give way to
`rust_prepare_chunk`. Python's state resolution and state group caches
are not filled, so creating a room costs one more transaction
(`RoomsCreateTestCase` counts it) and the first local event after a
fork resolves state again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXMkcZVkcZhi21augBff4a
2026-10-17 10:45:55 +00:00
Erik JohnstonandClaude Opus 5.5 a05a064643 Add the Rust event persister and what it builds on
Phase 0 of the event persistence plan, apart from the write transaction
helper:

- `experimental_features.rust_event_persistence`, one ordered level
  (off, shadow, prepare, transaction, full). Nothing uses the levels yet
  beyond building the persister.
- `RustHandlers.persister`: built on the events writer when the level is not
  off and the main database uses the native driver. It holds the pool, the
  shared caches and the events, backfill and cache stream trackers.
- `PersistEffects`, the Python cache updates a Rust transaction will ask for
  in place of `txn.call_after`, applied in one call by
  `DataStore._apply_rust_persist_effects`. A test persists batches through
  Python and checks that applying the matching effects updates the same
  caches. The chunk's stream IDs come back unfinished and are finished last,
  as Python's `get_next_mult` does after the transaction's callbacks, so
  the current token never passes an event the stream change caches don't
  know about.
- `PersistContext`, read from an `EventContext` once per event.
- `Event::to_persistence_value` and `EventInternalMetadata::to_persistence_value`:
  the `event_json` columns built without the GIL.
- Rust-side persistence counters, added to the Python metrics by a pre-collect
  hook. A homeserver can now register more than one such hook. The persisted
  position is reported only when it moved, so it can't pull back the gauge
  the Python path sets.
- `SYNAPSE_TEST_RUST_PERSIST` for trial and `RUST_PERSIST` for Complement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHKYPSc37kWqGACn9aKxKL
2026-10-16 13:06:06 +00:00
Erik JohnstonandClaude Opus 5.5 d63740fc30 Add the rust_sliding_sync option and a Rust SlidingSyncHandler
Phase 0 of the sliding sync port (renders/sliding-sync-rust-plan.md). No
behaviour change: nothing calls the Rust handler yet.

- `experimental_features.rust_sliding_sync: off | on`. There is no shadow
  mode. Booleans are accepted, as for `rust_sync`.
- `synapse_rust.sliding_sync.SlidingSyncHandler`, built by `RustHandlers`
  on the native driver only. It shares the v2 sync handler's `SyncStore`
  and carries the pool, deferred and blocking helpers the later phases'
  entry points use.
- `SlidingSyncStats`, observed per request into the
  `synapse_sliding_sync_rust_*` histograms. `SyncDbStats` now counts the
  transactions a reader begins.
- The `synapse_sliding_sync_rust_rooms_total` and
  `synapse_sliding_sync_rust_requests_total` counters.
- `SYNAPSE_TEST_RUST_SLIDING_SYNC` and `_STRICT` for trial, with the handler
  wrapped in `_BlockingRustCalls`, and `RUST_SLIDING_SYNC` for Complement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5b7Ruy4cgt27Mjq6Ptxbb
2026-10-16 10:19:27 +00:00
Erik JohnstonandClaude Opus 5.5 a78b02b55c Load events in Rust for Python too, on the native driver
With the main database on the native Postgres driver, every event load
goes through the Rust `EventLoader`. `EventsWorkerStore` sends the events
its own lookups miss to `RustStore.load_events`, and `_get_events_from_db`
to `reload_events`. Loads from Python, sync and state resolution then
coalesce in the shared event cache, and an invalidation during a load
stops it caching a stale entry. Python's fetch threads and in-flight map
stay only for other databases.

- `EventLoader::load` goes through `Cache::get_or_load_many`. The extra
  redaction events it reads are returned but no longer cached.
- `PoolQuerier` checks its connection out before claiming any loads, so
  readers that hold connections while they wait cannot starve a load.
- An unusable event raises `InvalidEventError` or
  `DatabaseCorruptionError`, as from Python, which some callers catch.
- Cancelling a Python caller leaves the load running for the others, and
  `evt_db_fetch_count` counts only what a caller read itself.
- `RustStore` holds the `DatabasePool` weakly and reads its pool per call.

Tests: trial makes the loads block, as it does Python's transactions
(`_BlockingRustCalls`, renamed from `_BlockingRustSyncHandler`). The
cancellation and outage tests get Rust variants. Two tests stop depending
on how far requests advance the clock or on which `DatabasePool` object
a worker store keeps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VpBQxvqZTUvHCMm3u6CR7J
2026-10-16 09:34:35 +00:00
Erik JohnstonandClaude Opus 5.5 b8d8d5e015 Fix review findings in the Rust transaction sender
- Only build the Rust sender from a real `RustFederationClient`. A test
  that mocks the federation client no longer fails with a `TypeError`.
- Log the `TX` and response lines under Python's logger names, so log
  configs that name those loggers still apply.
- Move the PDUs and EDUs into the request body, so a transaction holds
  one copy of them while it is retried, besides the canonical JSON.
- Drop the unused `time_now` from `Event::to_pdu_value`.
- Set the compare mode per `TransactionManager` in the tests that use
  it, not on the class.
- Test that the tracing headers reach the request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5b7Ruy4cgt27Mjq6Ptxbb
2026-10-15 14:54:44 +00:00
Erik JohnstonandClaude Opus 5.5 5aff3307b2 Send transactions through Rust at rust_federation_sending: transactions
At the new `transactions` level, `TransactionManager` hands each
transaction to the Rust sender. Python keeps the tracing span, the
module callback and the `last_sent_pdu_time` gauge.

In tests, every transaction body Rust builds is checked against the one
Python builds (`SYNAPSE_TEST_RUST_FED_STRICT`). The federation sender
and catch-up test cases also run at the new level, with Rust building
the transactions that their mocked `send_transaction` receives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5b7Ruy4cgt27Mjq6Ptxbb
2026-10-15 14:54:44 +00:00
Erik JohnstonandClaude Opus 5.5 32448196cc Drop the rust_sync gate on an unshared event cache
`rust_sync: on` was refused unless `rust_sync_allow_unshared_event_cache`
was set, because the Rust sync handler held a second copy of every event
it served. Python and Rust now share one event cache, so the gate and its
option go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VpBQxvqZTUvHCMm3u6CR7J
2026-10-15 12:14:54 +00:00
Erik JohnstonandClaude Opus 5.5 a28082a499 Dispatch sync rooms to Rust in rust_sync shadow and on modes
`on` serves each joined or archived room from the Rust handler. A room the
Rust handler cannot compute, or fails on, is computed by Python. Python's
fallback rooms share the Rust handler's lazy-loaded members caches, so a
room that moves between the two does not resend members.

`shadow` serves Python's entry and compares the Rust one with it, counting
the outcome in synapse_sync_rust_shadow_total. The two run at once, so the
response waits for the slower. Cancellation stops both.

Config parsing refuses `on` while the Rust and Python event caches are
separate, unless `rust_sync_allow_unshared_event_cache` is set. Under trial,
a Rust error or a shadow mismatch fails the test.

The call invite test now builds its events for the room's own version. An
event of another version is stored, but the Rust event loader cannot read
it back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194eNQP5weBtVMg8NKtc6VU
2026-10-14 23:14:41 +00:00
Erik JohnstonandClaude Fable 5.1 1abc57e826 Add the Rust sync handler skeleton behind experimental_features.rust_sync
`rust_sync` takes `off`, `shadow` or `on`, as `rust_state_res` does, and
`RustHandlers.sync` is built when the main database uses the native Rust
driver. The Python `SyncHandler` reads the option and takes the Rust
handler, warning and keeping the Python path when the driver is not the
Rust one. Nothing is served by Rust yet: the per-room entry points come
with the ports that follow.

Trial gets the same plumbing the state resolution has: `SYNAPSE_TEST_
RUST_SYNC` sets the option on every test homeserver, and the Rust handler
is wrapped so that each entry point's `_blocking` twin answers under the
fake reactor.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194eNQP5weBtVMg8NKtc6VU
2026-10-14 13:35:25 +00:00
Erik JohnstonandClaude Fable 5.1 2c379b5cb5 Resolve state groups in Rust, remembering each group's converted state
With the database work cached, converting the state sets from Python
dicts was the largest piece of a warm resolution: 5,010 entries pulled
out of tuples and interned, under the GIL, before the future starts,
and 2,505 entries built into a dict on the way back. Every group's
state was converted again on every resolution it took part in.

The handler gains resolve_state_groups, which takes the state groups
by ID with their state, as StateResolutionHandler.resolve_state_groups
receives them, and keeps each group's converted StateMap in a cache
keyed on the group ID. State groups are immutable and their IDs are
never reused, so an entry can only go stale by its group being
deleted, which makes it dead weight rather than wrong. The cache is
cleared on a room purge with the others. A group given with fewer
entries than its cached state is a filtered one: it is converted for
that call and does not replace the full map.

The result is returned in the shape of a _StateCacheEntry, chosen as
_make_state_cache_entry does: the input group the resolved state
equals, else the input group it differs least from and the difference,
else the state itself. So the common cases cross the boundary as a
group ID or a small delta rather than the whole state.

StateResolutionHandler.resolve_state_groups uses it in "on" mode, with
the usual fallback to Python for rooms without a chain cover index.
Shadow mode keeps comparing through resolve_events_with_store. The
state sets are now shared maps inside the Rust, so a cached map and a
converted one are handled alike.

On the benchmark room (2,000 members, 1,000 conflicted events) a warm
resolution through the state groups takes 9 to 11 ms end to end once
the groups' state is cached, from 15 to 19 through the flat entry
point; Python warm is 37 to 44.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ULdieJyQoHNzeHq42H2M1p
2026-10-13 17:34:55 +00:00
Erik JohnstonandClaude Opus 5.5 bf17370b6a Make the Rust state resolution synchronous under trial
The Rust resolution fires its deferred from the tokio threads, and the
fake reactor only waits for those inside a request. Background work that
resolves state, such as processing a PDU after /send has returned, had
not finished when tests checked its effects, so tests failed with
SYNAPSE_TEST_RUST_STATE_RES set.

The handler gains resolve_blocking, and test homeservers use it when
SYNAPSE_TEST_RUST_STATE_RES is set, as make_fake_db_pool already does
for database transactions. tests/state/test_rust_handler.py still covers
the deferred.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ULdieJyQoHNzeHq42H2M1p
2026-10-13 11:50:49 +00:00
Erik JohnstonandClaude Opus 5.5 51a20c80c8 Resolve state in Rust behind experimental_features.rust_state_res
For v2 and v2.1 rooms on the native driver:

- "on" resolves in Rust, falling back to Python for rooms without a
  chain cover index. The per-room CPU and database metrics come from
  the Rust stats, as the log context does not see that work.
- "shadow" resolves in Python, then in Rust with a copy of the event
  map, logs any difference and counts the outcome in
  synapse_state_res_rust_shadow_total.
- "off" (the default) is unchanged.

SYNAPSE_TEST_RUST_STATE_RES sets the mode for every test homeserver.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ULdieJyQoHNzeHq42H2M1p
2026-10-13 10:49:24 +00:00
Erik JohnstonandClaude Fable 5.1 97364c1b02 Give the database pool interface a Protocol
`make_pool` returned the Rust pool under a `type: ignore` because it is
not an `adbapi.ConnectionPool`. Name the slice of the adbapi interface
`DatabasePool` relies on as a Protocol, which both pools satisfy, and
type `make_pool` and the test harness fakes with it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ULdieJyQoHNzeHq42H2M1p
2026-10-12 18:49:53 +00:00
Erik JohnstonandClaude Opus 4.8 8753216c66 Let the test suite run against the Rust Postgres backend
Add SYNAPSE_TEST_RUST_DRIVER to run the Postgres test suite against the native
Rust driver (sets use_rust_driver on the test database config, off by default).
Teach the harness's make_fake_db_pool to build a RustConnectionPool for the Rust
engine, with its worker thread pool swapped for the threadless test pool so
queries run deterministically on the reactor during `pump` (the shim's block_on
waits there for the tokio workers). Test-database create/drop is admin work, so
it now goes directly over psycopg2 (always available in tests) rather than the
engine module's `connect`.

Running the suite this way surfaces a few real psycopg2-compatibility gaps:

  - `fixup_config_defaults` (was `fixup_default_host`) now also fills libpq's
    PGUSER / PGPASSWORD environment defaults, not just the host — tokio-postgres
    reads none of them from the environment, but Synapse's test setup relies on
    them (as psycopg2 does).
  - `build_dsn` skips `None`-valued args, matching psycopg2's handling of unset
    kwargs.

With these, a homeserver boots on the Rust backend under the test harness and
starts running real transactions and background updates. The remaining psycopg2
type-coercion / behaviour differences are addressed in the commits that follow;
psycopg2 and sqlite runs are unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3G4M92AmwSSZCbmtMJU3d
2026-10-12 18:31:06 +00:00
Erik JohnstonandClaude Fable 5.1 318ce331c2 Thread the RustRuntime through the Rust backend's startup path
The Rust `ConnectionPool` takes the homeserver's `RustRuntime`, whose
tokio runtime drives the pool. `make_pool`, `rust_dbapi.connect` and
`make_conn` now take it too, and `DatabasePool` and `Databases` pass
`hs.get_rust_runtime()`. `make_conn` falls back to a standalone runtime
on the global reactor for the scripts and tests that have no homeserver
to take one from.

The adapter and engine tests build their pools through the shared
`_make_pool` helper for the same reason.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ULdieJyQoHNzeHq42H2M1p
2026-10-12 18:31:06 +00:00
Erik JohnstonandClaude Opus 4.8 96113be9be Wire the native Rust Postgres backend behind a use_rust_driver flag
Turn the Rust backend on with `database.use_rust_driver: true` (alongside
`name: psycopg2`). Keeping `name: psycopg2` means the engine is still selected as
Postgres and all the `isinstance(engine, PostgresEngine)` dialect checks hold;
the flag only swaps the driver implementation.

  - `create_engine` returns `RustPostgresEngine` instead of `Psycopg2Engine` when
    the flag is set.
  - `make_pool` gains a `clock` argument and, for the Rust engine, builds a
    `RustConnectionPool` (via `_make_rust_pool`) instead of an adbapi pool:
    it derives the libpq DSN from the config `args`, sizes threads/connections
    to `cp_max`, passes the engine's synchronous_commit / statement_timeout,
    starts the pool, and registers a shutdown hook via the clock. Its declared
    return type stays `adbapi.ConnectionPool` — `RustConnectionPool` provides the
    `_db_pool` subset `DatabasePool` uses (runWithConnection / threadID /
    running / threadpool) — so callers that lean on adbapi-specific methods keep
    type-checking.
  - `RustConnectionPool` takes synchronous_commit / statement_timeout_ms and
    threads them into its pooled connections' session setup.
  - `DatabasePool` passes its clock to `make_pool`; the test harness's
    `make_fake_db_pool` accepts the new argument.

Tested: create_engine honours the flag (and the Rust engine is still a
PostgresEngine, not a Psycopg2Engine); make_pool builds a started
RustConnectionPool that serves a query as `_db_pool`. psycopg2 and sqlite
homeserver boots remain green. A full homeserver boot on the Rust backend isn't
covered here because the test harness patches `make_pool` with a synchronous
adbapi fake.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W3G4M92AmwSSZCbmtMJU3d
2026-10-12 18:31:01 +00:00
Erik Johnston 7ae1d987ef Add a per-homeserver clock to the Rust side (#20294)
This is a prerequisite to being able to add caching on the Rust side.
2026-10-07 11:11:08 +01:00
Andrew Morgan b3c67110a5 Bump Twisted in poetry.lock from 25.5.0 to 26.4.0 (#20259) 2026-09-29 17:50:55 +00:00
Andrew Morgan c9b61fc2a3 Stop real threadpool for test DB pool before replacing it with threadless test implementation (#20272) 2026-09-29 16:25:32 +00:00
e863ce7fab Fire deferreds from Rust futures without taking the GIL (#20252)
Tokio tasks currently complete by taking the GIL and calling
`reactor.callFromThread`. Taking the GIL may block for a period of time,
and we do not want that to happen on the tokio reactor threads (as that
can block other work from happening).

To avoid this, we instead add a work queue that we can add to from Rust
without taking the GIL, which is drained by the reactor. We signal to
the Twisted reactor that it should wake up by using a unix socket pair,
which is exactly how `reactor.callFromThread` works.

---

The self-pipe trick is basically where you create a pair of unix sockets
connected to each other. One end is added to the reactor so the reactor
is woken up when there are bytes to read, and when another thread needs
to wake up the reactor it just needs to write a byte into the other unix
socket.

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com>
2026-09-25 09:49:14 +01:00
Eric Eastwood 93f4ea43c7 Fix tests that use homeserver_to_use=GenericWorkerServer not being able to be run standalone (#20017)
*Spawning from
https://github.com/element-hq/synapse/pull/19057#discussion_r2427537811,*

Fix tests that use `homeserver_to_use=GenericWorkerServer` not being
able to be run standalone

Fix https://github.com/element-hq/synapse/issues/15671 (previously
https://github.com/matrix-org/synapse/issues/15671)

Before this change:

```shell
$ poetry run trial tests.storage.test_rollback_worker.WorkerSchemaTests.test_rolling_back
tests.storage.test_rollback_worker
  WorkerSchemaTests
    test_rolling_back ...                                               [ERROR]

===============================================================================
[ERROR]
Traceback (most recent call last):
  File "synapse/tests/unittest.py", line 129, in new
    return code(orig, *args, **kwargs)
  File "synapse/tests/unittest.py", line 223, in setUp
    return orig()
  File "synapse/tests/unittest.py", line 398, in setUp
    self.hs = self.make_homeserver(self.reactor, self.clock)
  File "synapse/tests/storage/test_rollback_worker.py", line 54, in make_homeserver
    hs = self.setup_test_homeserver(homeserver_to_use=GenericWorkerServer)
  File "synapse/tests/unittest.py", line 669, in setup_test_homeserver
    hs = setup_test_homeserver(
  File "synapse/tests/server.py", line 1260, in setup_test_homeserver
    prepare_database(
  File "synapse/synapse/storage/prepare_database.py", line 167, in prepare_database
    raise UpgradeDatabaseException(EMPTY_DATABASE_ON_WORKER_ERROR)
synapse.storage.prepare_database.UpgradeDatabaseException: Uninitialised database: run the main synapse process to prepare the database schema before starting worker processes.

tests.storage.test_rollback_worker.WorkerSchemaTests.test_rolling_back
-------------------------------------------------------------------------------
Ran 1 tests in 0.034s

FAILED (errors=1)
```


### What was the problem before?

[`PREPPED_SQLITE_DB_CONN`](https://github.com/element-hq/synapse/blob/1a1af7b622f219ba0f2501709298caa230ad3912/tests/server.py#L1247-L1262)
is a process global and shared between all tests. Whichever test first
calls `setup_test_homeserver(...)` builds the template database for the
whole trial run.

`prepare_database(...)` has a built-in check to refuse upgrading the
database ["to avoid multiple workers doing it at
once."](https://github.com/element-hq/synapse/blob/1a1af7b622f219ba0f2501709298caa230ad3912/synapse/storage/prepare_database.py#L164-L167)
and throw `UpgradeDatabaseException`.

This means that if we happen to first run a test that uses a worker
(`homeserver_to_use=GenericWorkerServer`), `prepare_database(...)` will
just throw its `UpgradeDatabaseException`. And since
`PREPPED_SQLITE_DB_CONN` is assigned before `prepare_database(...)`, it
will never try to prepare again and the rest of the tests will fail.
2026-08-10 16:34:56 -05:00
Andrew Morgan 6bfec47c47 Revert "Fix flaky 3PID inhibit error unit tests" (#19916) 2026-07-14 09:46:28 +00:00
Eric Eastwood 026937b251 Update FakeChannel.await_result(...) to drive async Rust (Tokio runtime/thread pool) (#19879)
Split off from https://github.com/element-hq/synapse/pull/19878 (needed
to drive requests in tests that use async Rust)

Follow-up to https://github.com/element-hq/synapse/pull/19871
2026-07-07 04:07:19 -05:00
Andrew Morgan c7a47bc121 Fix flaky 3PID inhibit error unit tests (#19913) 2026-07-06 11:34:41 +00:00
Eric Eastwood 7da21a715d Update HomeserverTestCase.get_success(...) and friends to drive async Rust (Tokio runtime/thread pool) (#19871)
This means you can use `get_success(...)` anywhere regardless
of what kind of work needs to be done.

Spawning from adding some more async Rust things in
https://github.com/element-hq/synapse/pull/19846 and wanting something
more standard instead of the custom `till_deferred_has_result(...)` that
has crept in to a few files.

Alternative to https://github.com/element-hq/synapse/pull/19867 spurred
on by [this
comment](https://github.com/element-hq/synapse/pull/19867#discussion_r3441774685)
from @erikjohnston


### How does this work?

Previously, `get_success(...)` just ran in a hot-loop advancing the
Twisted reactor clock which didn't give any time for other threads to do
some work or acquire the GIL if necessary (whenever there is a hand-off
from Rust to Python, we need the GIL).

Now, `get_success(...)` loops until we see a result (until we hit the
~0.1s real-time timeout). In the loop, we call
[`time.sleep(0)`](https://docs.python.org/3/library/time.html#time.sleep)
which will "Suspend execution of the calling thread [...]" (CPU and GIL)
to allow other threads to do some work. Then like before, we advance the
Twisted reactor clock to run any scheduled callbacks which includes
anything the other threads may have scheduled.


### Does this slow down the entire test suite?

Seems just as fast as before. There is minutes variance in what we had
before and after but both are within the same range of each other.

(see PR for actual before/after timings)
2026-07-02 15:20:38 -05:00
Eric Eastwood d11b012a81 Force keyword-args for clear default_config(server_name="test") usage (#19849)
I.e. `default_config("test")` and `default_config("test", False)` are so
opaque and hard to connect the dots with.

Spawning from trying to figure out what the `server_name` is set as for
our `HomeserverTestCase` in order to reference it in
https://github.com/element-hq/synapse/pull/19848#discussion_r3397455309
2026-06-12 09:45:22 -05:00
Erik Johnston c430c16df4 Port event content to Rust (#19725)
Based on #19708.

This is on the path to porting the entire event class to Rust, as
`event.content` will then return the new Rust class `JsonObject`.

This PR adds a pure Rust `JsonObject` class that is a `Mapping`
representing a json-style object. It uses `serde_json::Value` as its
in-memory representation and `pythonize` for conversion when a field is
looked up on the object.

I'm not thrilled with the name, but couldn't think of a better one.

This also adds `JsonObject` handling to the JSON serialisation functions
we use, as well as to the `freeze(..)` function.

Reviewable commit-by-commit.
2026-05-08 14:19:03 +01:00
Olivier 'reivilibre 6c7e05fe20 Allow Synapse to start up even when discovery fails for an OpenID Connect provider. (#19509)
Fixes: #8088

Previously we would perform OIDC discovery on startup,
which involves making HTTP requests to the identity provider(s).

If that took a long time, we would block startup.

If that failed, we would crash startup.

This commit:

- makes the loading happen in the background on startup
- makes an error in the 'preload' non-fatal (though it logs at CRITICAL
for visibility)
- adds a templated error page to show on failed redirects (for
unavailable providers), as otherwise you get a JSON response in your
navigator.
- This involves introducing 2 new exception types to mark other
exceptions and keep the error handling fine-grained.

The machinery was already there to load-on-demand the discovery config,
so when the identity provider
comes back up, the discovery is reattempted and login can succeed.

Signed-off-by: Olivier 'reivilibre <oliverw@matrix.org>
2026-03-24 17:39:21 +00:00
Devon HudsonandAndrew Morgan df24e0f302 Fix support for older versions of zope-interface (#19274)
Fixes #19269 

Versions of zope-interface from RHEL, Ubuntu LTS 22 & 24 and OpenSuse
don't support the new python union `X | Y` syntax for interfaces. This
PR partially reverts the change over to fully use the new syntax, adds a
minimum supported version of zope-interface to Synapse's dependency
list, and removes the linter auto-upgrades which prefer the newer
syntax.

### 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))

---------

Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com>
2025-12-12 15:34:13 +00:00
Devon HudsonandEric Eastwood 8b0083cad9 Respond with useful error codes when Content-Length header/s are invalid (#19212)
Related to https://github.com/element-hq/synapse/issues/17035, when
Synapse receives a request that is larger than the maximum size allowed,
it aborts the connection without ever sending back a HTTP response.
I dug into our usage of twisted and how best to try and report such an
error and this is what I came up with.

It would be ideal to be able to report the status from within
`handleContentChunk` but that is called too early on in the twisted http
handling code, before things have been setup enough to be able to
properly write a response.
I tested this change out locally (both with C-S and S-S apis) and they
do receive a 413 response now in addition to the connection being
closed.

Hopefully this will aid in being able to quickly detect when
https://github.com/element-hq/synapse/issues/17035 is occurring as the
current situation makes it very hard to narrow things down to that
specific issue without making a lot of assumptions.

This PR also responds with more meaningful error codes now in the case
of:
- multiple `Content-Length` headers
- invalid `Content-Length` header value
- request content size being larger than the `Content-Length` value

### 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))

---------

Co-authored-by: Eric Eastwood <erice@element.io>
2025-12-08 21:39:18 +00:00
Eric Eastwood 39316672da Be able to shutdown homeserver that hasn't setup (#19187)
For example, a homeserver can fail to `setup` if it fails to connect to
the database.

Fix https://github.com/element-hq/synapse/issues/19188

Follow-up to https://github.com/element-hq/synapse/pull/18828


### Background

As part of Element's plan to support a light form of vhosting (virtual
host) (multiple instances of Synapse in the same Python process) (c.f
Synapse Pro for small hosts), we're currently diving into the details
and implications of running multiple instances of Synapse in the same
Python process.

"Clean tenant deprovisioning" tracked internally by
https://github.com/element-hq/synapse-small-hosts/issues/50
2025-12-02 10:58:06 -06:00
Andrew Ferrazzutti fcac7e0282 Write union types as X | Y where possible (#19111)
aka PEP 604, added in Python 3.10
2025-11-06 14:02:33 -06:00
Andrew Ferrazzutti fc244bb592 Use type hinting generics in standard collections (#19046)
aka PEP 585, added in Python 3.9

 - https://peps.python.org/pep-0585/
 - https://docs.astral.sh/ruff/rules/non-pep585-annotation/
2025-10-22 16:48:19 -05:00
Eric Eastwood 7b8831310f No need to have version_string as an argument since it's always the same (#19012)
Assuming, we're happy with
https://github.com/element-hq/synapse/pull/19011, this PR makes sense.
2025-10-07 13:27:24 -05:00
Devon HudsonandEric Eastwood 396de6544a Cleanly shutdown SynapseHomeServer object (#18828)
This PR aims to allow for a clean shutdown of the `SynapseHomeServer`
object so that it can be fully deleted and cleaned up by garbage
collection without shutting down the entire python process.

Fix https://github.com/element-hq/synapse-small-hosts/issues/50

### 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))

---------

Co-authored-by: Eric Eastwood <erice@element.io>
2025-10-01 02:42:09 +00:00
Eric Eastwood 5adb08f3c9 Remove MockClock() (#18992)
Spawning from adding some logcontext debug logs in
https://github.com/element-hq/synapse/pull/18966 and since we're not
logging at the `set_current_context(...)` level (see reasoning there),
this removes some usage of `set_current_context(...)`.

Specifically, `MockClock.call_later(...)` doesn't handle logcontexts
correctly. It uses the calling logcontext as the callback context
(wrong, as the logcontext could finish before the callback finishes) and
it didn't reset back to the sentinel context before handing back to the
reactor. It was like this since it was [introduced 10+ years
ago](https://github.com/element-hq/synapse/commit/38da9884e70e8e44bde14c67a7a8a9d49a8b87ac).
Instead of fixing the implementation which would just be a copy of our
normal `Clock`, we can just remove `MockClock`
2025-09-30 11:27:29 -05:00
Eric Eastwood 5143f93dc9 Fix server_name in logging context for multiple Synapse instances in one process (#18868)
### Background

As part of Element's plan to support a light form of vhosting (virtual
host) (multiple instances of Synapse in the same Python process), we're
currently diving into the details and implications of running multiple
instances of Synapse in the same Python process.

"Per-tenant logging" tracked internally by
https://github.com/element-hq/synapse-small-hosts/issues/48

### Prior art

Previously, we exposed `server_name` by providing a static logging
`MetadataFilter` that injected the values:


https://github.com/element-hq/synapse/blob/205d9e4fc4774850f34971469ae500e70119d17a/synapse/config/logger.py#L216

While this can work fine for the normal case of one Synapse instance per
Python process, this configures things globally and isn't compatible
when we try to start multiple Synapse instances because each subsequent
tenant will overwrite the previous tenant.


### What does this PR do?

We remove the `MetadataFilter` and replace it by tracking the
`server_name` in the `LoggingContext` and expose it with our existing
[`LoggingContextFilter`](https://github.com/element-hq/synapse/blob/205d9e4fc4774850f34971469ae500e70119d17a/synapse/logging/context.py#L584-L622)
that we already use to expose information about the `request`.

This means that the `server_name` value follows wherever we log as
expected even when we have multiple Synapse instances running in the
same process.


### A note on logcontext

Anywhere, Synapse mistakenly uses the `sentinel` logcontext to log
something, we won't know which server sent the log. We've been fixing up
`sentinel` logcontext usage as tracked by
https://github.com/element-hq/synapse/issues/18905

Any further `sentinel` logcontext usage we find in the future can be
fixed piecemeal as normal.


https://github.com/element-hq/synapse/blob/d2a966f922fdc95bc86f7fe55b7b54a9ab3f25c1/docs/log_contexts.md#L71-L81


### Testing strategy

1. Adjust your logging config to include `%(server_name)s` in the format
    ```yaml
    formatters:
        precise:
format: '%(asctime)s - %(server_name)s - %(name)s - %(lineno)d -
%(levelname)s - %(request)s - %(message)s'
    ```
1. Start Synapse: `poetry run synapse_homeserver --config-path
homeserver.yaml`
1. Make some requests (`curl
http://localhost:8008/_matrix/client/versions`, etc)
1. Open the homeserver logs and notice the `server_name` in the logs as
expected. `unknown_server_from_sentinel_context` is expected for the
`sentinel` logcontext (things outside of Synapse).
2025-09-26 17:10:48 -05:00
Eric Eastwood 5a9ca1e3d9 Introduce Clock.call_when_running(...) to include logcontext by default (#18944)
Introduce `Clock.call_when_running(...)` to wrap startup code in a
logcontext, ensuring we can identify which server generated the logs.

Background:

>  Ideally, nothing from the Synapse homeserver would be logged against the `sentinel` 
>  logcontext as we want to know which server the logs came from. In practice, this is not 
>  always the case yet especially outside of request handling. 
>   
>  Global things outside of Synapse (e.g. Twisted reactor code) should run in the 
>  `sentinel` logcontext. It's only when it calls into application code that a logcontext 
>  gets activated. This means the reactor should be started in the `sentinel` logcontext, 
>  and any time an awaitable yields control back to the reactor, it should reset the 
>  logcontext to be the `sentinel` logcontext. This is important to avoid leaking the 
>  current logcontext to the reactor (which would then get picked up and associated with 
>  the next thing the reactor does). 
>
> *-- `docs/log_contexts.md`

Also adds a lint to prefer `Clock.call_when_running(...)` over
`reactor.callWhenRunning(...)`

Part of https://github.com/element-hq/synapse/issues/18905
2025-09-22 10:27:59 -05:00
Eric Eastwood ca655e4020 Start background tasks after we fork the process (daemonize) (#18886)
Spawning from https://github.com/element-hq/synapse/pull/18871

[This change](https://github.com/element-hq/synapse/pull/18871/commits/6ce2f3e59d6dcd8a798552a087414cf137d0ddad)
was originally used to fix CPU time going backwards when we `daemonize`.

While, we don't seem to run into this problem on `develop`, I still
think this is a good change to make. We don't need background tasks
running on a process that will soon be forcefully exited and where the
reactor isn't even running yet. We now kick off the background tasks
(`run_as_background_process`) after we have forked the process and
started the reactor.

Also as simple note, we don't need background tasks running in both halves of a fork.
2025-09-09 10:10:34 -05:00
Eric Eastwood bff4a11b3f Re-introduce: Fix LaterGauge metrics to collect from all servers (#18791)
Re-introduce: https://github.com/element-hq/synapse/pull/18751 that was
reverted in https://github.com/element-hq/synapse/pull/18789 (explains
why the PR was reverted in the first place).

- Adds a `cleanup` pattern that cleans up metrics from each homeserver
in the tests. Previously, the list of hooks built up until our CI
machines couldn't operate properly, see
https://github.com/element-hq/synapse/pull/18789
- Fix long-standing issue with `synapse_background_update_status`
metrics only tracking the last database listed in the config (see
https://github.com/element-hq/synapse/pull/18791#discussion_r2261706749)
2025-09-02 12:14:27 -05:00
reivilibre a31d53b28f Use twisted.internet.testing module in tests instead of deprecated twisted.test.proto_helpers. (#18728)
Follows: #18727

---------

Signed-off-by: Olivier 'reivilibre <oliverw@matrix.org>
2025-07-30 12:32:10 +01:00
Eric Eastwood d4af2970f3 Refactor Histogram metrics to be homeserver-scoped (#18724)
Bulk refactor `Histogram` metrics to be homeserver-scoped. We also add
lints to make sure that new `Histogram` metrics don't sneak in without
using the `server_name` label (`SERVER_NAME_LABEL`).

Part of https://github.com/element-hq/synapse/issues/18592



### Testing strategy

 1. Add the `metrics` listener in your `homeserver.yaml`
    ```yaml
    listeners:
      # This is just showing how to configure metrics either way
      #
      # `http` `metrics` resource
      - port: 9322
        type: http
        bind_addresses: ['127.0.0.1']
        resources:
          - names: [metrics]
            compress: false
      # `metrics` listener
      - port: 9323
        type: metrics
        bind_addresses: ['127.0.0.1']
    ```
1. Start the homeserver: `poetry run synapse_homeserver --config-path
homeserver.yaml`
1. Fetch `http://localhost:9322/_synapse/metrics` and/or
`http://localhost:9323/metrics`
1. Observe response includes the TODO metrics with the `server_name`
label

### Todo

- [x] Wait for https://github.com/element-hq/synapse/pull/18656 to merge


### Dev notes

```
LoggingDatabaseConnection
make_conn
make_pool
make_fake_db_pool
```

### 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))
2025-07-29 15:35:38 -05:00
Eric Eastwood f13a136396 Refactor Gauge metrics to be homeserver-scoped (#18725)
Bulk refactor `Gauge` metrics to be homeserver-scoped. We also add lints
to make sure that new `Gauge` metrics don't sneak in without using the
`server_name` label (`SERVER_NAME_LABEL`).

Part of https://github.com/element-hq/synapse/issues/18592



### Testing strategy

 1. Add the `metrics` listener in your `homeserver.yaml`
    ```yaml
    listeners:
      # This is just showing how to configure metrics either way
      #
      # `http` `metrics` resource
      - port: 9322
        type: http
        bind_addresses: ['127.0.0.1']
        resources:
          - names: [metrics]
            compress: false
      # `metrics` listener
      - port: 9323
        type: metrics
        bind_addresses: ['127.0.0.1']
    ```
1. Start the homeserver: `poetry run synapse_homeserver --config-path
homeserver.yaml`
1. Fetch `http://localhost:9322/_synapse/metrics` and/or
`http://localhost:9323/metrics`
1. Observe response includes the TODO metrics with the `server_name`
label

### 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))
2025-07-29 10:37:59 -05:00