Skip to content

Commit dc98c47

Browse files
committed
initial commit
0 parents  commit dc98c47

File tree

15 files changed

+707
-0
lines changed

15 files changed

+707
-0
lines changed

.editorconfig

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# EditorConfig is awesome: https://EditorConfig.org
2+
3+
# top-most EditorConfig file
4+
root = true
5+
6+
[*]
7+
indent_style = tab
8+
indent_size = 4
9+
end_of_line = lf
10+
charset = utf-8
11+
trim_trailing_whitespace = true
12+
insert_final_newline = true
13+
14+
[*.yaml]
15+
indent_style = space
16+
indent_size = 2

.github/workflows/ci.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
test:
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
os: [ ubuntu, windows ]
13+
runs-on: ${{ matrix.os }}-latest
14+
name: Test on ${{ matrix.os }}-latest
15+
timeout-minutes: 15
16+
steps:
17+
- uses: actions/checkout@v4
18+
- uses: dtolnay/rust-toolchain@stable
19+
- run: cargo test
20+
21+
doc:
22+
name: Docs
23+
runs-on: ubuntu-latest
24+
timeout-minutes: 15
25+
steps:
26+
- uses: actions/checkout@v4
27+
- uses: dtolnay/rust-toolchain@nightly
28+
- uses: dtolnay/install@cargo-docs-rs
29+
- run: cargo docs-rs
30+

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/.vscode
2+
/Cargo.lock
3+
/target

Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "rcurs"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "An oxidized RCU implementation"
6+
license = "MIT"
7+
repository = "https://github.com/threadexio/rcurs"
8+
readme = "README.md"
9+
keywords = ["rcu", "synchronization", "concurrent", "parallel", "lock-free"]
10+
categories = ["concurrency"]
11+
12+
[dependencies]
13+
14+
[features]
15+
default = ["std"]
16+
17+
std = []

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 1337
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[`core::hint::spin_loop()`]: https://doc.rust-lang.org/stable/core/hint/fn.spin_loop.html
2+
[`Condvar`]: https://doc.rust-lang.org/stable/std/sync/struct.Condvar.html
3+
4+
# rcurs
5+
6+
A simple [RCU](https://en.wikipedia.org/wiki/Read-copy-update) with an oxidized interface. Read more at the [docs](https://docs.rs/rcurs).
7+
8+
The crate supports running both with or without the `std` library but has a hard dependency on `alloc`. If your environment allows, you should try to keep the `std` feature enabled as that contains typically more efficient implementations of blocking primitives.
9+
10+
Without the `std` feature, the only way to block is to spin in place using whatever optimization [`core::hint::spin_loop()`] can provide. But with the standard library, blocking is done using [`Condvar`]s. [`Condvar`]s call out to the kernel for blocking. The kernel can then choose what is best, spin itself, or usually give control back to the scheduler to run other processes.
11+
12+
## Features
13+
14+
- `std`: Enable use of primitives in the standard library

rust-toolchain.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[toolchain]
2+
channel = "stable"
3+
profile = "minimal"
4+
components = ["clippy", "rustfmt"]

rustfmt.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
max_width = 70
2+
hard_tabs = true
3+
tab_spaces = 4
4+
newline_style = "Unix"
5+
use_small_heuristics = "Off"
6+
fn_call_width = 60
7+
attr_fn_like_width = 60
8+
struct_lit_width = 60
9+
struct_variant_width = 60
10+
array_width = 60
11+
chain_width = 60
12+
single_line_if_else_max_width = 0
13+
reorder_imports = true
14+
reorder_modules = true
15+
remove_nested_parens = true
16+
match_arm_leading_pipes = "Never"
17+
fn_params_layout = "Tall"
18+
match_block_trailing_comma = true
19+
edition = "2021"
20+
merge_derives = true
21+
use_try_shorthand = true
22+
use_field_init_shorthand = true
23+
force_explicit_abi = true

src/cfg.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
macro_rules! cfg_std {
2+
($($item:item)*) => {
3+
$(
4+
#[cfg(feature = "std")]
5+
$item
6+
)*
7+
};
8+
}
9+
10+
pub(crate) use cfg_std;

src/lib.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//! [`Rcu`] or Read-Copy-Update is a mechanism that allows sharing and updating
2+
//! a piece of data while multiple parallel code blocks have access to it in a
3+
//! lock-free manner. A better explanation is available at the [kernel docs](https://www.kernel.org/doc/html/latest/RCU/whatisRCU.html)
4+
//! or even [Wikipedia](https://en.wikipedia.org/wiki/Read-copy-update).
5+
//!
6+
//! Internally, an RCU is composed of an _atomic_ pointer (the atomic part
7+
//! is important) to some data along with the number of currently active
8+
//! references to it. Something like a garbage-collector (but obviously way
9+
//! simpler). So when you want to get at the actual data, you simply increment
10+
//! the counter of references by one and decrement it when you are done. This
11+
//! is the easy part, now how do we update the value without locking and
12+
//! without messing up anyone already working? Well, each reference does not
13+
//! keep a pointer to the RCU, but instead copies the value of the atomic
14+
//! pointer when it is created. The key is that the pointer to the data is
15+
//! atomic. So we simply create a new copy of data, make our changes to that
16+
//! copy, and then atomically update the pointer. This way we ensure that a
17+
//! reference that is created at the same time as we update it uses either:
18+
//! the old value or the new value. In any case, it does not end up with an
19+
//! invalid pointer. Because we are a model programmer, we must also not
20+
//! forget to clean up the old data which is effectively outdated and useless.
21+
//! But we can't just clean up the old data: there might be references
22+
//! to it from before we did the update. Ah, so we will wait for the code
23+
//! with old references to drop them, and because we swapped the pointer before,
24+
//! no new references can get access to the old data. After waiting and ensuring
25+
//! there are no remaining references to the old data, we can now safely free
26+
//! it without worry.
27+
//!
28+
//! # Example
29+
//!
30+
//! ```rust,no_run
31+
//! use std::thread;
32+
//! use std::time::Duration;
33+
//!
34+
//! type Rcu<T> = rcurs::Rcu<T, rcurs::Spin>;
35+
//!
36+
//! #[derive(Debug, Clone, PartialEq, Eq)]
37+
//! struct User {
38+
//! uid: i32,
39+
//! gid: i32,
40+
//! }
41+
//!
42+
//! fn setugid(user: &Rcu<User>, uid: i32, gid: i32) {
43+
//! let mut new = user.get().clone();
44+
//!
45+
//! if new.uid == uid && new.gid == gid {
46+
//! return;
47+
//! }
48+
//!
49+
//! new.uid = uid;
50+
//! new.gid = gid;
51+
//!
52+
//! user.update(new);
53+
//! }
54+
//!
55+
//! // Basically a `sleep`` function that holds onto `user` and prints it after
56+
//! // `sec` seconds have passed.
57+
//! fn compute(user: &Rcu<User>, id: &str, sec: u64) {
58+
//! let user = user.get();
59+
//! thread::sleep(Duration::from_secs(sec));
60+
//! println!("compute[{id}]: finish work for {}:{}", user.uid, user.gid);
61+
//! }
62+
//!
63+
//! fn thread_1(user: &Rcu<User>) {
64+
//! compute(user, "1", 3);
65+
//!
66+
//! // This call will update `user`.
67+
//! setugid(user, 1000, 1000);
68+
//!
69+
//! compute(user, "3", 4);
70+
//! }
71+
//!
72+
//! fn thread_2(user: &Rcu<User>) {
73+
//! // The following compute call will always see the `User { uid: 0, gid: 0}`
74+
//! // as the `setugid` call happens 3 seconds after it has started executing.
75+
//! compute(user, "2", 5);
76+
//! }
77+
//!
78+
//! fn main() {
79+
//! let user = Rcu::new(User { uid: 0, gid: 0 });
80+
//!
81+
//! thread::scope(|scope| {
82+
//! scope.spawn(|| thread_1(&user));
83+
//! scope.spawn(|| thread_2(&user));
84+
//! });
85+
//! }
86+
//! ```
87+
#![deny(missing_docs)]
88+
#![warn(
89+
clippy::all,
90+
clippy::correctness,
91+
clippy::pedantic,
92+
clippy::cargo,
93+
clippy::nursery,
94+
clippy::perf,
95+
clippy::style
96+
)]
97+
#![allow(
98+
clippy::missing_panics_doc,
99+
clippy::significant_drop_tightening,
100+
clippy::needless_lifetimes
101+
)]
102+
#![cfg_attr(not(feature = "std"), no_std)]
103+
104+
mod cfg;
105+
106+
mod notify;
107+
mod rcu;
108+
109+
#[doc(inline)]
110+
pub use self::notify::*;
111+
112+
#[doc(inline)]
113+
pub use self::rcu::{Guard, Rcu};

