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::{Inner, Operation, Pending, Target, recovery_encoded_bound};
24use crate::storage::{ReadLimits, RecordInput, StorageCursor, StorageManager, StoredRecord};
25use crate::{Error, Result};
26use harmos_domain::state::Position;
27
28/* === Constants === */
29
30/* === Macros === */
31
32/* === Type Aliases === */
33
34/* === Structs === */
35
36pub struct Permit {
37    context: crate::execution::Context,
38    inner: Arc<Inner>,
39    bytes: usize,
40    target: Target,
41    first_record: StorageCursor,
42    records: Vec<StoredRecord>,
43    record_bytes: usize,
44    capture_bytes: usize,
45    payload: Vec<u8>,
46    active: bool,
47    snapshot: bool,
48}
49
50/* === Enums === */
51
52/* === Traits === */
53
54/* === Implementations === */
55
56impl<C> StorageManager<C> {
57    #[tracing::instrument(name = "storage.state.admission.reserve_transition", level = "debug", skip_all)]
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    #[tracing::instrument(name = "storage.state.admission.reserve_snapshot", level = "debug", skip_all)]
63    pub async fn reserve_snapshot(&self, payload_bytes: usize) -> Result<Permit> {
64        self.reserve_recovery(payload_bytes, None, true).await
65    }
66
67    #[tracing::instrument(name = "storage.state.admission.reserve_recovery", level = "debug", skip_all)]
68    async fn reserve_recovery(&self, payload_bytes: usize, records: Option<ReadLimits>, snapshot: bool) -> Result<Permit> {
69        let recovery_bytes = recovery_encoded_bound(payload_bytes, self.inner.recovery.history(), self.inner.primary.history())?;
70        let bytes = recovery_bytes;
71        let batches = 1 + usize::from(records.is_some());
72        if batches > self.inner.settings.request.max_pending_write_batches.get() {
73            return Err(Error::InvalidInput {
74                field: "managed capture",
75                reason: "requires two queue slots",
76            });
77        }
78        if bytes > self.inner.settings.request.max_pending_write_bytes.get() {
79            return Err(Error::InvalidInput {
80                field: "managed recovery",
81                reason: "group exceeds queued byte limit",
82            });
83        }
84        let mut changed = self.inner.changed.subscribe();
85        loop {
86            {
87                let mut state = self.inner.state.lock().unwrap();
88                if !snapshot && recovery_bytes > state.journal_limit {
89                    return Err(Error::InvalidInput {
90                        field: "managed recovery",
91                        reason: "group exceeds recovery read bound",
92                    });
93                }
94                if state.status.closing {
95                    return Err(Error::Closing { domain: "storage" });
96                }
97                if !state.ready || !state.status.running {
98                    return Err(Error::NotReady { domain: "storage" });
99                }
100                if let Some(source) = &state.status.failure {
101                    return Err(Error::Persistence { source: source.clone() });
102                }
103                state.collect();
104                if state.reserved == 0
105                    && state.finishing.is_none()
106                    && state.status.pending_bytes <= self.inner.settings.request.max_pending_write_bytes.get() - bytes
107                    && state.status.pending_batches <= self.inner.settings.request.max_pending_write_batches.get() - batches
108                {
109                    state.next.checked_add(batches as u64).ok_or(Error::InvalidInput {
110                        field: "managed capture",
111                        reason: "queue order overflow",
112                    })?;
113                    let mut first_record = state.status.accepted.clone();
114                    if let Some(limits) = records {
115                        first_record.sequence.checked_add(limits.rows.get() as u64).ok_or(Error::InvalidInput {
116                            field: "managed capture",
117                            reason: "position overflow",
118                        })?;
119                        first_record.sequence += 1;
120                    }
121                    let target = Target {
122                        index: state.next.checked_add(1).ok_or(Error::InvalidInput {
123                            field: "managed recovery",
124                            reason: "queue order overflow",
125                        })?,
126                        sequence: state.status.recovery_accepted.sequence.checked_add(u64::from(!snapshot)).ok_or(Error::InvalidInput {
127                            field: "managed recovery",
128                            reason: "position overflow",
129                        })?,
130                    };
131                    state.reserved = bytes;
132                    state.reserved_batches = batches;
133                    state.collect();
134                    return Ok(Permit {
135                        context: crate::execution::Context::current(),
136                        inner: self.inner.clone(),
137                        bytes: recovery_bytes,
138                        target,
139                        first_record,
140                        records: Vec::new(),
141                        record_bytes: 0,
142                        capture_bytes: 0,
143                        payload: Vec::new(),
144                        active: true,
145                        snapshot,
146                    });
147                }
148                self.inner.relieve_pressure(&mut state, bytes, batches);
149            }
150            changed.changed().await.map_err(|_| Error::NotReady { domain: "storage" })?;
151        }
152    }
153
154    #[tracing::instrument(name = "storage.state.admission.settle_transition", level = "debug", skip_all)]
155    pub async fn settle_transition(&self) {
156        let mut changed = self.inner.changed.subscribe();
157        loop {
158            if self.inner.state.lock().unwrap().reserved == 0 {
159                return;
160            }
161            if changed.changed().await.is_err() {
162                return;
163            }
164        }
165    }
166}
167
168impl Permit {
169    pub fn snapshot_payload(&mut self, payload: Vec<u8>) -> Result<()> {
170        let context = self.context.clone();
171        context.sync(|| {
172            let _stage = tracing::debug_span!("storage.state.snapshot_payload").entered();
173            debug_assert!(self.snapshot);
174            let bytes = recovery_encoded_bound(payload.len(), self.inner.recovery.history(), self.inner.primary.history())?;
175            if bytes > self.bytes {
176                return Err(Error::InvalidInput {
177                    field: "state snapshot",
178                    reason: "payload exceeds reservation",
179                });
180            }
181            self.payload = payload;
182            self.bytes = bytes;
183            let mut state = self.inner.state.lock().unwrap();
184            state.reserved = bytes;
185            state.collect();
186            self.inner.notify();
187            Ok(())
188        })
189    }
190
191    pub fn projected(&self) -> &[StoredRecord] {
192        &self.records
193    }
194
195    #[tracing::instrument(name = "storage.state.admission.push", level = "debug", skip_all)]
196    pub async fn push(&mut self, record: RecordInput, limits: ReadLimits) -> Result<()> {
197        let context = self.context.clone();
198        let span = context.sync(|| tracing::debug_span!("storage.state.project"));
199        let future = async {
200            let invalid = || Error::InvalidInput {
201                field: "record projection",
202                reason: "capture limit exceeded",
203            };
204            if self.records.len() >= limits.rows.get() {
205                return Err(invalid());
206            }
207            let bytes = record.encoded_bytes(&self.first_record.history)?;
208            let capture_total = self.capture_bytes.checked_add(bytes).ok_or_else(invalid)?;
209            let total = self.record_bytes.checked_add(bytes).ok_or_else(invalid)?;
210            // Refuse a group that cannot fit even after all accepted work drains.
211            // Waiting with a partial permit in that case would deadlock itself.
212            if capture_total > limits.bytes.get()
213                || self.bytes.checked_add(total).is_none_or(|total| total > self.inner.settings.request.max_pending_write_bytes.get())
214            {
215                return Err(invalid());
216            }
217            let mut position = self.first_record.clone();
218            position.sequence = position.sequence.checked_add(self.records.len() as u64).ok_or_else(invalid)?;
219            let capacity = self.inner.settings.request.max_pending_write_bytes.get();
220            self.inner
221                .admit_when(|state| {
222                    super::super::capacity::admitting(state, true)?;
223                    state.collect();
224                    if state.status.pending_bytes <= capacity - bytes {
225                        state.reserved += bytes;
226                        state.collect();
227                        return Ok(Some(()));
228                    }
229                    // File finalization also needs this exclusive permit to end.
230                    // Only queued writes and saver scratch can drain underneath us;
231                    // never wait on bytes held by our own permit or staged owners.
232                    let held = state.status.pending_bytes.saturating_sub(state.queue.bytes());
233                    if held > capacity - bytes {
234                        return Err(invalid());
235                    }
236                    // Flush accepted work only; this permit owns the uncommitted
237                    // position and partial projection until the whole group settles.
238                    self.inner.relieve_pressure(state, bytes, 0);
239                    Ok(None)
240                })
241                .await?;
242            self.records.push(record.stored(position));
243            self.record_bytes = total;
244            self.capture_bytes = capture_total;
245            Ok(())
246        };
247        context.scope(tracing::Instrument::instrument(future, span)).await
248    }
249
250    pub fn encode(&mut self, value: &impl serde::Serialize) -> Result<()> {
251        let context = self.context.clone();
252        context.sync(|| {
253            let _stage = tracing::debug_span!("storage.state.encode").entered();
254            self.payload = serde_json::to_vec(value).map_err(|_| Error::InvalidInput {
255                field: "managed recovery",
256                reason: "cannot encode group",
257            })?;
258            // Encoding runs under the original conservative scratch reservation.
259            // Only the encoded buffer survives commit; release unused framing and
260            // growth allowance before the group becomes queued work.
261            let bytes = recovery_encoded_bound(self.payload.len(), self.inner.recovery.history(), self.inner.primary.history())?;
262            if bytes > self.bytes {
263                return Err(Error::InvalidInput {
264                    field: "managed recovery",
265                    reason: "encoded group exceeds queued byte limit",
266                });
267            }
268            self.bytes = bytes;
269            let mut state = self.inner.state.lock().unwrap();
270            state.reserved = self.bytes.checked_add(self.record_bytes).ok_or(Error::InvalidInput {
271                field: "managed recovery",
272                reason: "size overflow",
273            })?;
274            state.reserved_batches = 1 + usize::from(!self.records.is_empty());
275            state.collect();
276            self.inner.notify();
277            Ok(())
278        })
279    }
280
281    pub fn commit(mut self, frontier: Position) {
282        let context = self.context.clone();
283        context.sync(|| {
284            let _stage = tracing::debug_span!("storage.state.commit").entered();
285            let mut state = self.inner.state.lock().unwrap();
286            let required_data = if self.snapshot {
287                state.journal_required_data.clone()
288            } else {
289                self.records.last().map_or_else(|| state.status.saved.clone(), |record| record.position.clone())
290            };
291            if required_data.sequence > state.journal_required_data.sequence {
292                state.journal_required_data = required_data.clone();
293            }
294            if let Some(last) = self.records.last() {
295                state.status.accepted = last.position.clone();
296                state.data_accepted = Target {
297                    index: self.target.index,
298                    sequence: last.position.sequence,
299                };
300                state.queue.push_back(Arc::new(Pending::new(
301                    &self.inner,
302                    None,
303                    self.target.index,
304                    self.record_bytes,
305                    Operation::Records(crate::StorageBatch::new(std::mem::take(&mut self.records))),
306                )));
307                self.target.index += 1; // Both slots were checked before mutation.
308            }
309            let position = crate::storage::RecoveryPosition {
310                history: state.status.recovery_accepted.history.clone(),
311                sequence: self.target.sequence,
312            };
313            state.status.recovery_accepted = position.clone();
314            state.recovery_accepted = self.target;
315            state.next = self.target.index;
316            state.queue.push_back(Arc::new(Pending::new(
317                &self.inner,
318                (!self.snapshot).then_some(frontier),
319                self.target.index,
320                self.bytes,
321                if self.snapshot {
322                    Operation::Snapshot(crate::StorageBatch::new(vec![RecoverySnapshot {
323                        required_data: required_data.clone(),
324                        through: position,
325                        payload: std::mem::take(&mut self.payload),
326                    }]))
327                } else {
328                    Operation::Recovery(crate::StorageBatch::new(vec![RecoveryEntry {
329                        required_data,
330                        position,
331                        payload: std::mem::take(&mut self.payload),
332                    }]))
333                },
334            )));
335            state.reserved = 0;
336            state.reserved_batches = 0;
337            self.active = false;
338            state.collect();
339            self.inner.notify();
340        })
341    }
342}
343
344impl Drop for Permit {
345    fn drop(&mut self) {
346        if self.active {
347            let mut state = self.inner.state.lock().unwrap();
348            state.reserved = 0;
349            state.reserved_batches = 0;
350            state.collect();
351            self.inner.notify();
352        }
353    }
354}
355
356/* === Public Functions === */
357
358/* === Private Functions === */
359
360/* === Tests === */