-
Notifications
You must be signed in to change notification settings - Fork 28
Description
.. guideline:: gui_TornReadWrite
:category: Concurrency
:status: Draft
:rule:
Prevent torn reads and writes during concurrent shared data access
.. rationale:: gui_TornReadWrite
A torn read (or torn write) occurs when a memory operation is not atomic at the hardware level,
allowing one thread to observe a partially-updated value written by another thread.
On x86-64, for example, naturally-aligned accesses up to 8 bytes are guaranteed atomic by the hardware.
However, tearing can occur when:
-
Misaligned access crosses a cache line boundary — A 64-bit value straddling a 64-byte cache line boundary may be written in two separate bus transactions, allowing another core to observe an intermediate state.
-
Access size exceeds native word width — Operations on 128-bit values (e.g.,
u128) are not atomic on most architectures without special instructions. -
Compiler optimizations — Without proper atomic or volatile semantics, the compiler may split or reorder memory accesses.
In safety-critical systems, torn reads can cause:
- Corrupted sensor readings leading to incorrect control decisions
- Invalid state machine transitions
- Violated invariants in concurrent data structures
- Non-deterministic behavior that is extremely difficult to reproduce and diagnose
The Rust memory model, following C++11, does not guarantee any atomicity for non-atomic types accessed concurrently.
Data races on non-atomic types are undefined behavior, regardless of hardware atomicity guarantees.
.. non_compliant_example:: gui_TornReadWrite
In this example, a u64 is shared between threads using raw pointers without atomic operations.
Even if the value happens to be aligned, this constitutes a data race and is undefined behavior.
If the value crosses a cache line boundary, torn reads become likely:
.. code-block:: rust
use std::thread;
static mut SHARED: u64 = 0;
fn main() {
// UNSAFE: Data race - undefined behavior
let writer = thread::spawn(|| {
for _ in 0..1_000_000 {
unsafe {
// Non-atomic write to shared data
SHARED = 0xFFFF_FFFF_FFFF_FFFF;
SHARED = 0;
}
}
});
let reader = thread::spawn(|| {
for _ in 0..1_000_000 {
unsafe {
// Non-atomic read - may observe torn value
let val = SHARED;
// val could be 0, 0xFFFFFFFFFFFFFFFF,
// or a torn value like 0x00000000FFFFFFFF
process(val);
}
}
});
writer.join().unwrap();
reader.join().unwrap();
}
.. compliant_example:: gui_TornReadWrite
The corrected example uses AtomicU64 to ensure all accesses are atomic and free from tearing:
.. code-block:: rust
use std::sync::atomic::{AtomicU64, Ordering};
use std::thread;
static SHARED: AtomicU64 = AtomicU64::new(0);
fn main() {
let writer = thread::spawn(|| {
for _ in 0..1_000_000 {
// Atomic write - guaranteed not to tear
SHARED.store(0xFFFF_FFFF_FFFF_FFFF, Ordering::Relaxed);
SHARED.store(0, Ordering::Relaxed);
}
});
let reader = thread::spawn(|| {
for _ in 0..1_000_000 {
// Atomic read - guaranteed to observe a complete value
let val = SHARED.load(Ordering::Relaxed);
// val is guaranteed to be either 0 or 0xFFFFFFFFFFFFFFFF
process(val);
}
});
writer.join().unwrap();
reader.join().unwrap();
}
For larger types or complex data structures, use a Mutex or RwLock:
.. code-block:: rust
use std::sync::{Arc, RwLock};
use std::thread;
#[derive(Clone, Default)]
struct SensorData {
timestamp: u64,
position: [f64; 3],
velocity: [f64; 3],
}
fn main() {
let data = Arc::new(RwLock::new(SensorData::default()));
let data_writer = Arc::clone(&data);
let writer = thread::spawn(move || {
loop {
let reading = read_sensor();
// Lock ensures entire struct is updated atomically
let mut guard = data_writer.write().unwrap();
*guard = reading;
}
});
let data_reader = Arc::clone(&data);
let reader = thread::spawn(move || {
loop {
// Lock ensures entire struct is read atomically
let guard = data_reader.read().unwrap();
let snapshot = guard.clone();
drop(guard);
process(snapshot);
}
});
}
.. bibliography:: gui_TornReadWrite
.. list-table::
:widths: 10 90
* - :cite:t:`rustonomicon-atomics`
- "Atomics." *The Rustonomicon*. Accessed December 18, 2025. https://doc.rust-lang.org/nomicon/atomics.html.
* - :cite:t:`rust-reference-data-races`
- "Behavior considered undefined: Data races." *The Rust Reference*. Accessed December 18, 2025. https://doc.rust-lang.org/reference/behavior-considered-undefined.html.
* - :cite:t:`intel-sdm-vol3`
- Intel Corporation. *Intel® 64 and IA-32 Architectures Software Developer's Manual, Volume 3A: System Programming Guide*. Section 9.1.1, "Guaranteed Atomic Operations." 2024.
* - :cite:t:`boehm-2005`
- Boehm, Hans-J. "Threads Cannot Be Implemented as a Library." *ACM SIGPLAN Notices* 40, no. 6 (June 2005): 261–268. https://doi.org/10.1145/1065010.1065042.
* - :cite:t:`adve-gharachorloo-1996`
- Adve, Sarita V., and Kourosh Gharachorloo. "Shared Memory Consistency Models: A Tutorial." *Computer* 29, no. 12 (December 1996): 66–76.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status