Match HomeServer wrapper method names to Python; make RustRuntime clonable

Rename the HomeServer wrapper accessors to mirror the Python methods they
call (get_rust_runtime, get_clock), so the wrapper reads 1:1 against
synapse/server.py.

Make RustRuntime itself a cheaply-clonable handle (Clone + Deref to
RustRuntimeInner) rather than exposing Arc<RustRuntimeInner> in
signatures. With pyo3's opt-in from_py_object extraction, the same class
serves as both the Python-facing object and the Rust-side handle:
consumers now declare `runtime: RustRuntime` in their constructors and
pyo3 clones it out (an Arc refcount bump), so no separate Py-wrapper
type is needed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HyuiV3m44ZbK9o24EnMMES
This commit is contained in:
Erik Johnston
2026-07-28 09:08:21 +00:00
co-authored by Claude Opus 5
parent efb0e1cce4
commit a6557c7989
8 changed files with 32 additions and 33 deletions
+3 -3
View File
@@ -26,7 +26,7 @@ use pyo3::{
use tokio::sync::oneshot;
use crate::reactor::Reactor;
use crate::runtime::RustRuntimeInner;
use crate::runtime::RustRuntime;
create_exception!(
synapse.synapse_rust.http_client,
@@ -75,7 +75,7 @@ fn logging_context_module(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>> {
/// Does not handle deferred cancellation or contextvars.
pub fn create_deferred<'py, F, O>(
py: Python<'py>,
runtime: &Arc<RustRuntimeInner>,
runtime: &RustRuntime,
fut: F,
) -> PyResult<Bound<'py, PyAny>>
where
@@ -91,7 +91,7 @@ where
// Keep the runtime state (and, through it, the reactor) alive while the
// task is in flight.
let runtime = Arc::clone(runtime);
let runtime = runtime.clone();
handle.spawn(async move {
let res = task.await;
+2 -2
View File
@@ -41,7 +41,7 @@ impl RustHandlers {
// The per-homeserver Rust state, which gives us the tokio runtime
// and the Twisted reactor.
let runtime = homeserver.rust_runtime(py)?;
let runtime = homeserver.get_rust_runtime(py)?;
let db_pool = PythonDatabasePoolWrapper::new(
&homeserver.main_database_pool(py)?,
@@ -62,7 +62,7 @@ impl RustHandlers {
versions::VersionsHandler {
global_unstable_feature_map: Arc::clone(&global_unstable_feature_map),
store: Arc::clone(&store),
runtime: Arc::clone(&runtime),
runtime: runtime.clone(),
},
)?;
+2 -2
View File
@@ -21,7 +21,7 @@ use serde::Serialize;
use crate::config::{types::RoomCreationPreset, SynapseHomeServerConfig};
use crate::deferred::create_deferred;
use crate::runtime::RustRuntimeInner;
use crate::runtime::RustRuntime;
use crate::storage::store::{PerUserExperimentalFeature, Store};
/// `GET /_matrix/client/versions` response
@@ -48,7 +48,7 @@ pub struct VersionsHandler {
pub store: Arc<Store>,
/// The per-homeserver Rust state, used to bridge our `async` response
/// back into a Twisted deferred that Python can `await`.
pub runtime: Arc<RustRuntimeInner>,
pub runtime: RustRuntime,
}
#[pymethods]
+5 -9
View File
@@ -15,12 +15,10 @@
//! A typed wrapper around the Python `HomeServer`.
use std::sync::Arc;
use pyo3::{intern, prelude::*};
use crate::config::SynapseHomeServerConfig;
use crate::runtime::{RustRuntime, RustRuntimeInner};
use crate::runtime::RustRuntime;
/// The Python `HomeServer`, as seen from Rust.
///
@@ -41,14 +39,12 @@ impl<'a, 'py> FromPyObject<'a, 'py> for HomeServer {
impl HomeServer {
/// The per-homeserver Rust state (`hs.get_rust_runtime()`), which gives
/// access to the tokio runtime and the reactor.
pub fn rust_runtime(&self, py: Python<'_>) -> PyResult<Arc<RustRuntimeInner>> {
let runtime: Bound<'_, RustRuntime> = self
pub fn get_rust_runtime(&self, py: Python<'_>) -> PyResult<RustRuntime> {
Ok(self
.0
.bind(py)
.call_method0(intern!(py, "get_rust_runtime"))?
.extract()?;
Ok(Arc::clone(runtime.get().inner()))
.extract()?)
}
/// The Rust-side view of `hs.config`.
@@ -58,7 +54,7 @@ impl HomeServer {
/// The Synapse `Clock` (`hs.get_clock()`).
// TODO: give the clock a typed wrapper of its own.
pub fn clock(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
pub fn get_clock(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
Ok(self
.0
.bind(py)
+4 -8
View File
@@ -13,7 +13,6 @@
*/
use std::collections::HashMap;
use std::sync::Arc;
use anyhow::Context;
use http_body_util::BodyExt;
@@ -22,7 +21,7 @@ use reqwest::RequestBuilder;
use crate::deferred::create_deferred;
use crate::errors::HttpResponseException;
use crate::runtime::{RustRuntime, RustRuntimeInner};
use crate::runtime::RustRuntime;
/// Called when registering modules with python.
pub fn register_module(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
@@ -43,7 +42,7 @@ pub fn register_module(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()>
#[pyclass]
struct HttpClient {
client: reqwest::Client,
runtime: Arc<RustRuntimeInner>,
runtime: RustRuntime,
}
#[pymethods]
@@ -51,7 +50,7 @@ impl HttpClient {
#[new]
#[pyo3(signature = (runtime, user_agent, http2_only = false))]
pub fn py_new(
runtime: &Bound<'_, RustRuntime>,
runtime: RustRuntime,
user_agent: &str,
http2_only: bool,
) -> PyResult<HttpClient> {
@@ -65,10 +64,7 @@ impl HttpClient {
let client = builder.build().context("building reqwest client")?;
Ok(HttpClient {
client,
runtime: Arc::clone(runtime.get().inner()),
})
Ok(HttpClient { client, runtime })
}
pub fn get<'a>(
+1 -1
View File
@@ -99,7 +99,7 @@ impl MSC4388RendezvousHandler {
eviction_interval: u64,
ttl: u64,
) -> PyResult<Py<Self>> {
let clock = homeserver.clock(py)?;
let clock = homeserver.get_clock(py)?;
// Construct a Python object so that we can get a reference to the
// evict method and schedule it to run.
+1 -1
View File
@@ -123,7 +123,7 @@ impl RendezvousHandler {
let base = Uri::try_from(format!("{base}_synapse/client/rendezvous"))
.map_err(|_| PyValueError::new_err("Invalid base URI"))?;
let clock = homeserver.clock(py)?;
let clock = homeserver.get_clock(py)?;
let eviction_duration = SynapseDuration::from_milliseconds(eviction_interval);
+14 -7
View File
@@ -22,6 +22,7 @@
//! [`Arc<RustRuntimeInner>`] at construction time and don't need the GIL (or
//! the Python-facing object) to reach it afterwards.
use std::ops::Deref;
use std::sync::{Arc, Mutex, Weak};
use std::time::Duration;
@@ -119,18 +120,24 @@ impl Drop for RustRuntimeInner {
}
}
/// The Python-facing handle to the per-homeserver Rust state.
/// A cheaply-clonable handle to the per-homeserver Rust state, and the
/// Python-facing class for it.
///
/// Constructed by `HomeServer.get_rust_runtime()`, and passed to the Rust
/// classes that need it (which take a clone of the inner [`Arc`] and drop
/// this handle).
#[pyclass(frozen, name = "RustRuntime", module = "synapse.synapse_rust")]
/// One instance is constructed per homeserver by
/// `HomeServer.get_rust_runtime()`. Rust classes that need it take it as a
/// constructor argument — pyo3 extracts a `#[pyclass]` that is `Clone` by
/// cloning, which here is just an `Arc` refcount bump — and hold their own
/// clone. Derefs to [`RustRuntimeInner`].
#[pyclass(frozen, from_py_object, module = "synapse.synapse_rust")]
#[derive(Clone)]
pub struct RustRuntime {
inner: Arc<RustRuntimeInner>,
}
impl RustRuntime {
pub fn inner(&self) -> &Arc<RustRuntimeInner> {
impl Deref for RustRuntime {
type Target = RustRuntimeInner;
fn deref(&self) -> &Self::Target {
&self.inner
}
}