tuwunel_core/utils/two_phase_counter.rs
1//! Two-Phase Counter.
2
3use std::{
4 collections::VecDeque,
5 ops::{Deref, Range},
6 sync::{Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard},
7};
8
9use crate::{Result, checked, error, is_equal_to};
10
11/// Two-Phase Counter.
12///
13/// This device solves the problem of a One-Phase Counter (or just a counter)
14/// which is incremented to provide unique sequence numbers (or index numbers)
15/// fundamental to server operation. For example, let's say a new Matrix Pdu
16/// is received: the counter is incremented and its value becomes the PduId
17/// used as a key for the Pdu value when writing to the database.
18///
19/// Problem: With a single counter shared by both writers and readers, pending
20/// writes might still be in-flight and not visible to readers after the writer
21/// incremented it. For example, client-sync sees the counter at a certain
22/// value, but that value has no Pdu found because its write has not been
23/// completed with global visibility. Client-sync will then move on to the next
24/// counter value having missed the data from the current one.
25pub struct Counter<F: Fn(u64) -> Result + Send + Sync> {
26 /// Self is intended to be `Arc<Counter>` with inner state mutable via Lock.
27 inner: RwLock<State<F>>,
28}
29
30/// Inner protected state for Two-Phase Counter.
31pub struct State<F: Fn(u64) -> Result + Send + Sync> {
32 /// Monotonic counter. The next sequence number is drawn by adding one to
33 /// this value. That number will be persisted and added to `pending`.
34 dispatched: u64,
35
36 /// Callback to persist the next sequence number drawn from `dispatched`.
37 /// This prevents pending numbers from being reused after server restart.
38 commit: F,
39
40 /// List of pending sequence numbers. One less than the minimum value in
41 /// this list is the "retirement" sequence number where all writes have
42 /// completed and all reads are globally visible.
43 pending: VecDeque<u64>,
44
45 /// Callback to notify updates of the retirement value. This is likely
46 /// called from the destructor of a permit/guard; try not to panic.
47 release: F,
48}
49
50#[clippy::has_significant_drop]
51/// Holds a dispatched sequence number until its write operation retires.
52///
53/// The permit dereferences to its unique sequence number and records the
54/// retirement frontier sampled at dispatch. Dropping it retires the sequence
55/// through the shared counter so the retirement frontier advances in order.
56pub struct Permit<F: Fn(u64) -> Result + Send + Sync> {
57 /// Link back to the shared-state.
58 state: Arc<Counter<F>>,
59
60 /// The retirement value computed as a courtesy when this permit was
61 /// created.
62 retired: u64,
63
64 /// Sequence number of this permit.
65 id: u64,
66}
67
68impl<F: Fn(u64) -> Result + Send + Sync> Counter<F> {
69 /// Construct a new Two-Phase counter state. The value of `init` is
70 /// considered retired, and the next sequence number dispatched will be one
71 /// greater.
72 pub fn new(init: u64, commit: F, release: F) -> Arc<Self> {
73 Arc::new(Self {
74 inner: State::new(init, commit, release).into(),
75 })
76 }
77
78 /// Obtain a sequence number to conduct write operations for the scope.
79 pub fn next(self: &Arc<Self>) -> Result<Permit<F>> {
80 let (retired, id) = self.write().dispatch()?;
81
82 Ok(Permit::<F> { state: self.clone(), retired, id })
83 }
84
85 /// Load the current and dispatched values simultaneously
86 #[inline]
87 pub fn range(&self) -> Range<u64> {
88 let inner = self.read();
89
90 Range {
91 start: inner.retired(),
92 end: inner.dispatched,
93 }
94 }
95
96 /// Load the highest sequence number safe for reading, also known as the
97 /// retirement value with writes "globally visible."
98 #[inline]
99 pub fn current(&self) -> u64 { self.read().retired() }
100
101 /// Load the highest sequence number (dispatched); may still be pending or
102 /// may be retired.
103 #[inline]
104 pub fn dispatched(&self) -> u64 { self.read().dispatched }
105
106 /// Borrow the state for reading, tolerating a poisoned lock.
107 ///
108 /// Poisoning carries no information here: `commit` runs before any mutation
109 /// and `release` after all of them, so an unwind through either callback
110 /// leaves the state consistent. Honoring the flag instead turns a single
111 /// failed write into a permanent outage of every sequence number.
112 #[inline]
113 fn read(&self) -> RwLockReadGuard<'_, State<F>> {
114 self.inner
115 .read()
116 .unwrap_or_else(PoisonError::into_inner)
117 }
118
119 /// Borrow the state for writing, tolerating a poisoned lock.
120 ///
121 /// The reasoning is the same as [`Self::read`].
122 #[inline]
123 fn write(&self) -> RwLockWriteGuard<'_, State<F>> {
124 self.inner
125 .write()
126 .unwrap_or_else(PoisonError::into_inner)
127 }
128}
129
130impl<F: Fn(u64) -> Result + Send + Sync> State<F> {
131 /// Create new state, starting from `init`. The next sequence number
132 /// dispatched will be one greater than `init`.
133 fn new(dispatched: u64, commit: F, release: F) -> Self {
134 Self {
135 dispatched,
136 commit,
137 pending: VecDeque::new(),
138 release,
139 }
140 }
141
142 /// Dispatch the next sequence number as pending. The retired value is
143 /// calculated as a courtesy while the state is under lock.
144 fn dispatch(&mut self) -> Result<(u64, u64)> {
145 let prev = self.dispatched;
146 let retired = self.retired();
147 let dispatched = checked!(prev + 1)?;
148 debug_assert!(
149 !self.check_pending(dispatched),
150 "sequence number cannot already be pending",
151 );
152
153 (self.commit)(dispatched)?;
154 self.pending.push_back(dispatched);
155 self.dispatched = dispatched;
156
157 Ok((retired, dispatched))
158 }
159
160 /// Retire the sequence number `id`.
161 ///
162 /// This runs from a destructor, so outside debug assertions a
163 /// desynchronized pending list or a failing release callback is logged
164 /// rather than raised as a panic.
165 fn retire(&mut self, id: u64) {
166 debug_assert!(self.check_pending(id), "sequence number must be currently pending");
167
168 let Some(index) = self.pending_index(id) else {
169 error!(id, "Sequence number was not pending for retirement.");
170 return;
171 };
172
173 let removed = self.pending.remove(index);
174
175 debug_assert_eq!(removed, Some(id), "sequence number removed must match id");
176
177 // release only occurs when the oldest value retires
178 if index != 0 {
179 return;
180 }
181
182 // release occurs for the maximum retired value
183 let release = if self.pending.is_empty() { self.dispatched } else { id };
184
185 debug_assert!(release >= id, "sequence number released must not be less than id");
186
187 (self.release)(release)
188 .inspect_err(|error| error!(release, %error, "Failed to release sequence number."))
189 .ok();
190 }
191
192 /// Calculate the retired sequence number, one less than the lowest pending
193 /// sequence number. If nothing is pending the value of `dispatched` has
194 /// been previously retired and is returned.
195 fn retired(&self) -> u64 {
196 debug_assert!(
197 self.pending.iter().is_sorted(),
198 "Pending values should be naturally sorted"
199 );
200
201 self.pending
202 .front()
203 .map(|val| val.saturating_sub(1))
204 .unwrap_or(self.dispatched)
205 }
206
207 /// Get the position of `id` in the pending list.
208 fn pending_index(&self, id: u64) -> Option<usize> {
209 debug_assert!(
210 self.pending.iter().is_sorted(),
211 "Pending values should be naturally sorted"
212 );
213
214 self.pending.binary_search(&id).ok()
215 }
216
217 /// Check for `id` in the pending list sequentially (for debug and assertion
218 /// purposes only)
219 fn check_pending(&self, id: u64) -> bool { self.pending.iter().any(is_equal_to!(&id)) }
220}
221
222impl<F: Fn(u64) -> Result + Send + Sync> Permit<F> {
223 /// Access the retired sequence number sampled at this permit's creation.
224 /// This may be outdated prior to access. Obtained as a courtesy under lock.
225 #[inline]
226 #[must_use]
227 pub fn retired(&self) -> &u64 { &self.retired }
228
229 /// Access the sequence number obtained by this permit; a unique value
230 #[inline]
231 #[must_use]
232 pub fn id(&self) -> &u64 { &self.id }
233}
234
235impl<F: Fn(u64) -> Result + Send + Sync> Deref for Permit<F> {
236 type Target = u64;
237
238 #[inline]
239 fn deref(&self) -> &Self::Target { self.id() }
240}
241
242impl<F: Fn(u64) -> Result + Send + Sync> Drop for Permit<F> {
243 fn drop(&mut self) { self.state.write().retire(self.id); }
244}