1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//! Lock-free holder for the current rule hash (ADR-103 A7).
//!
//! The chain calls its `rule_hash` provider while holding the chain lock,
//! so the provider must not take locks. [`RuleHashCell`] is the intended
//! source: writers publish with [`RuleHashCell::set`], the provider reads
//! with [`RuleHashCell::get`], and a read never takes a lock (a seqlock
//! over four atomic words). A reader only retries while a write is in
//! flight, which is a few instructions; it spins briefly and then yields,
//! so it is lock-free in the sense that matters here (no mutex, no
//! priority inversion on the chain lock) but not wait-free.
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering, fence};
/// The current rule hash, readable without locking.
#[derive(Debug, Default)]
pub struct RuleHashCell {
/// Odd while a write is in flight.
seq: AtomicU64,
present: AtomicBool,
words: [AtomicU64; 4],
/// Serializes writers only; readers never touch it.
writers: Mutex<()>,
}
impl RuleHashCell {
/// An empty cell (`get` returns `None`).
pub fn new() -> Self {
Self::default()
}
/// Publish a new value (or clear it).
pub fn set(&self, hash: Option<[u8; 32]>) {
let _w = self.writers.lock().unwrap_or_else(|e| e.into_inner());
self.seq.fetch_add(1, Ordering::Relaxed); // odd: write in flight
fence(Ordering::Release); // order the odd marker before the data stores
let h = hash.unwrap_or([0; 32]);
for (i, w) in self.words.iter().enumerate() {
let mut b = [0u8; 8];
b.copy_from_slice(&h[i * 8..i * 8 + 8]);
w.store(u64::from_le_bytes(b), Ordering::Relaxed);
}
self.present.store(hash.is_some(), Ordering::Relaxed);
self.seq.fetch_add(1, Ordering::Release); // even: stable
}
/// The current value. Takes no lock; retries (spin, then yield) only
/// while a write is in flight.
pub fn get(&self) -> Option<[u8; 32]> {
let mut spins = 0u32;
loop {
let s1 = self.seq.load(Ordering::Acquire);
if s1 & 1 == 1 {
spins += 1;
if spins < 16 {
std::hint::spin_loop();
} else {
std::thread::yield_now();
}
continue;
}
let mut out = [0u8; 32];
for (i, w) in self.words.iter().enumerate() {
out[i * 8..i * 8 + 8].copy_from_slice(&w.load(Ordering::Relaxed).to_le_bytes());
}
let present = self.present.load(Ordering::Relaxed);
fence(Ordering::Acquire);
if self.seq.load(Ordering::Relaxed) == s1 {
return present.then_some(out);
}
}
}
/// A provider closure for `ChainManager::set_rule_hash_provider`.
pub fn provider(self: &std::sync::Arc<Self>) -> crate::chain::RuleHashProvider {
let cell = std::sync::Arc::clone(self);
std::sync::Arc::new(move || cell.get())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_get_clear() {
let c = RuleHashCell::new();
assert_eq!(c.get(), None);
c.set(Some([7; 32]));
assert_eq!(c.get(), Some([7; 32]));
c.set(None);
assert_eq!(c.get(), None);
}
#[test]
fn readers_never_see_a_torn_value() {
let c = std::sync::Arc::new(RuleHashCell::new());
let w = {
let c = c.clone();
std::thread::spawn(move || {
for i in 0..20_000u32 {
c.set(Some([(i % 251) as u8; 32]));
}
})
};
for _ in 0..20_000 {
if let Some(h) = c.get() {
assert!(h.iter().all(|b| *b == h[0]), "torn read {h:?}");
}
}
w.join().unwrap();
}
}