Files
synapse/rust
Erik JohnstonandClaude Fable 5.1 f049bbf911 Expose Rust caches to Synapse's invalidation, metrics and expiry
Invalidation stays in Python. Synapse invalidates caches by *name*: it
resolves `getattr(store, cache_name)` and calls `.invalidate(key)` on
whatever it finds. So `RustCache`, a `#[pyclass]` exposing that protocol,
bound onto a datastore under the right attribute name, inherits the whole
existing path (the replication stream, `txn.call_after`, `invalidate_all`)
with no changes to the machinery. Keys arrive as strings from other workers,
because the stream's column is `keys text[]`, and each key type decodes them
back via `PyCacheKey`. A key it cannot decode matches nothing, as it would
for a Python cache, rather than failing the batch of invalidations it
arrived in.

Every cache belongs to one homeserver's `RustRuntime`, whose `CacheRegistry`
builds it and is what expires it, so nothing here is process-wide. Tests
run several homeservers in one process, and one homeserver's expiry tick
must not reach another's caches. Entries expire by age like Python's, off
the same 30 second looping call and the same `expire_caches` /
`cache_entry_ttl` options, but per cache rather than via a global linked
list, since a cache's LRU order is already its age order. Memory-pressure
eviction is not implemented. That one does need a global coldest-first
order, which a k-way merge over the per-cache LRU tails can give without a
global list.

Rust keeps the hit, miss and eviction counters. `register_rust_cache`
registers a cache with the usual `CacheMetric` and copies them onto it at
each scrape, and wires `set_cache_factor` up so that `SYNAPSE_CACHE_FACTOR_*`
and `caches.per_cache_factors` reach a Rust cache the same way.

There are still no caches. The next commit adds the first.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ULdieJyQoHNzeHq42H2M1p
2026-10-14 12:41:47 +00:00
..