Calculate the forward extremities in Rust

Port `_calculate_new_extremities`, `_calculate_new_state_dag_extremities`
and `_prune_extremities` from the persist events controller to
`rust/src/persist/extremities.rs`. They read through an `ExtremityReads`
trait, which the `PersistReader` implements and the unit tests implement
over an in-memory DAG. Metrics go to a `PersistCounts` the caller owns,
so shadow mode can drop them.

Missing prev state events fail with `MissingPrevStateEvents`, which
`persist_error_to_py` turns into the same 500 `SynapseError` as Python.

`PersistReaderForTest` gains the three calculations, and
`test_persist_reader.py` compares each with the Python method on the
same database and the same new events.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHKYPSc37kWqGACn9aKxKL
This commit is contained in:
Erik JohnstonandClaude Opus 5.5 committed 2026-10-16 15:51:22 +00:00
1 parent 1afdeb20df
commit a737c2c97f
8 files changed
+1514 -53

No files matched your search

+27 -1
View File
@@ -18,10 +18,14 @@
use std::sync::Arc;
use http::StatusCode;
use pyo3::{exceptions::PyRuntimeError, intern, prelude::*, types::PyDict};
use super::{context::PersistContext, metrics::PersistMetrics};
use super::{
context::PersistContext, extremities::MissingPrevStateEvents, metrics::PersistMetrics,
};
use crate::{
errors::SynapseError,
events::Event,
homeserver::HomeServer,
runtime::RustRuntime,
@@ -122,6 +126,28 @@ impl Persister {
}
}
/// Convert an error from persisting to the Python exception the Python
/// path would raise: a Python exception as it was raised, a 500
/// `SynapseError` for missing prev state events, as
/// `_calculate_new_state_dag_extremities` raises, and a `RuntimeError`
/// otherwise.
pub fn persist_error_to_py(e: anyhow::Error) -> PyErr {
let e = match e.downcast::<PyErr>() {
Ok(err) => return err,
Err(e) => e,
};
if let Some(missing) = e.downcast_ref::<MissingPrevStateEvents>() {
return SynapseError::new(
StatusCode::INTERNAL_SERVER_ERROR,
missing.to_string(),
"M_UNKNOWN",
None,
None,
);
}
PyRuntimeError::new_err(format!("{e:#}"))
}
/// `(internal_metadata, json)`: the two `event_json` columns as Rust would
/// write them for `event`, for tests to compare with Python's.
#[pyfunction]