Skip to main content

harmos_storage/state/
admission.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12/* === Re-Exports === */
13
14/* === Imports === */
15
16use crate::storage::manager::RecordWriteBytes;
17// Standard Library Imports
18use std::sync::Arc;
19// Third Party Imports
20
21// Local Imports
22use super::{RecoveryEntry, RecoverySnapshot};
23use crate::storage::manager::{
24    ENTRY_OVERHEAD, Inner, Operation, Pending, Target, encoded_size, recovery_preparation_bytes, recovery_resident_bytes,
25};
26use crate::storage::{ReadLimits, RecordWrite, StorageCursor, StorageManager, StoredRecord};
27use crate::{Error, Result};
28use harmos_domain::state::Position;
29
30/* === Constants === */
31
32/* === Macros === */
33
34/* === Type Aliases === */
35
36/* === Structs === */
37
38pub struct Permit {
39    inner: Arc<Inner>,
40    bytes: usize,
41    target: Target,
42    first_record: StorageCursor,
43    records: Vec<StoredRecord>,
44    record_bytes: usize,
45    capture_bytes: usize,
46    payload: Vec<u8>,
47    active: bool,
48    snapshot: bool,
49}
50
51/* === Enums === */
52
53/* === Traits === */
54
55/* === Implementations === */
56
57impl<C> StorageManager<C> {
58    pub async fn reserve_transition(&self, payload_bytes: usize, records: Option<ReadLimits>) -> Result<Permit> {
59        self.reserve_recovery(payload_bytes, records, false).await
60    }
61
62    pub async fn reserve_snapshot(&self, payload_bytes: usize) -> Result<Permit> {
63        self.reserve_recovery(payload_bytes, None, true).await
64    }
65
66    async fn reserve_recovery(&self, payload_bytes: usize, records: Option<ReadLimits>, snapshot: bool) -> Result<Permit> {
67        let recovery_bytes = recovery_preparation_bytes(payload_bytes, self.inner.recovery.history(), self.inner.primary.history())?;
68        let bytes = recovery_bytes;
69        let batches = 1 + usize::from(records.is_some());
70        if batches > self.inner.settings.request.max_pending_write_batches.get() {
71            return Err(Error::InvalidInput { field: "managed capture", reason: "requires two queue slots" });
72        }
73        if bytes > self.inner.settings.request.max_pending_write_bytes.get() {
74            return Err(Error::InvalidInput { field: "managed recovery", reason: "group exceeds memory budget" });
75        }
76        let mut changed = self.inner.changed.subscribe();
77        loop {
78            {
79                let mut state = self.inner.state.lock().unwrap();
80                if !snapshot && recovery_bytes > state.journal_limit {
81                    return Err(Error::InvalidInput { field: "managed recovery", reason: "group exceeds recovery read bound" });
82                }
83                if state.status.closing {
84                    return Err(Error::Closing { domain: "storage" });
85                }
86                if !state.ready || !state.status.running {
87                    return Err(Error::NotReady { domain: "storage" });
88                }
89                if let Some(source) = &state.status.failure {
90                    return Err(Error::Persistence { source: source.clone() });
91                }
92                state.collect();
93                if state.reserved == 0
94                    && state.finishing.is_none()
95                    && state.status.pending_bytes <= self.inner.settings.request.max_pending_write_bytes.get() - bytes
96                    && state.status.pending_batches <= self.inner.settings.request.max_pending_write_batches.get() - batches
97                {
98                    state
99                        .next
100                        .checked_add(batches as u64)
101                        .ok_or(Error::InvalidInput { field: "managed capture", reason: "queue order overflow" })?;
102                    let mut first_record = state.status.accepted.clone();
103                    if let Some(limits) = records {
104                        first_record
105                            .sequence
106                            .checked_add(limits.rows.get() as u64)
107                            .ok_or(Error::InvalidInput { field: "managed capture", reason: "position overflow" })?;
108                        first_record.sequence += 1;
109                    }
110                    let target = Target {
111                        index: state
112                            .next
113                            .checked_add(1)
114                            .ok_or(Error::InvalidInput { field: "managed recovery", reason: "queue order overflow" })?,
115                        sequence: state
116                            .status
117                            .recovery_accepted
118                            .sequence
119                            .checked_add(u64::from(!snapshot))
120                            .ok_or(Error::InvalidInput { field: "managed recovery", reason: "position overflow" })?,
121                    };
122                    state.reserved = bytes;
123                    state.reserved_batches = batches;
124                    state.collect();
125                    return Ok(Permit {
126                        inner: self.inner.clone(),
127                        bytes: recovery_bytes,
128                        target,
129                        first_record,
130                        records: Vec::new(),
131                        record_bytes: 0,
132                        capture_bytes: 0,
133                        payload: Vec::new(),
134                        active: true,
135                        snapshot,
136                    });
137                }
138                self.inner.relieve_pressure(&mut state, bytes, batches);
139            }
140            changed.changed().await.map_err(|_| Error::NotReady { domain: "storage" })?;
141        }
142    }
143
144    pub async fn settle_transition(&self) {
145        let mut changed = self.inner.changed.subscribe();
146        loop {
147            if self.inner.state.lock().unwrap().reserved == 0 {
148                return;
149            }
150            if changed.changed().await.is_err() {
151                return;
152            }
153        }
154    }
155}
156
157impl Permit {
158    pub fn snapshot_payload(&mut self, payload: Vec<u8>) -> Result<()> {
159        debug_assert!(self.snapshot);
160        let bytes = recovery_resident_bytes(payload.capacity(), self.inner.recovery.history(), self.inner.primary.history())?;
161        if bytes > self.bytes {
162            return Err(Error::InvalidInput { field: "state snapshot", reason: "payload exceeds reservation" });
163        }
164        self.payload = payload;
165        self.bytes = bytes;
166        let mut state = self.inner.state.lock().unwrap();
167        state.reserved = bytes;
168        state.collect();
169        self.inner.notify();
170        Ok(())
171    }
172
173    pub fn projected(&self) -> &[StoredRecord] {
174        &self.records
175    }
176
177    pub async fn push(&mut self, record: RecordWrite<'_>, limits: ReadLimits) -> Result<()> {
178        let invalid = || Error::InvalidInput { field: "record projection", reason: "capture limit exceeded" };
179        if self.records.len() >= limits.rows.get() {
180            return Err(invalid());
181        }
182        let capture = encoded_size(&record)?
183            .checked_add(encoded_size(&self.first_record.history)?)
184            .and_then(|bytes| bytes.checked_add(ENTRY_OVERHEAD))
185            .ok_or_else(invalid)?;
186        let capture_total = self.capture_bytes.checked_add(capture).ok_or_else(invalid)?;
187        // An incremental Vec may retain spare slots or briefly own both old and
188        // replacement allocations. Four inline slots per live record cover
189        // initial minimum capacity and geometric growth before copying.
190        let bytes =
191            record.resident_bytes(&self.first_record.history)?.checked_add(std::mem::size_of::<StoredRecord>() * 3).ok_or_else(invalid)?;
192        let total = self.record_bytes.checked_add(bytes).ok_or_else(invalid)?;
193        // Refuse a group that cannot fit even after all accepted work drains.
194        // Waiting with a partial permit in that case would deadlock itself.
195        if capture_total > limits.bytes.get()
196            || self.bytes.checked_add(total).is_none_or(|total| total > self.inner.settings.request.max_pending_write_bytes.get())
197        {
198            return Err(invalid());
199        }
200        let mut position = self.first_record.clone();
201        position.sequence = position.sequence.checked_add(self.records.len() as u64).ok_or_else(invalid)?;
202        let capacity = self.inner.settings.request.max_pending_write_bytes.get();
203        self.inner
204            .admit_when(|state| {
205                super::super::capacity::admitting(state, super::super::capacity::Pool::Write)?;
206                state.collect();
207                if state.status.pending_bytes <= capacity - bytes {
208                    state.reserved += bytes;
209                    state.collect();
210                    return Ok(Some(()));
211                }
212                // File finalization also needs this exclusive permit to end.
213                // Only queued writes and saver scratch can drain underneath us;
214                // never wait on bytes held by our own permit or staged owners.
215                let held = state.status.pending_bytes.saturating_sub(state.queue.bytes()).saturating_sub(state.scratch_bytes);
216                if held > capacity - bytes {
217                    return Err(invalid());
218                }
219                // Flush accepted work only; this permit owns the uncommitted
220                // position and partial projection until the whole group settles.
221                self.inner.relieve_pressure(state, bytes, 0);
222                Ok(None)
223            })
224            .await?;
225        self.records.push(record.stored(position));
226        self.record_bytes = total;
227        self.capture_bytes = capture_total;
228        Ok(())
229    }
230
231    pub fn encode(&mut self, value: &impl serde::Serialize) -> Result<()> {
232        self.payload =
233            serde_json::to_vec(value).map_err(|_| Error::InvalidInput { field: "managed recovery", reason: "cannot encode group" })?;
234        // Encoding runs under the original conservative scratch reservation.
235        // Only the encoded buffer survives commit; release unused framing and
236        // growth allowance before the group becomes queued work.
237        let bytes = recovery_resident_bytes(self.payload.capacity(), self.inner.recovery.history(), self.inner.primary.history())?;
238        if bytes > self.bytes {
239            return Err(Error::InvalidInput { field: "managed recovery", reason: "encoded allocation exceeds reservation" });
240        }
241        self.bytes = bytes;
242        let mut state = self.inner.state.lock().unwrap();
243        state.reserved =
244            self.bytes.checked_add(self.record_bytes).ok_or(Error::InvalidInput { field: "managed recovery", reason: "size overflow" })?;
245        state.reserved_batches = 1 + usize::from(!self.records.is_empty());
246        state.collect();
247        self.inner.notify();
248        Ok(())
249    }
250
251    pub fn commit(mut self, frontier: Position) {
252        let mut state = self.inner.state.lock().unwrap();
253        let required_data = if self.snapshot {
254            state.journal_required_data.clone()
255        } else {
256            self.records.last().map_or_else(|| state.status.saved.clone(), |record| record.position.clone())
257        };
258        if required_data.sequence > state.journal_required_data.sequence {
259            state.journal_required_data = required_data.clone();
260        }
261        if let Some(last) = self.records.last() {
262            state.status.accepted = last.position.clone();
263            state.data_accepted = Target { index: self.target.index, sequence: last.position.sequence };
264            state.queue.push_back(Arc::new(Pending {
265                index: self.target.index,
266                bytes: self.record_bytes,
267                journal: None,
268                operation: Operation::Records(std::mem::take(&mut self.records)),
269            }));
270            self.target.index += 1; // Both slots were checked before mutation.
271        }
272        let position =
273            crate::storage::RecoveryPosition { history: state.status.recovery_accepted.history.clone(), sequence: self.target.sequence };
274        state.status.recovery_accepted = position.clone();
275        state.recovery_accepted = self.target;
276        state.next = self.target.index;
277        state.queue.push_back(Arc::new(Pending {
278            index: self.target.index,
279            bytes: self.bytes,
280            journal: (!self.snapshot).then_some(frontier),
281            operation: if self.snapshot {
282                Operation::Snapshot(RecoverySnapshot {
283                    required_data: required_data.clone(),
284                    through: position,
285                    payload: std::mem::take(&mut self.payload),
286                })
287            } else {
288                Operation::Recovery(vec![RecoveryEntry { required_data, position, payload: std::mem::take(&mut self.payload) }])
289            },
290        }));
291        state.reserved = 0;
292        state.reserved_batches = 0;
293        self.active = false;
294        state.collect();
295        self.inner.notify();
296    }
297}
298
299impl Drop for Permit {
300    fn drop(&mut self) {
301        if self.active {
302            let mut state = self.inner.state.lock().unwrap();
303            state.reserved = 0;
304            state.reserved_batches = 0;
305            state.collect();
306            self.inner.notify();
307        }
308    }
309}
310
311/* === Public Functions === */
312
313/* === Private Functions === */
314
315/* === Tests === */