Skip to content

Commit 78c6ae2

Browse files
committed
feat: ngx::sync::RwLock implementation
1 parent bafb5b8 commit 78c6ae2

File tree

6 files changed

+147
-2
lines changed

6 files changed

+147
-2
lines changed

Cargo.lock

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ rust-version.workspace = true
2626

2727
[dependencies]
2828
allocator-api2 = { version = "0.2.21", default-features = false }
29+
lock_api = "0.4.13"
2930
nginx-sys = { path = "nginx-sys", default-features=false, version = "0.5.0"}
3031

3132
[features]

nginx-sys/build/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const NGX_CONF_FEATURES: &[&str] = &[
2727
"have_kqueue",
2828
"have_memalign",
2929
"have_posix_memalign",
30+
"have_sched_yield",
3031
"have_variadic_macros",
3132
"http",
3233
"http_cache",

nginx-sys/src/lib.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,23 @@ pub fn ngx_random() -> core::ffi::c_long {
163163
}
164164
}
165165

166+
/// Causes the calling thread to relinquish the CPU.
167+
#[inline]
168+
pub fn ngx_sched_yield() {
169+
#[cfg(windows)]
170+
unsafe {
171+
SwitchToThread()
172+
};
173+
#[cfg(all(not(windows), ngx_feature = "have_sched_yield"))]
174+
unsafe {
175+
sched_yield()
176+
};
177+
#[cfg(not(any(windows, ngx_feature = "have_sched_yield")))]
178+
unsafe {
179+
usleep(1)
180+
}
181+
}
182+
166183
/// Returns cached timestamp in seconds, updated at the start of the event loop iteration.
167184
///
168185
/// Can be stale when accessing from threads, see [ngx_time_update].

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ pub mod http;
6868
/// This module provides an interface into the NGINX logger framework.
6969
pub mod log;
7070

71+
pub mod sync;
72+
7173
/// Define modules exported by this library.
7274
///
7375
/// These are normally generated by the Nginx module system, but need to be

src/sync.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//! Synchronization primitives over shared memory.
2+
//!
3+
//! This module provides an alternative implementation for the `ngx_atomic_t` type,
4+
//! `ngx_atomic_*`/`ngx_rwlock_*` family of functions and related usage patterns from nginx.
5+
//!
6+
//! `<ngx_atomic.h>` contains a wide variety of implementation variants for different platforms and
7+
//! build configurations. It's not feasible to properly expose all of these to the Rust code, and we
8+
//! are not going to. The implementation here uses similar logic on the foundation of the
9+
//! [core::sync::atomic] types and is intentionally _not interoperable_ with the nginx atomics.
10+
//! Thus, it's only suitable for use for new shared memory structures instead of, for example,
11+
//! interacting with the upstream zones.
12+
//!
13+
//! One potential pitfall here is that atomics in Rust are specified in terms of threads, and we use
14+
//! the types in this module for interprocess synchronization. This should not be an issue though,
15+
//! as Rust refers to the C/C++11 memory model for atomics, and there's a following note in
16+
//! [atomics.lockfree]:
17+
//!
18+
//! > [Note: Operations that are lock-free should also be address-free. That is, atomic operations
19+
//! > on the same memory location via two different addresses will communicate atomically. The
20+
//! > implementation should not depend on any per-process state. This restriction enables
21+
//! > communication via memory that is mapped into a process more than once and by memory that is
22+
//! > shared between two processes. — end note]
23+
//!
24+
//! In practice, this recommendation is applied in all the implementations that matter to us.
25+
use core::sync::atomic::{self, Ordering};
26+
27+
use nginx_sys::ngx_sched_yield;
28+
29+
const NGX_RWLOCK_SPIN: usize = 2048;
30+
const NGX_RWLOCK_WLOCK: usize = usize::MAX;
31+
32+
type NgxAtomic = atomic::AtomicUsize;
33+
34+
/// Raw lock type.
35+
///
36+
pub struct RawSpinlock(NgxAtomic);
37+
38+
/// Reader-writer lock over an atomic variable, based on the nginx rwlock implementation.
39+
pub type RwLock<T> = lock_api::RwLock<RawSpinlock, T>;
40+
41+
/// RAII structure used to release the shared read access of a lock when dropped.
42+
pub type RwLockReadGuard<'a, T> = lock_api::RwLockReadGuard<'a, RawSpinlock, T>;
43+
44+
/// RAII structure used to release the exclusive write access of a lock when dropped.
45+
pub type RwLockWriteGuard<'a, T> = lock_api::RwLockWriteGuard<'a, RawSpinlock, T>;
46+
47+
unsafe impl lock_api::RawRwLock for RawSpinlock {
48+
// Only used for initialization, will not be mutated
49+
#[allow(clippy::declare_interior_mutable_const)]
50+
const INIT: RawSpinlock = RawSpinlock(NgxAtomic::new(0));
51+
52+
type GuardMarker = lock_api::GuardNoSend;
53+
54+
fn lock_shared(&self) {
55+
loop {
56+
if self.try_lock_shared() {
57+
return;
58+
}
59+
60+
if unsafe { nginx_sys::ngx_ncpu > 1 } {
61+
for n in 0..NGX_RWLOCK_SPIN {
62+
for _ in 0..n {
63+
core::hint::spin_loop()
64+
}
65+
66+
if self.try_lock_shared() {
67+
return;
68+
}
69+
}
70+
}
71+
72+
ngx_sched_yield()
73+
}
74+
}
75+
76+
fn try_lock_shared(&self) -> bool {
77+
let value = self.0.load(Ordering::Acquire);
78+
79+
if value == NGX_RWLOCK_WLOCK {
80+
return false;
81+
}
82+
83+
self.0
84+
.compare_exchange(value, value + 1, Ordering::Acquire, Ordering::Relaxed)
85+
.is_ok()
86+
}
87+
88+
unsafe fn unlock_shared(&self) {
89+
self.0.fetch_sub(1, Ordering::Release);
90+
}
91+
92+
fn lock_exclusive(&self) {
93+
loop {
94+
if self.try_lock_exclusive() {
95+
return;
96+
}
97+
98+
if unsafe { nginx_sys::ngx_ncpu > 1 } {
99+
for n in 0..NGX_RWLOCK_SPIN {
100+
for _ in 0..n {
101+
core::hint::spin_loop()
102+
}
103+
104+
if self.try_lock_exclusive() {
105+
return;
106+
}
107+
}
108+
}
109+
110+
ngx_sched_yield()
111+
}
112+
}
113+
114+
fn try_lock_exclusive(&self) -> bool {
115+
self.0
116+
.compare_exchange(0, NGX_RWLOCK_WLOCK, Ordering::Acquire, Ordering::Relaxed)
117+
.is_ok()
118+
}
119+
120+
unsafe fn unlock_exclusive(&self) {
121+
self.0.store(0, Ordering::Release)
122+
}
123+
}

0 commit comments

Comments
 (0)