kevy_embedded/replica_glue.rs
1//! Glue between the public `Store` surface and the replica runner —
2//! the constructor that decides whether to spawn the background
3//! thread, plus the read-only enforcement helper that every mutating
4//! API in `ops.rs` calls. Extracted from `store.rs` to keep that file
5//! under the 500-LOC ceiling.
6
7use std::sync::atomic::{AtomicU64, Ordering};
8
9use crate::config::Config;
10use crate::store::Shards;
11
12/// Construct + spawn the replica runner when configured. Returns
13/// `None` when the upstream is unset (normal primary store). The
14/// returned handle is owned by `DropGuard`, which joins the runner
15/// thread on the last `Store` clone drop.
16#[cfg(not(target_arch = "wasm32"))]
17pub(crate) fn spawn_replica_runner(
18 config: &Config,
19 shards: &Shards,
20) -> Option<crate::replica_runner::ReplicaRunner> {
21 let upstream = config.replica_upstream.as_ref()?.clone();
22 Some(crate::replica_runner::ReplicaRunner::spawn(
23 shards.clone(),
24 upstream,
25 config.replica_id.clone(),
26 config.replica_reconnect_min,
27 config.replica_reconnect_max,
28 config.link_security.replica.clone(),
29 ))
30}
31
32/// Generate a process-unique replica id for `Store::open_replica`.
33/// Format: `"kevy-embedded-{pid}-{seq}"` — the pid stays stable across
34/// a process, the seq counter advances per open so two embeds in the
35/// same process don't collide on a single slot in the primary's
36/// SlotTable (the bug that would otherwise cause backlog frames a
37/// fresh replica still needs to be evicted as soon as the prior
38/// embed disconnects).
39#[cfg(not(target_arch = "wasm32"))]
40pub(crate) fn fresh_replica_id() -> String {
41 static SEQ: AtomicU64 = AtomicU64::new(0);
42 let n = SEQ.fetch_add(1, Ordering::Relaxed);
43 format!("kevy-embedded-{}-{}", std::process::id(), n)
44}
45
46impl crate::Store {
47 /// The embed-writer replication listener's actually-bound address,
48 /// when this store was opened with
49 /// [`Config::with_embed_writer`](crate::Config::with_embed_writer).
50 /// Open with port `0` to let the OS pick a free port and read the
51 /// real one back here — the race-free way to run an ephemeral
52 /// writer (tests, sidecars, one process per scope).
53 #[cfg(not(target_arch = "wasm32"))]
54 pub fn writer_addr(&self) -> Option<std::net::SocketAddr> {
55 self.guard.replica_source.as_ref().map(|s| s.local_addr())
56 }
57}