rustledger_lsp/snapshot.rs
1//! Immutable world snapshot for request handling.
2//!
3//! Each LSP request receives an immutable snapshot of the world state.
4//! This allows requests to be processed concurrently without locks.
5
6use std::sync::Arc;
7use std::sync::atomic::{AtomicU64, Ordering};
8
9/// Global revision counter used by [`Snapshot`].
10///
11/// Note: the LSP main loop maintains its OWN per-instance revision
12/// counter on `MainLoopState`. This global is kept only because the
13/// `Snapshot` API exposes `is_current` / `is_cancelled`, which today
14/// has no production callers and is exercised only by the test below.
15/// New cancellation-detection logic should use the per-instance
16/// counter on `MainLoopState` so multiple LSP instances in one
17/// process (e.g., integration tests) don't clobber each other.
18static REVISION: AtomicU64 = AtomicU64::new(0);
19
20/// Bump the global revision counter. See [`REVISION`] note about
21/// preferring the per-instance counter on `MainLoopState`.
22///
23/// Kept `#[allow(dead_code)]` because production no longer calls it
24/// (the per-instance counter on `MainLoopState` replaced it); the
25/// remaining caller is `test_snapshot_cancellation` below, which
26/// pins the `Snapshot::is_cancelled` contract even though no
27/// production handler reads `Snapshot` today.
28#[allow(dead_code)]
29pub fn bump_revision() -> u64 {
30 REVISION.fetch_add(1, Ordering::SeqCst) + 1
31}
32
33/// Get the current revision. See [`REVISION`] note about preferring
34/// the per-instance counter on `MainLoopState`.
35pub fn current_revision() -> u64 {
36 REVISION.load(Ordering::SeqCst)
37}
38
39/// An immutable snapshot of the world state.
40///
41/// Snapshots capture the revision at creation time, allowing
42/// handlers to detect if they should cancel (revision changed).
43#[derive(Debug)]
44pub struct Snapshot {
45 /// The revision at snapshot creation time.
46 revision: u64,
47 /// Parsed directives (TODO: replace with actual data)
48 _data: Arc<()>,
49}
50
51impl Snapshot {
52 /// Create a new snapshot at the current revision.
53 pub fn new() -> Self {
54 Self {
55 revision: current_revision(),
56 _data: Arc::new(()),
57 }
58 }
59
60 /// Check if this snapshot is still current (not cancelled).
61 pub fn is_current(&self) -> bool {
62 self.revision == current_revision()
63 }
64
65 /// Check if this snapshot has been cancelled.
66 pub fn is_cancelled(&self) -> bool {
67 !self.is_current()
68 }
69}
70
71impl Default for Snapshot {
72 fn default() -> Self {
73 Self::new()
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn test_snapshot_cancellation() {
83 let snap = Snapshot::new();
84 assert!(snap.is_current());
85
86 bump_revision();
87 assert!(snap.is_cancelled());
88 }
89}