Skip to main content

native_ipc/
liveness.rs

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)]
42/// Bounded counters for active mappings that still retain a session lease.
43pub 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)]
50/// Consistency of independently observed active-region and active-byte counters.
51pub enum LeaseFactsConsistency {
52    /// Both counters were observed at zero and cannot increase without the session owner.
53    Exact,
54    /// A concurrent active-mapping drop may make either nonzero counter stale-high.
55    ApproximateDuringConcurrentDrop,
56}
57
58impl ActiveLeaseFacts {
59    /// Number of currently charged active mappings observed.
60    pub const fn regions(self) -> u32 {
61        self.regions
62    }
63
64    /// Page-rounded mapping bytes currently charged to the session.
65    pub const fn bytes(self) -> u64 {
66        self.bytes
67    }
68
69    /// Whether the two independently observed atomic counters are exact.
70    pub const fn consistency(self) -> LeaseFactsConsistency {
71        self.consistency
72    }
73
74    /// Whether both observed counters are zero.
75    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;