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}