Files
synapse/rust
Erik JohnstonandClaude Opus 5.5 ea11b6bb2d Persist each queued batch in Rust at rust_event_persistence: full
At `full`, `_persist_event_batch` hands the batch to
`Persister.persist_batch` and steps through it with `batch_next`:

- The first step drops the events already persisted under the same
  transaction ID (`get_already_persisted_events`).
- Each step prepares the next chunk of up to 100 events, marks the
  batch's state groups as being persisted
  (`persisting_state_group_references`, two short native
  transactions), takes the chunk's stream orderings and writes it.
- Python applies what the chunk asks for, runs
  `_after_persisting_events` and finishes the orderings before the next
  step, which first releases the state groups. So each chunk is
  prepared against current caches.
- If Python cannot apply a chunk, `batch_abort` releases the state
  groups as a failed persist does.

A chunk Rust cannot prepare (a state resolution fallback, or an error)
is handed back with the rest of the batch to the chunk-by-chunk path,
which prepares it again and falls back or raises as at lower levels.

Python drives the steps, rather than Rust calling back into Python, so
every step has a `_blocking` twin for tests and nothing waits on the
reactor from Rust. That costs the same hops as a callback: one in and
one out per chunk.

The prepare inputs are now built from `Event`s that share their data
with the Python events, so a chunk's input can be built without the
GIL once the batch has been deduplicated.

The persister's Deferreds already follow the logcontext rules, so they
are awaited directly. Wrapping them in `make_deferred_yieldable` again,
as the `prepare_chunk`, `persist_events_txn` and `persist_chunk` call
sites did, resumed the caller in the sentinel context.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXMkcZVkcZhi21augBff4a
2026-10-17 23:16:13 +00:00
..
2026-10-15 14:54:15 +00:00