Safe Lock-free Primitives with iceoryx2's ByteAtomic

Hacker News Top Tools

Summary

This article introduces iceoryx2's ByteAtomic, a byte-wise atomic wrapper that prevents undefined behavior when implementing lock-free primitives like sequence locks in Rust and C++.

No content available
Original Article
View Cached Full Text

Cached at: 08/04/26, 01:45 PM

# Safe Lock-free Primitives with iceoryx2's ByteAtomic Source: [https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/](https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/) Marika Lehmann \- 28/07/2026 ## [Data Races and Sequence Lock](https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#data-races-and-sequence-lock) In multithreaded programming, a common scenario involves multiple threads reading from and modifying shared data concurrently\. If this read and write operations are not atomic, a data race occurs\. In languages like Rust and C\+\+, which have almost the same memory model, this results in undefined behavior\. To prevent this, locks can be used to protect the data from being modified while it is being read\. However, traditional locking mechanisms carry the risk of deadlocks which is unacceptable, especially in safety\-critical and high\-reliability systems\. A common approach to mitigating the described data race without using blocking locks is to utilize a sequence lock\. The sequence lock contains the shared data and an atomic counter that has an odd value whenever the data is being updated: Using a sequence lock, a writer thread increments the sequence counter to an odd value, updates the data, and then increments the counter to an even value\. A reader thread reads the sequence counter both before and after copying the shared data\. If the counter has changed or is currently odd, it indicates that the data was concurrently modified\. The reader then discards the corrupted copy and retries\. ![sequence-lock](https://ekxide.io/_ipx/_/blog-images/sequence-lock.png) **The Problem:**Even if the reader detects that the data was modified and discards the copy before use, the act of copying the non\-atomic data itself still triggers undefined behavior\. While a sequence lock can*detect*that a data race occurred, it does*not prevent*it\. Consequently, it is currently not possible to implement a correct sequence lock in Rust or C\+\+ without decomposing the data into smaller, individually atomic parts\. This is a known problem, and while there are ongoing proposals to introduce an "atomic memcpy"[1](https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#user-content-fn-1)[2](https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#user-content-fn-2)to the Rust and C\+\+ standard libraries, we cannot rely on that feature yet\. Targeting safety\-critical and high\-reliability systems, iceoryx2 provides a[library of lock\-free constructs](https://docs.rs/iceoryx2-bb-lock-free/latest/iceoryx2_bb_lock_free/)that are based on mechanisms similar to a sequence lock\. To make these constructs safe and correct, we need a way to perform memory copies that are atomic at the byte level, ensuring no data races occur\. This is why we implemented the byte\-wise atomic wrapper[`ByteAtomic`](https://github.com/eclipse-iceoryx/iceoryx2/blob/f0a1e6d03459908b41938bf5c89a1cd29f588da8/iceoryx2-bb/container/src/byte_atomic.rs), which we will describe in the following sections\. While its concept is simple, achieving true safety required overcoming a subtle but critical issue with uninitialized memory\. ## [Solution: A Byte\-wise Atomic Wrapper](https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#solution-a-byte-wise-atomic-wrapper) To prevent the aforementioned data race and thus the undefined behavior, the`ByteAtomic`in iceoryx2 provides byte\-wise atomic read and write operations on its inner type\. This wrapper only guarantees that*each byte*is updated/read atomically; it does*not*provide higher\-level thread\-safety guarantees\. Users must still enforce proper synchronization \(such as a sequence lock\) to prevent torn reads or writes\. The wrapper only ensures that the memory copy is not undefined behavior, but it does not guarantee data integrity on its own\. ### [Implementation](https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#implementation) The wrapper's implementation has undergone some refinement as we addressed the complexities of memory safety\. The initial version of our`ByteAtomic`wrapper looked like this: It is named`FixedSizeByteAtomic`because the array size must be provided at compile time, as Rust does not yet allow using`core::mem::size\_of::<T\>\(\)`directly in a struct definition\. Once this becomes possible, we plan to remove the`SIZE`generic parameter, remove the runtime fixed\-size version`RelocatableByteAtomic`, and rename the struct to`ByteAtomic`\. #### [Padding Bytes](https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#padding-bytes) To understand why the implementation had to evolve, let's take a look at the initial, naive implementation of`new\(\)`: This version of`new\(\)`accepts a copyable value, performs a`transmute\_copy`into a byte array, and stores every byte as an`AtomicU8`into the ByteAtomic's`data`field\. This works fine \- unless`T`contains uninitialized memory, such as a`MaybeUninit`or padding bytes: [`transmute\_copy`](https://doc.rust-lang.org/std/mem/fn.transmute_copy.html)assumes that the value being copied is a valid representation of the destination type, in our case a valid`u8`\. This assumption fails for padding bytes because they are uninitialized memory; reading them leads to undefined behavior[3](https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#user-content-fn-3)\. Therefore, we have to ensure that we only copy the fields \(i\.e\., the initialized bytes\) of the passed value\. This led to the current, correct implementation of`new\(\)`: We now require the inner type`T`to implement the[`AtomicCopy`](https://github.com/eclipse-iceoryx/iceoryx2/blob/f0a1e6d03459908b41938bf5c89a1cd29f588da8/iceoryx2-bb/elementary-traits/src/atomic_copy.rs)trait from iceoryx2 for types that can be atomically copied\. It provides`for\_each\_field\(\)`, a field\-wise accessor for byte\-wise copying\. This method applies the provided callback to each offset\-size pair of every field in`T`\. With this,`new\(\)`copies only the initialized bytes of`value`into the`data`field, effectively skipping potential padding bytes\. Of course, implementations of the`AtomicCopy`trait must ensure that the offset and size of each field are calculated correctly; otherwise, undefined behavior may still occur\. Note that the return type of`read\(\)`has also evolved\. In its initial version,`read\(\)`returned a`MaybeUnint<T\>`to alert the user that, while the`ByteAtomic`prevents undefined behavior during memory copies, torn reads can still occur\. To emphasize this risk, we changed the return type to[`MaybeTorn<T\>`](https://github.com/eclipse-iceoryx/iceoryx2/blob/f0a1e6d03459908b41938bf5c89a1cd29f588da8/iceoryx2-bb/container/src/byte_atomic.rs#L105)\. This type wraps a`MaybeUninit<T\>`and serves as a constant reminder that the data integrity is not yet guaranteed\. Only after verifying that no concurrent writes occurred can the user safely call`assume\_consistent\(\)`to extract the read value\. Otherwise, the returned`T`may be logically invalid and its use could lead to undefined behavior\. ### [Usage](https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#usage) The manual implementation of`AtomicCopy`for`Foo`would look like this: For convenience, we have implemented`AtomicCopy`for all scalar types and provided a[derive macro](https://docs.rs/iceoryx2-bb-derive-macros/latest/iceoryx2_bb_derive_macros/derive.AtomicCopy.html)\. This macro automatically implements the trait for all structs whose fields also implement`AtomicCopy`\. This is how it looks like in use for`Foo`: ## [Conclusion](https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#conclusion) Writing correct lock\-free code is difficult\. Even the "simple" and well\-known sequence lock, which often forms the basis for more complex lock\-free constructs, entails data races and undefined behavior\. While a future standard library "atomic memcpy" would be the ideal and efficient solution, the byte\-wise atomic wrapper provided by iceoryx2 enables developers to implement a correct and safe sequence lock and other lock\-free primitives today\. We are working to integrate this wrapper into our existing lock\-free constructs to finalize their transition to a fully safe implementation\. - [Discuss on iceoryx2 community forum](https://community.iceoryx.io/t/safe-lock-free-primitives-with-iceoryx2s-byteatomic/19) - [Discuss on Reddit](https://www.reddit.com/r/programming/comments/1vf68qb/safe_lockfree_primitives_with_iceoryx2s_byteatomic/) - [Discuss on programming\.dev](https://programming.dev/post/54554551) 1. [https://github\.com/rust\-lang/rfcs/pull/3301](https://github.com/rust-lang/rfcs/pull/3301)[↩](https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#user-content-fnref-1) 2. [https://www\.open\-std\.org/jtc1/sc22/wg21/docs/papers/2022/p1478r7\.html](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1478r7.html)[↩](https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#user-content-fnref-2) 3. Using`copy\_nonoverlapping`would shift the problem to the`AtomicU8`creation\.[↩](https://ekxide.io/blog/byte-wise-atomic-wrapper-to-prevent-ub/#user-content-fnref-3)

Similar Articles

Safe Made Easy Pt.1: Single Ownership is (Not) Optional

Lobsters Hottest

This article introduces a new approach to memory safety based on linear types and abstract interpretation, aiming to eliminate common bugs like use-after-free and memory leaks more ergonomically than Rust.

Safe SIMD in Rust, even on the inside

Lobsters Hottest

Rust's SIMD abstractions now allow safe usage without unsafe code by leveraging CPU feature tokens introduced in Rust 1.87, enabling concise and portable vector operations.

The Edge of Safe Rust

Lobsters Hottest

A TokioConf 2026 talk/blog post explores pushing safe Rust to its limits by implementing tracing garbage collection for complex pointer structures, sharing techniques for circular references and raw-pointer GC design.