Skip to main content

vtcode_commons/
thread_safety.rs

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