ngx/
sync.rs

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.
25use core::sync::atomic::{self, Ordering};
26
27use nginx_sys::ngx_sched_yield;
28
29const NGX_RWLOCK_SPIN: usize = 2048;
30const NGX_RWLOCK_WLOCK: usize = usize::MAX;
31
32type NgxAtomic = atomic::AtomicUsize;
33
34/// Raw lock type.
35///
36pub struct RawSpinlock(NgxAtomic);
37
38/// Reader-writer lock over an atomic variable, based on the nginx rwlock implementation.
39pub type RwLock<T> = lock_api::RwLock<RawSpinlock, T>;
40
41/// RAII structure used to release the shared read access of a lock when dropped.
42pub 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.
45pub type RwLockWriteGuard<'a, T> = lock_api::RwLockWriteGuard<'a, RawSpinlock, T>;
46
47unsafe 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}