Skip to main content

telltale_vm/session/
store.rs

1impl<'de> Deserialize<'de> for SessionState {
2    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3    where
4        D: serde::Deserializer<'de>,
5    {
6        let raw = SessionStateSerde::deserialize(deserializer)?;
7        let mut session = Self {
8            sid: raw.sid,
9            roles: raw.roles,
10            role_ids: BTreeMap::new(),
11            local_types: raw.local_types,
12            buffers: raw.buffers,
13            edge_lookup: BTreeMap::new(),
14            handler_ids: BTreeMap::new(),
15            handlers_by_id: Vec::new(),
16            edge_handler_lookup: BTreeMap::new(),
17            default_handler_id: None,
18            label_ids: BTreeMap::new(),
19            labels_by_id: Vec::new(),
20            branch_lookup: BTreeMap::new(),
21            auth_leaves: raw.auth_leaves,
22            auth_trees: raw.auth_trees,
23            auth_roots: raw.auth_roots,
24            edge_handlers: raw.edge_handlers,
25            default_handler: raw.default_handler,
26            edge_traces: raw.edge_traces,
27            status: raw.status,
28            epoch: raw.epoch,
29        };
30        session.rebuild_derived_indexes();
31        Ok(session)
32    }
33}
34
35/// Store of all sessions managed by the VM.
36///
37/// Provides type lookup/update methods that match the Lean
38/// `SessionStore.lookupType` / `SessionStore.updateType` pattern.
39#[derive(Debug, Default, Serialize, Deserialize)]
40pub struct SessionStore {
41    sessions: BTreeMap<SessionId, SessionState>,
42    #[serde(default)]
43    archived_closed: Vec<ClosedSessionSummary>,
44    next_id: SessionId,
45}
46
47impl SessionStore {
48    /// Create an empty session store.
49    #[must_use]
50    pub fn new() -> Self {
51        Self::default()
52    }
53
54    /// Open a new session with an externally supplied session id.
55    ///
56    /// Callers should source ids from `SessionStore::next_session_id()`.
57    #[allow(clippy::needless_pass_by_value)]
58    pub fn open_with_sid(
59        &mut self,
60        sid: SessionId,
61        roles: Vec<String>,
62        buffer_config: &BufferConfig,
63        initial_types: &BTreeMap<String, LocalTypeR>,
64    ) -> SessionId {
65        let plan = SessionOpenPlan::new(&roles, initial_types);
66        self.open_with_sid_from_plan(sid, &plan, buffer_config)
67    }
68
69    /// Open a new session from a reusable precomputed open plan.
70    pub fn open_with_sid_from_plan(
71        &mut self,
72        sid: SessionId,
73        plan: &SessionOpenPlan,
74        buffer_config: &BufferConfig,
75    ) -> SessionId {
76        let state = SessionState::from_open_plan(sid, plan, buffer_config);
77        self.sessions.insert(sid, state);
78        self.next_id = self.next_id.max(sid.saturating_add(1));
79        sid
80    }
81
82    /// Open a new session with the given roles, buffer config, and initial local types.
83    ///
84    /// Returns the session ID. Endpoints are constructed as `Endpoint { sid, role }`.
85    #[allow(clippy::needless_pass_by_value)]
86    pub fn open(
87        &mut self,
88        roles: Vec<String>,
89        buffer_config: &BufferConfig,
90        initial_types: &BTreeMap<String, LocalTypeR>,
91    ) -> SessionId {
92        let sid = self.next_id;
93        self.open_with_sid(sid, roles, buffer_config, initial_types)
94    }
95
96    /// Next session identifier that will be allocated by `open`.
97    #[must_use]
98    pub fn next_session_id(&self) -> SessionId {
99        self.next_id
100    }
101
102    // ---- Type state methods (match Lean SessionStore.lookupType / updateType) ----
103
104    /// Lookup the current local type for an endpoint.
105    ///
106    /// Matches Lean `SessionStore.lookupType`.
107    #[must_use]
108    pub fn lookup_type(&self, ep: &Endpoint) -> Option<&LocalTypeR> {
109        self.sessions
110            .get(&ep.sid)?
111            .local_types
112            .get(ep)
113            .map(|e| &e.current)
114    }
115
116    /// Update the local type for an endpoint (type advancement on commit).
117    ///
118    /// Matches Lean `SessionStore.updateType`.
119    pub fn update_type(&mut self, ep: &Endpoint, new_type: LocalTypeR) {
120        if let Some(session) = self.sessions.get_mut(&ep.sid) {
121            if let Some(entry) = session.local_types.get_mut(ep) {
122                entry.current = new_type;
123            }
124            session.refresh_endpoint_branch_lookup(ep);
125        }
126    }
127
128    /// Update the original type (when entering a new Mu scope).
129    pub fn update_original(&mut self, ep: &Endpoint, new_original: LocalTypeR) {
130        if let Some(session) = self.sessions.get_mut(&ep.sid) {
131            if let Some(entry) = session.local_types.get_mut(ep) {
132                entry.original = new_original;
133            }
134        }
135    }
136
137    /// Get the original type for recursive unfolding.
138    #[must_use]
139    pub fn original_type(&self, ep: &Endpoint) -> Option<&LocalTypeR> {
140        self.sessions
141            .get(&ep.sid)?
142            .local_types
143            .get(ep)
144            .map(|e| &e.original)
145    }
146
147    /// Remove type entry (on Halt/End — session endpoint completed).
148    pub fn remove_type(&mut self, ep: &Endpoint) {
149        if let Some(session) = self.sessions.get_mut(&ep.sid) {
150            session.local_types.remove(ep);
151            session.branch_lookup.remove(ep);
152        }
153    }
154
155    // ---- Session access methods ----
156
157    /// Get a reference to a session.
158    #[must_use]
159    pub fn get(&self, sid: SessionId) -> Option<&SessionState> {
160        self.sessions.get(&sid)
161    }
162
163    /// Get a mutable reference to a session.
164    pub fn get_mut(&mut self, sid: SessionId) -> Option<&mut SessionState> {
165        self.sessions.get_mut(&sid)
166    }
167
168    /// Iterate over all sessions.
169    pub fn iter(&self) -> impl Iterator<Item = &SessionState> {
170        self.sessions.values()
171    }
172
173    /// Close a session.
174    ///
175    /// # Errors
176    ///
177    /// Returns an error if the session is not found.
178    pub fn close(&mut self, sid: SessionId) -> Result<(), String> {
179        let session = self
180            .sessions
181            .get_mut(&sid)
182            .ok_or_else(|| format!("session {sid} not found"))?;
183
184        session.status = SessionStatus::Closed;
185        session.buffers.clear();
186        session.edge_traces.clear();
187        session.epoch = session.epoch.saturating_add(1);
188        Ok(())
189    }
190
191    /// Closed/cancelled/faulted session identifiers still resident in the store.
192    #[must_use]
193    pub fn closed_session_ids(&self) -> Vec<SessionId> {
194        self.sessions
195            .iter()
196            .filter_map(|(sid, session)| {
197                matches!(
198                    session.status,
199                    SessionStatus::Closed
200                        | SessionStatus::Cancelled
201                        | SessionStatus::Faulted { .. }
202                )
203                .then_some(*sid)
204            })
205            .collect()
206    }
207
208    /// Reap specific session ids from live storage and archive compact summaries.
209    ///
210    /// # Panics
211    ///
212    /// Panics if a session disappears between the initial residency/status check
213    /// and the subsequent removal from the store.
214    pub fn reap_sessions(&mut self, session_ids: &[SessionId]) -> Vec<ClosedSessionSummary> {
215        let mut reaped = Vec::new();
216        for sid in session_ids {
217            let Some(session) = self.sessions.get(sid) else {
218                continue;
219            };
220            if !matches!(
221                session.status,
222                SessionStatus::Closed | SessionStatus::Cancelled | SessionStatus::Faulted { .. }
223            ) {
224                continue;
225            }
226
227            let session = self
228                .sessions
229                .remove(sid)
230                .expect("session existence checked before removal");
231            let summary = ClosedSessionSummary::from_session(&session);
232            self.archived_closed.push(summary.clone());
233            reaped.push(summary);
234        }
235        reaped
236    }
237
238    /// Reap all closed/cancelled/faulted sessions from live storage.
239    pub fn reap_closed(&mut self) -> Vec<ClosedSessionSummary> {
240        let sids = self.closed_session_ids();
241        self.reap_sessions(&sids)
242    }
243
244    /// Number of active sessions.
245    #[must_use]
246    pub fn active_count(&self) -> usize {
247        self.sessions
248            .values()
249            .filter(|s| s.status == SessionStatus::Active)
250            .count()
251    }
252
253    /// Number of sessions still resident in the store.
254    #[must_use]
255    pub fn live_count(&self) -> usize {
256        self.sessions.len()
257    }
258
259    /// All session IDs.
260    #[must_use]
261    pub fn session_ids(&self) -> Vec<SessionId> {
262        self.sessions.keys().copied().collect()
263    }
264
265    /// Archived closed-session summaries retained after reaping.
266    #[must_use]
267    pub fn archived_closed(&self) -> &[ClosedSessionSummary] {
268        &self.archived_closed
269    }
270
271    /// Approximate retained state for the session store.
272    #[must_use]
273    pub fn memory_usage(&self) -> SessionStoreMemoryUsage {
274        let mut usage = SessionStoreMemoryUsage {
275            live_sessions: self.sessions.len(),
276            archived_closed_sessions: self.archived_closed.len(),
277            ..SessionStoreMemoryUsage::default()
278        };
279        usage.retained_bytes.archived_closed = self
280            .archived_closed
281            .iter()
282            .map(ClosedSessionSummary::retained_bytes_estimate)
283            .sum();
284
285        for session in self.sessions.values() {
286            if matches!(
287                session.status,
288                SessionStatus::Closed | SessionStatus::Cancelled | SessionStatus::Faulted { .. }
289            ) {
290                usage.live_closed_sessions += 1;
291            }
292            usage.live_local_type_entries += session.local_types.len();
293            usage.live_buffer_count += session.buffers.len();
294            usage.live_buffered_messages += session
295                .buffers
296                .values()
297                .map(BoundedBuffer::len)
298                .sum::<usize>();
299            usage.live_edge_handler_count += session.edge_handlers.len();
300            usage.live_auth_leaf_count += session.auth_leaves.values().map(Vec::len).sum::<usize>();
301            usage.live_auth_tree_count += session.auth_trees.len();
302            usage.live_auth_root_count += session.auth_roots.len();
303            usage.retained_bytes.live_sessions += session.retained_session_core_bytes();
304            usage.retained_bytes.local_types += session.retained_local_type_bytes();
305            usage.retained_bytes.buffers += session.retained_buffer_bytes();
306            usage.retained_bytes.traces += session.retained_trace_bytes();
307            usage.retained_bytes.auth += session.retained_auth_bytes();
308            usage.retained_bytes.handlers += session.retained_handler_bytes();
309        }
310        usage.retained_bytes.total = usage
311            .retained_bytes
312            .live_sessions
313            .saturating_add(usage.retained_bytes.archived_closed)
314            .saturating_add(usage.retained_bytes.local_types)
315            .saturating_add(usage.retained_bytes.buffers)
316            .saturating_add(usage.retained_bytes.traces)
317            .saturating_add(usage.retained_bytes.auth)
318            .saturating_add(usage.retained_bytes.handlers);
319
320        usage
321    }
322
323    /// Lookup edge-bound handler id.
324    #[must_use]
325    pub fn lookup_handler(&self, edge: &Edge) -> Option<&HandlerId> {
326        self.sessions
327            .get(&edge.sid)?
328            .lookup_handler_for_roles(&edge.sender, &edge.receiver)
329    }
330
331    /// Lookup a default handler id for a session.
332    #[must_use]
333    pub fn default_handler_for_session(&self, sid: SessionId) -> Option<&HandlerId> {
334        self.sessions.get(&sid)?.default_handler_binding()
335    }
336
337    /// Set the default handler id for a session.
338    pub fn set_default_handler_for_session(&mut self, sid: SessionId, handler: HandlerId) {
339        if let Some(session) = self.sessions.get_mut(&sid) {
340            let handler_id = session.intern_handler_binding(&handler);
341            session.default_handler = handler;
342            session.default_handler_id = Some(handler_id);
343        }
344    }
345
346    /// Update edge-bound handler id.
347    pub fn update_handler(&mut self, edge: &Edge, handler: HandlerId) {
348        if let Some(session) = self.sessions.get_mut(&edge.sid) {
349            let handler_id = session.intern_handler_binding(&handler);
350            if let Some(edge_key) = session.edge_key_for_roles(&edge.sender, &edge.receiver) {
351                session.edge_handler_lookup.insert(edge_key, handler_id);
352            }
353            session.edge_handlers.insert(edge.clone(), handler);
354        }
355    }
356
357    /// Lookup coherence trace for an edge.
358    #[must_use]
359    pub fn lookup_trace(&self, edge: &Edge) -> Option<&[ValType]> {
360        self.sessions
361            .get(&edge.sid)?
362            .edge_traces
363            .get(edge)
364            .map(Vec::as_slice)
365    }
366
367    /// Update coherence trace for an edge.
368    pub fn update_trace(&mut self, edge: &Edge, trace: Vec<ValType>) {
369        if let Some(session) = self.sessions.get_mut(&edge.sid) {
370            session.edge_traces.insert(edge.clone(), trace);
371        }
372    }
373}
374
375// ---- Type unfolding utilities ----
376
377/// Unfold top-level `Mu` to its body.
378///
379/// Recursively strips `Mu` constructors to reach the first action.
380#[must_use]
381// RECURSION_SAFE: each step unwraps one Mu node from a finite local type tree.
382pub fn unfold_mu(lt: &LocalTypeR) -> LocalTypeR {
383    match lt {
384        LocalTypeR::Mu { body, .. } => unfold_mu(body),
385        other => other.clone(),
386    }
387}
388
389/// Resolve a continuation that may be a `Var` (recursive reference).
390///
391/// If `cont` is `Var`, unfolds back to the original type's mu body.
392/// If `cont` is `Mu`, unfolds it. Otherwise returns as-is.
393#[must_use]
394pub fn unfold_if_var(cont: &LocalTypeR, original: &LocalTypeR) -> LocalTypeR {
395    match cont {
396        LocalTypeR::Var(_) => unfold_mu(original),
397        LocalTypeR::Mu { .. } => unfold_mu(cont),
398        other => other.clone(),
399    }
400}
401
402/// Like `unfold_if_var`, but also returns the new Mu scope (original) if one was entered.
403///
404/// When the continuation is a `Mu`, the Mu itself becomes the new original
405/// for subsequent `Var` resolution. Returns `(resolved_type, Some(mu))` when
406/// entering a new Mu scope, `(resolved_type, None)` otherwise.
407#[must_use]
408pub fn unfold_if_var_with_scope(
409    cont: &LocalTypeR,
410    original: &LocalTypeR,
411) -> (LocalTypeR, Option<LocalTypeR>) {
412    match cont {
413        LocalTypeR::Var(_) => (unfold_mu(original), None),
414        LocalTypeR::Mu { .. } => (unfold_mu(cont), Some(cont.clone())),
415        other => (other.clone(), None),
416    }
417}