pub struct SessionRegistry {
pub sessions: RwLock<HashMap<String, SharedSession>>,
/* private fields */
}Fields§
§sessions: RwLock<HashMap<String, SharedSession>>Implementations§
Source§impl SessionRegistry
impl SessionRegistry
pub fn new() -> Self
pub fn with_persistence<P: AsRef<Path>>(dir: P) -> Result<Self>
Sourcepub async fn persist_snapshot(&self) -> Result<()>
pub async fn persist_snapshot(&self) -> Result<()>
Snapshot every session (locking each briefly) and persist. Never holds the map lock across the per-session locks or the fs write.
Clone the shared handle for a session (brief map read; no session lock).
pub async fn get_session(&self, session_id: &str) -> Option<Session>
Sourcepub async fn get_all_sessions(&self) -> Vec<Session>
pub async fn get_all_sessions(&self) -> Vec<Session>
Deep-clones every registered session into one Vec, and therefore
holds the whole registry resident for as long as the caller keeps the
result. Only for one-shot, non-streaming work whose lifetime is its own
call (the shutdown snapshot in src/main.rs). A caller that emits the
sessions to a client — where the Vec stays alive for the duration of a
client-paced stream, times the number of concurrent streams — must use
SessionRegistry::shared_sessions instead and lock one handle at a
time, which keeps one Session clone resident.
A snapshot of every registered session’s shared handle, in unspecified order.
One synchronous pass under the map read lock, cloning
SharedSession pointers only — never a Session. This is the
entry point for a traversal that wants to visit every session without
materializing them all at once: take the handles here, then lock and
clone them one at a time (see watch_sync in macp-runtime, which uses
exactly that shape for the WatchSessions initial sync).
Unlike an ID list, this is a true snapshot of the session set: a
handle keeps its Session reachable even after the registry entry is
removed, so a traversal in progress sees every session that was
registered when the snapshot was taken, exactly once, whatever happens
to the map afterwards. Removal is never blocked — eviction takes the
write lock and removes unconditionally; only the deallocation of an
evicted session waits for the last handle to drop. The cost is one
pointer per session, against the ~8x larger String an ID list would
clone.
Sessions registered after the snapshot are absent from it, and a snapshotted session’s contents can still change under its mutex — the snapshot fixes the set, not the state.
Per the lock-ordering contract above, the map lock is released before any session mutex is taken: this returns handles and never locks one.
Sourcepub async fn session_ids_after(
&self,
after: Option<&str>,
limit: usize,
) -> Vec<String>
pub async fn session_ids_after( &self, after: Option<&str>, limit: usize, ) -> Vec<String>
Session IDs strictly greater than after, ascending (byte order), at most
limit. Keyset cursor primitive for ListSessions paging (see plan D1/D2).
Holds only the map read lock, for one synchronous pass — no session mutex is
taken and no .await happens under the guard, per the lock-ordering contract
documented above (map lock BEFORE session mutex; never hold the map lock
across an await).
Each call is individually consistent, but a multi-page traversal is not a snapshot: the lock is released between pages, so concurrent mutation is visible mid-traversal. A session inserted at a key at or below the cursor is missed by the remainder of the traversal; one inserted above the cursor appears in a later page; one removed above the cursor is never emitted. Already-emitted IDs are stable — the cursor only moves forward — so no ID is ever returned twice. This is inherent to keyset paging; callers must not present a completed traversal as a point-in-time view of the registry.
limit is caller-supplied and may be arbitrarily large (usize::MAX reads
as “no limit”); allocation is bounded by the map, never by the limit.