src/notify/blocking.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use super::Notify;
2+
3+
use std::sync::{Condvar, Mutex};
4+
5+
/// A [`Notify`] backend that uses a [`Condvar`] to achieve true blocking.
6+
pub struct Blocking {
7+
/* Keep track of both whether notify was called and how many waiter there are.
8+
* This way, the waiter who wakes up last can know to reset the `notified` flag
9+
* again to prepare for the next `notify`.
10+
*/
11+
lock: Mutex<(bool, u8)>,
12+
var: Condvar,
13+
}
14+
15+
impl Notify for Blocking {
16+
fn new() -> Self {
17+
Self {
18+
lock: Mutex::new((false, 0)),
19+
var: Condvar::new(),
20+
}
21+
}
22+
23+
fn wait(&self) {
24+
let mut guard = self.lock.lock().unwrap();
25+
guard.1 += 1;
26+
27+
let mut guard = self
28+
.var
29+
.wait_while(guard, |(notified, _)| !*notified)
30+
.unwrap();
31+
guard.1 -= 1;
32+
33+
if guard.1 == 0 {
34+
guard.0 = false;
35+
}
36+
}
37+
38+
fn notify(&self) {
39+
let mut guard = self.lock.lock().unwrap();
40+
guard.0 = true;
41+
self.var.notify_all();
42+
}
43+
}
44+
45+
impl Default for Blocking {
46+
fn default() -> Self {
47+
Self::new()
48+
}
49+
}

0 commit comments

Comments
 (0)