1use crate::active::LeaseReservation;
2use crate::session::SessionLimits;
3use core::cell::Cell;
4use core::marker::PhantomData;
5use core::sync::atomic::{AtomicU8, AtomicU32, AtomicU64, Ordering};
6use std::sync::Arc;
7
8const ACTIVE: u8 = 0;
9const POISONED: u8 = 1;
10const CLOSED: u8 = 2;
11
12pub(crate) struct ResourceOwner {
13 shared: Arc<SharedResources>,
14 not_sync: PhantomData<Cell<()>>,
15}
16
17struct SharedResources {
18 state: AtomicU8,
19 active_regions: AtomicU32,
20 active_bytes: AtomicU64,
21 maximum_regions: u32,
22 maximum_bytes: u64,
23 #[cfg(test)]
24 lease_drop_observer: std::sync::Mutex<Option<Arc<std::sync::Mutex<Vec<&'static str>>>>>,
25}
26
27pub(crate) struct RegionLease {
28 shared: Arc<SharedResources>,
29 bytes: u64,
30 #[cfg(test)]
31 drop_observer: Option<Arc<std::sync::Mutex<Vec<&'static str>>>>,
32}
33
34#[derive(Clone, Copy, Debug, Eq, PartialEq)]
35pub(crate) enum LivenessState {
36 Active,
37 Poisoned,
38 Closed,
39}
40
41#[derive(Clone, Copy, Debug, Eq, PartialEq)]
42pub struct ActiveLeaseFacts {
44 pub(crate) regions: u32,
45 pub(crate) bytes: u64,
46 pub(crate) consistency: LeaseFactsConsistency,
47}
48
49#[derive(Clone, Copy, Debug, Eq, PartialEq)]
50pub enum LeaseFactsConsistency {
52 Exact,
54 ApproximateDuringConcurrentDrop,
56}
57
58impl ActiveLeaseFacts {
59 pub const fn regions(self) -> u32 {
61 self.regions
62 }
63
64 pub const fn bytes(self) -> u64 {
66 self.bytes
67 }
68
69 pub const fn consistency(self) -> LeaseFactsConsistency {
71 self.consistency
72 }
73
74 pub const fn is_empty(self) -> bool {
76 self.regions == 0 && self.bytes == 0
77 }
78}
79
80#[derive(Clone, Copy, Debug, Eq, PartialEq)]
81pub(crate) enum ResourceError {
82 InvalidLimits,
83 Poisoned,
84 Closed,
85 ActiveLimit,
86 MappedLengthMismatch { reserved: u64, actual: u64 },
87 ActiveLeases(ActiveLeaseFacts),
88}
89
90impl ResourceOwner {
91 pub(crate) fn new(limits: SessionLimits) -> Result<Self, ResourceError> {
92 let limits = limits
93 .validate()
94 .map_err(|_| ResourceError::InvalidLimits)?;
95 Ok(Self {
96 shared: Arc::new(SharedResources {
97 state: AtomicU8::new(ACTIVE),
98 active_regions: AtomicU32::new(0),
99 active_bytes: AtomicU64::new(0),
100 maximum_regions: limits.max_active_regions,
101 maximum_bytes: limits.max_active_bytes,
102 #[cfg(test)]
103 lease_drop_observer: std::sync::Mutex::new(None),
104 }),
105 not_sync: PhantomData,
106 })
107 }
108
109 pub(crate) fn reserve(&mut self, bytes: u64) -> Result<LeaseReservation, ResourceError> {
110 if bytes == 0 || bytes > self.shared.maximum_bytes {
111 return Err(ResourceError::ActiveLimit);
112 }
113 self.ensure_active()?;
114 self.shared
115 .active_regions
116 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |regions| {
117 (regions < self.shared.maximum_regions).then_some(regions + 1)
118 })
119 .map_err(|_| ResourceError::ActiveLimit)?;
120 if self
121 .shared
122 .active_bytes
123 .fetch_update(Ordering::AcqRel, Ordering::Acquire, |current| {
124 current
125 .checked_add(bytes)
126 .filter(|next| *next <= self.shared.maximum_bytes)
127 })
128 .is_err()
129 {
130 self.shared.active_regions.fetch_sub(1, Ordering::AcqRel);
131 return Err(ResourceError::ActiveLimit);
132 }
133 if let Err(error) = self.ensure_active() {
134 self.shared.active_bytes.fetch_sub(bytes, Ordering::AcqRel);
135 self.shared.active_regions.fetch_sub(1, Ordering::AcqRel);
136 return Err(error);
137 }
138 #[cfg(test)]
139 let drop_observer = self
140 .shared
141 .lease_drop_observer
142 .lock()
143 .expect("test lease observer mutex is not poisoned")
144 .clone();
145 Ok(LeaseReservation::new(RegionLease {
146 shared: Arc::clone(&self.shared),
147 bytes,
148 #[cfg(test)]
149 drop_observer,
150 }))
151 }
152
153 pub(crate) fn poison(&mut self) {
154 let _ = self.shared.state.compare_exchange(
155 ACTIVE,
156 POISONED,
157 Ordering::AcqRel,
158 Ordering::Acquire,
159 );
160 }
161
162 pub(crate) fn try_close(&mut self) -> Result<(), ResourceError> {
163 let facts = self.active_lease_facts();
164 if facts.regions != 0 || facts.bytes != 0 {
165 return Err(ResourceError::ActiveLeases(facts));
166 }
167 loop {
168 let current = self.shared.state.load(Ordering::Acquire);
169 if current == CLOSED {
170 return Err(ResourceError::Closed);
171 }
172 if self
173 .shared
174 .state
175 .compare_exchange(current, CLOSED, Ordering::AcqRel, Ordering::Acquire)
176 .is_ok()
177 {
178 return Ok(());
179 }
180 }
181 }
182
183 pub(crate) fn state(&self) -> LivenessState {
184 decode_state(self.shared.state.load(Ordering::Acquire))
185 }
186
187 pub(crate) fn active_lease_facts(&self) -> ActiveLeaseFacts {
188 let regions = self.shared.active_regions.load(Ordering::Acquire);
189 let bytes = self.shared.active_bytes.load(Ordering::Acquire);
190 ActiveLeaseFacts {
191 regions,
192 bytes,
193 consistency: if regions == 0 && bytes == 0 {
194 LeaseFactsConsistency::Exact
195 } else {
196 LeaseFactsConsistency::ApproximateDuringConcurrentDrop
197 },
198 }
199 }
200
201 #[cfg(test)]
202 pub(crate) fn observe_lease_drop_for_test(
203 &mut self,
204 observer: Arc<std::sync::Mutex<Vec<&'static str>>>,
205 ) {
206 *self
207 .shared
208 .lease_drop_observer
209 .lock()
210 .expect("test lease observer mutex is not poisoned") = Some(observer);
211 }
212
213 fn ensure_active(&self) -> Result<(), ResourceError> {
214 match self.state() {
215 LivenessState::Active => Ok(()),
216 LivenessState::Poisoned => Err(ResourceError::Poisoned),
217 LivenessState::Closed => Err(ResourceError::Closed),
218 }
219 }
220}
221
222impl Drop for ResourceOwner {
223 fn drop(&mut self) {
224 self.poison();
225 }
226}
227
228impl RegionLease {
229 pub(crate) const fn bytes(&self) -> u64 {
230 self.bytes
231 }
232
233 pub(crate) fn state(&self) -> LivenessState {
234 decode_state(self.shared.state.load(Ordering::Acquire))
235 }
236}
237
238impl Drop for RegionLease {
239 fn drop(&mut self) {
240 let previous_bytes = self
241 .shared
242 .active_bytes
243 .fetch_sub(self.bytes, Ordering::AcqRel);
244 debug_assert!(previous_bytes >= self.bytes);
245 let previous_regions = self.shared.active_regions.fetch_sub(1, Ordering::AcqRel);
246 debug_assert!(previous_regions >= 1);
247 #[cfg(test)]
248 if let Some(observer) = &self.drop_observer {
249 observer
250 .lock()
251 .expect("test lease observer mutex is not poisoned")
252 .push("active-lease-drop");
253 }
254 }
255}
256
257fn decode_state(state: u8) -> LivenessState {
258 match state {
259 ACTIVE => LivenessState::Active,
260 POISONED => LivenessState::Poisoned,
261 CLOSED => LivenessState::Closed,
262 _ => unreachable!("private liveness state is canonical"),
263 }
264}
265
266#[cfg(test)]
267#[path = "liveness_test.rs"]
268mod tests;