Skip to content

Commit 132a1e4

Browse files
committed
feat: loom support
1 parent 4b7b295 commit 132a1e4

12 files changed

Lines changed: 137 additions & 12 deletions

File tree

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ local-event = { version = "0.1.1", default-features = false, optional = true }
1717
see = { version = "0.1.1", optional = true }
1818
slab = { version = "0.4.11", optional = true }
1919

20+
2021
[features]
2122
watch = ["dep:see"]
2223
mutex = ["dep:slab"]
@@ -25,5 +26,11 @@ event = ["dep:event-listener", "dep:local-event"]
2526
bilock = ["waker_slot"]
2627
async_flag = ["waker_slot"]
2728

29+
[target.'cfg(loom)'.dependencies]
30+
loom = { version = "0.7.2", features = ["futures"] }
31+
2832
[dev-dependencies]
2933
futures = { version = "0.3.31", features = ["executor"] }
34+
35+
[lints.rust]
36+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(loom)'] }

README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Synchrony
22

3+
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/compio-rs/synchrony/blob/master/LICENSE)
4+
[![crates.io](https://img.shields.io/crates/v/synchrony)](https://crates.io/crates/synchrony)
5+
[![docs.rs](https://img.shields.io/badge/docs.rs-synchrony-latest)](https://docs.rs/synchrony)
6+
[![Check](https://github.com/compio-rs/synchrony/actions/workflows/ci_check.yml/badge.svg)](https://github.com/compio-rs/synchrony/actions/workflows/ci_check.yml)
7+
[![Telegram](https://img.shields.io/badge/Telegram-compio--rs-blue?logo=telegram)](https://t.me/compio_rs)
8+
[![Discord](https://img.shields.io/discord/1429103613602435114?logo=discord&label=Discord)](https://discord.gg/bGG8EF9v)
9+
310
A library that provides both sync and unsync versions of common synchronization primitives.
411

512
## Features
@@ -8,11 +15,33 @@ All of the following primitives are provided in both sync and unsync versions:
815

916
- Shared (`Rc`/`Arc`)
1017
- Atomic Scalars
11-
- Watch
18+
- Watch
1219
- Waker Slot (`AtomicWaker` and its unsync counterpart)
1320
- Mutex
1421
- Async Mutex
1522
- BiLock
1623
- Flag (specialized `AtomicBool`)
1724
- Event (`event-listener` and `local-event`)
1825
- Async Flag
26+
27+
## Loom Testing Support
28+
29+
This library includes built-in support for [loom](https://github.com/tokio-rs/loom), a testing tool for concurrent Rust code that helps verify the correctness of concurrent algorithms.
30+
31+
When compiled with `--cfg loom`, the library automatically switches to use loom's implementations of synchronization primitives instead of the standard library versions. This enables you to:
32+
33+
- Detect data races and concurrency bugs
34+
- Verify lock-free algorithms
35+
- Test different thread interleavings exhaustively
36+
37+
For more information about loom and how to write loom tests, see the [loom documentation](https://docs.rs/loom).
38+
39+
### Supported Types
40+
41+
The following types automatically use loom implementations when the `loom` cfg is enabled:
42+
43+
- `std::sync::atomic::*` types (`AtomicBool`, `AtomicUsize`, etc.)
44+
- `std::sync::Arc``loom::sync::Arc`
45+
- `std::sync::Mutex``loom::sync::Mutex`
46+
- `std::cell::UnsafeCell``loom::cell::UnsafeCell`
47+
- `std::cell::Cell``loom::cell::Cell`

bacon.toml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
default_job = "clippy-loom"
2+
env.CARGO_TERM_COLOR = "always"
3+
4+
[jobs.clippy-loom]
5+
command = ["cargo", "clippy"]
6+
env.RUSTFLAGS = "--cfg loom"
7+
need_stdout = false
8+
9+
[jobs.test]
10+
command = [
11+
"cargo", "nextest", "run",
12+
"--hide-progress-bar", "--failure-output", "final"
13+
]
14+
need_stdout = true
15+
analyzer = "nextest"
16+
17+
[jobs.doc]
18+
command = ["cargo", "doc", "--no-deps"]
19+
need_stdout = false
20+
21+
[jobs.doc-open]
22+
command = ["cargo", "doc", "--no-deps", "--open"]
23+
need_stdout = false
24+
on_success = "back"
25+
26+
[keybindings]
27+
c = "job:clippy-loom"
28+
d = "job:doc-open"
29+
t = "job:test"

flake.nix

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
description = "Compio dev shell";
2+
description = "Synchrony dev shell";
33

44
inputs = {
55
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
@@ -27,7 +27,9 @@
2727
{
2828
devShells.default = mkShell {
2929
buildInputs = [
30+
bacon
3031
cargo-hack
32+
cargo-nextest
3133
(rust-bin.selectLatestNightlyWith (
3234
toolchain:
3335
toolchain.default.override {

src/atomic/mod.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
33
/// Multithreaded atomic scalars
44
pub mod sync {
5-
pub use std::sync::atomic::{
6-
AtomicBool, AtomicI8, AtomicI16, AtomicI32, AtomicI64, AtomicIsize, AtomicU8, AtomicU16,
7-
AtomicU32, AtomicU64, AtomicUsize,
8-
};
5+
crate::cfg_loom! {
6+
pub use std::sync::atomic::{
7+
AtomicBool, AtomicI8, AtomicI16, AtomicI32, AtomicI64, AtomicIsize, AtomicU8, AtomicU16,
8+
AtomicU32, AtomicU64, AtomicUsize,
9+
};
10+
}
911
}
1012

1113
/// Singlethreaded atomic scalars based on [`std::cell::Cell`]

src/atomic/unsync.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use std::{cell::Cell, fmt::Debug, sync::atomic::Ordering};
1+
use std::{fmt::Debug, sync::atomic::Ordering};
2+
3+
crate::cfg_loom! {
4+
use std::cell::Cell;
5+
}
26

37
atomic_int!(AtomicU8(u8));
48
atomic_int!(AtomicU16(u16));
@@ -41,11 +45,19 @@ impl Debug for AtomicBool {
4145

4246
impl AtomicBool {
4347
/// Creates a new [`AtomicBool`]
48+
#[cfg(not(loom))]
4449
pub const fn new(val: bool) -> Self {
4550
Self { v: Cell::new(val) }
4651
}
4752

53+
/// Creates a new [`AtomicBool`]
54+
#[cfg(loom)]
55+
pub fn new(val: bool) -> Self {
56+
Self { v: Cell::new(val) }
57+
}
58+
4859
/// Returns a mutable reference to the underlying boolean.
60+
#[cfg(not(loom))]
4961
pub fn get_mut(&mut self) -> &mut bool {
5062
self.v.get_mut()
5163
}
@@ -195,12 +207,20 @@ macro_rules! atomic_int {
195207
}
196208

197209
impl $t {
210+
#[cfg(not(loom))]
198211
#[doc = concat!("Creates a new [`", stringify!($t), "`]")]
199212
pub const fn new(val: $i) -> Self {
200213
Self { v: Cell::new(val) }
201214
}
202215

216+
#[cfg(loom)]
217+
#[doc = concat!("Creates a new [`", stringify!($t), "`]")]
218+
pub fn new(val: $i) -> Self {
219+
Self { v: Cell::new(val) }
220+
}
221+
203222
/// Returns a mutable reference to the underlying integer.
223+
#[cfg(not(loom))]
204224
pub fn get_mut(&mut self) -> &mut $i {
205225
self.v.get_mut()
206226
}

src/bilock.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,17 @@ pub mod unsync {
1818
macro_rules! impl_bilock {
1919
($sync:ident) => {
2020
use std::{
21-
cell::UnsafeCell,
2221
fmt::Debug,
2322
future::Future,
2423
ops::{Deref, DerefMut},
2524
pin::Pin,
2625
task::{Context, Poll},
2726
};
2827

28+
crate::cfg_loom! {
29+
use std::cell::UnsafeCell;
30+
}
31+
2932
use crate::$sync::{flag::Flag, shared::Shared, waker_slot::WakerSlot};
3033

3134
/// A lock shared by two parties.

src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,17 @@ pub mod unsync {
118118
/// A trait to assert that a type is `Send + Sync`.
119119
#[allow(dead_code)]
120120
trait AssertMt: Send + Sync {}
121+
122+
macro_rules! cfg_loom {
123+
{
124+
$vis:vis use $alt:ident :: $($tail:tt)*
125+
} => {
126+
#[cfg(loom)]
127+
$vis use loom::$($tail)*
128+
129+
#[cfg(not(loom))]
130+
$vis use $alt::$($tail)*
131+
};
132+
}
133+
134+
use cfg_loom;

src/mutex.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ const WAIT_KEY_NONE: usize = usize::MAX;
7979
macro_rules! impl_mutex {
8080
($sync:ident) => {
8181
use std::{
82-
cell::UnsafeCell,
8382
fmt,
8483
future::Future,
8584
marker::PhantomData,
@@ -93,6 +92,11 @@ macro_rules! impl_mutex {
9392
use slab::Slab;
9493

9594
use super::*;
95+
96+
crate::cfg_loom! {
97+
use std::cell::UnsafeCell;
98+
}
99+
96100
use crate::$sync::{
97101
atomic::AtomicUsize, mutex_blocking::Mutex as BlockingMutex, shared::Shared,
98102
};

src/mutex_blocking.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
33
/// Multithreaded blocking Mutex
44
pub mod sync {
5+
crate::cfg_loom! {
6+
use std::sync::{Mutex as Inner, MutexGuard as InnerGuard};
7+
}
58
use std::{
69
fmt,
710
ops::{Deref, DerefMut},
8-
sync::{Mutex as Inner, MutexGuard as InnerGuard},
911
};
1012

1113
/// A multithreaded Mutex based on [`std::sync::Mutex`].
@@ -19,10 +21,19 @@ pub mod sync {
1921

2022
impl<T> Mutex<T> {
2123
/// Creates a new mutex in an unlocked state ready for use.
24+
#[cfg(not(loom))]
2225
pub const fn new(val: T) -> Self {
2326
Self(Inner::new(val))
2427
}
2528

29+
/// Creates a new mutex in an unlocked state ready for use.
30+
///
31+
/// This `new` is not `const` due to loom not supporting it.
32+
#[cfg(loom)]
33+
pub fn new(val: T) -> Self {
34+
Self(Inner::new(val))
35+
}
36+
2637
/// Get the inner [`std::sync::Mutex`].
2738
pub fn into_inner(self) -> Inner<T> {
2839
self.0

0 commit comments

Comments
 (0)