Skip to main content

vtcode_commons/
thread_safety.rs

1//! # Thread Safety Primitives
2//!
3//! Based on "Formal methods for the unsafe side of the Force" (Antithesis, 2026).
4//! Provides rigorously defined primitives for bridging FFI and multi-threaded boundaries.
5//!
6//! ## `RelaxedAtomic<T>`
7//!
8//! Provides inner mutability for `Copy` types via relaxed atomic loads and stores.
9//! On x86_64 and ARM, relaxed loads/stores compile to the same instructions as
10//! regular memory accesses (no `LOCK` prefix), making this a zero-overhead way to
11//! achieve interior mutability for atomic-compatible types.
12//!
13//! For `u32`, provides `fetch_add` and `fetch_sub` methods that use atomic
14//! read-modify-write operations. These are atomic but emit `LOCK`-prefixed
15//! instructions on x86_64 (though without the stronger ordering fence overhead
16//! of `SeqCst`).
17//!
18//! For simple load-mutate-store patterns, use the `load`–`store` methods:
19//!
20//! ```
21//! # use vtcode_commons::thread_safety::RelaxedAtomic;
22//! let counter = RelaxedAtomic::new(0u32);
23//! let val = counter.load();
24//! counter.store(val + 1);
25//! ```
26//!
27//! For atomic increments/decrements, use `fetch_add`/`fetch_sub`:
28//!
29//! ```
30//! # use vtcode_commons::thread_safety::RelaxedAtomic;
31//! let counter = RelaxedAtomic::new(0u32);
32//! counter.fetch_add(1); // Atomic, no race condition
33//! ```
34//!
35//! # WARNING: Race Conditions Are Still Possible
36//!
37//! **Rust prevents data races, not race conditions.** (See "Rust Prevents Data Races,
38//! Not Race Conditions" by Matthias Endler.)
39//!
40//! A data race is unsynchronized concurrent access where at least one side writes.
41//! This is Undefined Behavior and Rust's type system prevents it.
42//!
43//! A **race condition** is any bug where the result depends on timing or thread
44//! interleaving. Rust does *not* prevent these.
45//!
46//! The load–mutate–store pattern is *not* atomic as a whole:
47//!
48//! ```rust,ignore
49//! // DANGEROUS: Two threads can interleave between load and store
50//! let val = counter.load();
51//! // <--- Another thread could load and store here
52//! counter.store(val + 1);
53//! ```
54//!
55//! This is the classic TOCTOU (Time-of-Check-Time-of-Use) bug. See the bank account
56//! example in the article above.
57//!
58//! ## When to use
59//!
60//! Use when a field needs interior mutability and is accessed without
61//! contention (same pattern as the original C code using plain loads/stores).
62//! If you need multi-step atomic operations (CAS, fetch_add), use the
63//! underlying `std::sync::atomic` types directly.
64//!
65//! ## When *not* to use
66//!
67//! Do not use when the operation must be atomic relative to other threads.
68//! The load–mutate–store pattern is *not* atomic as a whole — it can race
69//! with concurrent stores. Use only where the C code would have used a
70//! non-atomic access that happens to be race-free by design.
71//!
72//! ## Correct usage examples
73//!
74//! ```rust,ignore
75//! // CORRECT: Single-threaded or single-writer scenario
76//! let flag = RelaxedAtomic::new(false);
77//! // Only one thread ever writes to this
78//! flag.store(true);
79//!
80//! // CORRECT: Using fetch_add for atomic increment
81//! let counter = RelaxedAtomic::new(0u32);
82//! counter.fetch_add(1); // Atomic, no race condition
83//!
84//! // CORRECT: Read-only scenario
85//! let config = RelaxedAtomic::new(42u32);
86//! let val = config.load(); // Multiple readers, no writers
87//! ```
88//!
89//! ## Incorrect usage examples
90//!
91//! ```rust,ignore
92//! // INCORRECT: Non-atomic compound operation
93//! let counter = RelaxedAtomic::new(0u32);
94//! // Two threads doing this simultaneously can lose updates
95//! let val = counter.load();
96//! counter.store(val + 1);
97//!
98//! // INCORRECT: Check-then-act (TOCTOU)
99//! let balance = RelaxedAtomic::new(100u32);
100//! // Thread A: check balance
101//! let can_withdraw = balance.load() >= 100;
102//! // <--- Thread B could withdraw here
103//! // Thread A: withdraw
104//! if can_withdraw {
105//!     balance.store(balance.load() - 100);
106//! }
107//! ```
108
109use std::fmt;
110use std::marker::PhantomData;
111use std::sync::OnceLock;
112use std::sync::atomic::Ordering;
113use std::thread::{self, ThreadId};
114
115/// Trait for types that can be stored in a [`RelaxedAtomic`].
116///
117/// Implemented for `bool`, `u8`, `u16`, `u32`, `usize`, `i8`, `i16`, `i32`, `isize`.
118pub trait AtomicRepr: Copy + 'static {
119    /// The underlying `std::sync::atomic::Atomic*` type.
120    type Atomic: 'static + Send + Sync;
121    /// Create a new atomic instance for the given value.
122    fn new_atomic(val: Self) -> Self::Atomic;
123    /// Load the value with `Ordering::Relaxed`.
124    fn load(atomic: &Self::Atomic) -> Self;
125    /// Store the value with `Ordering::Relaxed`.
126    fn store(atomic: &Self::Atomic, val: Self);
127    /// Unwrap the atomic and return the contained value (no atomic instruction).
128    fn into_inner(atomic: Self::Atomic) -> Self;
129}
130
131macro_rules! impl_atomic_repr {
132    ($ty:ty, $atomic:ty) => {
133        impl AtomicRepr for $ty {
134            type Atomic = $atomic;
135            fn new_atomic(val: Self) -> Self::Atomic {
136                <$atomic>::new(val)
137            }
138            fn load(atomic: &Self::Atomic) -> Self {
139                atomic.load(Ordering::Relaxed)
140            }
141            fn store(atomic: &Self::Atomic, val: Self) {
142                atomic.store(val, Ordering::Relaxed);
143            }
144            fn into_inner(atomic: Self::Atomic) -> Self {
145                atomic.into_inner()
146            }
147        }
148    };
149}
150
151impl_atomic_repr!(bool, std::sync::atomic::AtomicBool);
152impl_atomic_repr!(u8, std::sync::atomic::AtomicU8);
153impl_atomic_repr!(u16, std::sync::atomic::AtomicU16);
154impl_atomic_repr!(u32, std::sync::atomic::AtomicU32);
155impl_atomic_repr!(usize, std::sync::atomic::AtomicUsize);
156impl_atomic_repr!(i8, std::sync::atomic::AtomicI8);
157impl_atomic_repr!(i16, std::sync::atomic::AtomicI16);
158impl_atomic_repr!(i32, std::sync::atomic::AtomicI32);
159impl_atomic_repr!(isize, std::sync::atomic::AtomicIsize);
160
161/// Provides inner mutability for `Copy` types via relaxed atomic operations.
162///
163/// On x86_64 and ARM, relaxed loads and stores compile to the same instructions
164/// as regular memory accesses — no `LOCK` prefix is emitted. This makes
165/// `RelaxedAtomic` a zero-overhead way to achieve interior mutability without
166/// the bus-lock cost of `fetch_*` or CAS operations.
167///
168/// Deliberately exposes only `load` and `store`. The `fetch_*` methods are
169/// omitted because they emit `LOCK`-prefixed instructions with measurable
170/// overhead. Instead, use the load–mutate–store pattern:
171///
172/// ```
173/// # use vtcode_commons::thread_safety::RelaxedAtomic;
174/// let counter = RelaxedAtomic::new(0u32);
175/// let val = counter.load();
176/// counter.store(val + 1);
177/// ```
178///
179/// # When to use
180///
181/// Use when a field needs interior mutability and is accessed without
182/// contention (same pattern as the original C code using plain loads/stores).
183/// If you need multi-step atomic operations (CAS, fetch_add), use the
184/// underlying `std::sync::atomic` types directly.
185///
186/// # When *not* to use
187///
188/// Do not use when the operation must be atomic relative to other threads.
189/// The load–mutate–store pattern is *not* atomic as a whole — it can race
190/// with concurrent stores. Use only where the C code would have used a
191/// non-atomic access that happens to be race-free by design.
192#[derive(Debug)]
193pub struct RelaxedAtomic<T: AtomicRepr> {
194    inner: T::Atomic,
195}
196
197impl<T: AtomicRepr> RelaxedAtomic<T> {
198    /// Create a new `RelaxedAtomic` with the given initial value.
199    #[inline]
200    pub fn new(val: T) -> Self {
201        Self {
202            inner: T::new_atomic(val),
203        }
204    }
205
206    /// Load the current value with relaxed ordering.
207    #[inline]
208    pub fn load(&self) -> T {
209        T::load(&self.inner)
210    }
211
212    /// Store a new value with relaxed ordering.
213    #[inline]
214    pub fn store(&self, val: T) {
215        T::store(&self.inner, val);
216    }
217
218    /// Consume the atomic and return the inner value.
219    pub fn into_inner(self) -> T {
220        T::into_inner(self.inner)
221    }
222}
223
224impl RelaxedAtomic<u32> {
225    /// Atomic add with relaxed ordering.
226    ///
227    /// Returns the previous value. This is an atomic read-modify-write operation
228    /// that compiles to a `LOCK XADD` instruction on x86_64. While it does emit
229    /// a `LOCK` prefix, it avoids the stronger ordering fence overhead of `SeqCst`.
230    ///
231    /// Use this for atomic increments where the load-mutate-store pattern would
232    /// cause race conditions.
233    #[inline]
234    pub fn fetch_add(&self, val: u32) -> u32 {
235        self.inner.fetch_add(val, Ordering::Relaxed)
236    }
237}
238
239impl RelaxedAtomic<u32> {
240    /// Atomic subtract with relaxed ordering.
241    ///
242    /// Returns the previous value. This is an atomic read-modify-write operation
243    /// that compiles to a `LOCK XSUB` instruction on x86_64.
244    #[inline]
245    pub fn fetch_sub(&self, val: u32) -> u32 {
246        self.inner.fetch_sub(val, Ordering::Relaxed)
247    }
248}
249
250/// WARNING: This performs two separate relaxed loads. Under concurrent writes
251/// the two values may come from different points in time. This is a race condition
252/// (not a data race) — Rust does not prevent it.
253///
254/// Use this ONLY for diagnostic assertions, debug checks, or logging.
255/// NEVER use this for correctness-critical decisions like:
256/// - Deciding whether to proceed with an operation
257/// - Checking if a resource is available
258/// - Validating state transitions
259///
260/// For correctness-critical comparisons, load both values atomically first:
261/// ```rust,ignore
262/// let a = atomic_a.load(Ordering::SeqCst);
263/// let b = atomic_b.load(Ordering::SeqCst);
264/// if a == b { /* safe to proceed */ }
265/// ```
266impl<T: AtomicRepr + PartialEq> PartialEq for RelaxedAtomic<T> {
267    fn eq(&self, other: &Self) -> bool {
268        self.load() == other.load()
269    }
270}
271
272impl<T: AtomicRepr + Eq> Eq for RelaxedAtomic<T> {}
273
274impl<T: AtomicRepr + Default> Default for RelaxedAtomic<T> {
275    fn default() -> Self {
276        Self::new(T::default())
277    }
278}
279
280impl<T: AtomicRepr> Clone for RelaxedAtomic<T> {
281    fn clone(&self) -> Self {
282        Self::new(self.load())
283    }
284}
285
286impl<T: AtomicRepr + fmt::Display> fmt::Display for RelaxedAtomic<T> {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        self.load().fmt(f)
289    }
290}
291
292/// Stores the `ThreadId` designated as the application's main thread.
293///
294/// Populated exactly once by [`designate_main_thread`]; subsequent calls are no-ops
295/// so that callers can re-assert designation from defensive initialization paths
296/// without panicking.
297static MAIN_THREAD_ID: OnceLock<ThreadId> = OnceLock::new();
298
299/// Designate the calling thread as the application's main thread.
300///
301/// Should be invoked once, early in `main`, before spawning any worker threads
302/// that may try to obtain a [`MainThreadToken`]. Subsequent calls have no effect.
303pub fn designate_main_thread() {
304    let _ = MAIN_THREAD_ID.set(thread::current().id());
305}
306
307/// Returns the `ThreadId` previously designated as the main thread, if any.
308pub fn main_thread_id() -> Option<ThreadId> {
309    MAIN_THREAD_ID.get().copied()
310}
311
312/// A witness of execution that exists solely on a designated "Main Thread".
313///
314/// In FFI contexts, many libraries (especially legacy C++ or UI frameworks)
315/// are not thread-safe and must only be initialized, called, or dropped from
316/// the same thread that originally created them.
317///
318/// `MainThreadToken` is a zero-sized proof carrier. Possessing it proves
319/// (at a type-system level) that the holder previously executed on the
320/// designated main thread. The `PhantomData<*mut ()>` makes the token
321/// `!Send + !Sync`, so a token obtained on the main thread cannot leak to
322/// another thread through ordinary safe code.
323#[derive(Debug, Clone, Copy, PartialEq, Eq)]
324pub struct MainThreadToken(PhantomData<*mut ()>);
325
326impl MainThreadToken {
327    /// Create a new `MainThreadToken` without verifying the current thread.
328    ///
329    /// # Safety
330    ///
331    /// The caller must guarantee that:
332    /// 1. They are executing on the thread that was (or will be) passed to
333    ///    [`designate_main_thread`], and
334    /// 2. The resulting token will not be transmitted to another thread
335    ///    through `unsafe` channels (the type is `!Send + !Sync`, which
336    ///    prevents safe channels from doing so).
337    #[expect(
338        unsafe_code,
339        reason = "phantom data marker; !Send + !Sync prevents token leakage"
340    )]
341    pub unsafe fn new_unchecked() -> Self {
342        Self(PhantomData)
343    }
344
345    /// Obtain a token if the current thread matches the one previously passed
346    /// to [`designate_main_thread`].
347    ///
348    /// Returns `None` if [`designate_main_thread`] has never been called, or
349    /// if the current thread is not the designated main thread.
350    pub fn try_new() -> Option<Self> {
351        let designated = MAIN_THREAD_ID.get()?;
352        if *designated == thread::current().id() {
353            Some(Self(PhantomData))
354        } else {
355            None
356        }
357    }
358}
359
360/// A wrapper that allows sending non-`Send` types across thread boundaries.
361///
362/// Re-exported from the `send_wrapper` crate. It implements `Send` and `Sync`
363/// regardless of whether the wrapped type is thread-safe. However, it will
364/// panic at runtime if the wrapped value is accessed from any thread other
365/// than the one that created it.
366pub use send_wrapper::SendWrapper;
367
368#[cfg(test)]
369mod tests {
370    use super::*;
371    use std::thread;
372
373    #[test]
374    fn worker_thread_never_obtains_token() {
375        // A spawned worker thread is never the designated main thread, even if
376        // some other test in this process has called `designate_main_thread`
377        // on a different thread. The token type is `!Send`, so we materialize
378        // it inside the worker and return only its presence as a `bool`.
379        let on_worker = thread::spawn(|| MainThreadToken::try_new().is_some())
380            .join()
381            .expect("worker thread");
382        assert!(!on_worker);
383    }
384
385    #[test]
386    fn try_new_returns_some_after_designation_on_same_thread() {
387        designate_main_thread();
388        // If this test happens to run on the same thread that another test
389        // designated, we still get a token; if a different thread was
390        // designated first, `try_new` correctly returns `None`.
391        match main_thread_id() {
392            Some(id) if id == thread::current().id() => {
393                assert!(MainThreadToken::try_new().is_some());
394            }
395            _ => {
396                assert!(MainThreadToken::try_new().is_none());
397            }
398        }
399    }
400}