harmos_storage/state/
reader.rs1use std::num::NonZeroUsize;
18
19use tokio::sync::{RwLockReadGuard, watch};
21
22use super::{RecoveryPage, RecoverySnapshot};
24use crate::storage::capacity::{Pool, Reservation};
25use crate::storage::manager::{ENTRY_OVERHEAD, Lifecycle, encoded_size, recovery_preparation_bytes};
26use crate::storage::{ReadLimits, RecoveryPosition, StorageManager};
27use crate::{Error, Result};
28use harmos_domain::state::Position;
29
30pub struct RecoveryReader<'a, C> {
39 storage: &'a StorageManager<C>,
40 _reservation: Reservation,
42 _lifecycle: RwLockReadGuard<'a, Lifecycle>,
43 through: RecoveryPosition,
44 limits: ReadLimits,
45 snapshot_bound: Option<NonZeroUsize>,
46}
47
48impl<C> StorageManager<C> {
55 pub async fn recovery_reader(&self, limits: ReadLimits, snapshot_bytes: Option<NonZeroUsize>) -> Result<RecoveryReader<'_, C>> {
56 let lifecycle = self.inner.lifecycle.read().await;
57 let invalid = |reason| Error::InvalidInput { field: "state recovery", reason };
58 let snapshot_bound = snapshot_bytes
59 .map(|bytes| {
60 recovery_preparation_bytes(bytes.get(), self.inner.recovery.history(), self.inner.primary.history())
61 .map(|charged| NonZeroUsize::new(charged).expect("positive snapshot budget"))
62 })
63 .transpose()?;
64 let mut bytes = limits
65 .bytes
66 .get()
67 .checked_mul(2)
68 .and_then(|n| n.checked_add(limits.rows.get().checked_mul(ENTRY_OVERHEAD)?))
69 .ok_or_else(|| invalid("recovery read size overflow"))?;
70 if let Some(bound) = snapshot_bound {
71 bytes = bytes.max(bound.get().checked_mul(2).ok_or_else(|| invalid("snapshot read size overflow"))?);
72 }
73 let through = {
74 let mut current = self.inner.state.lock().unwrap();
75 if current.status.running || current.status.closing || current.journal_saved.is_some() {
76 return Err(invalid("managed recovery requires a dormant acquired manager"));
77 }
78 if bytes > self.inner.settings.request.max_pending_write_bytes.get().saturating_sub(current.status.pending_bytes) {
79 return Err(invalid("recovery page exceeds shared memory budget"));
80 }
81 current.scratch_bytes += bytes;
82 current.collect();
83 current.status.recovery_saved.clone()
84 };
85 Ok(RecoveryReader {
86 storage: self,
87 _lifecycle: lifecycle,
88 _reservation: Reservation::charged(&self.inner, Pool::Write, bytes),
89 through,
90 limits,
91 snapshot_bound,
92 })
93 }
94}
95
96impl<C> RecoveryReader<'_, C> {
97 pub fn through(&self) -> &RecoveryPosition {
98 &self.through
99 }
100
101 pub async fn snapshot(&self) -> Result<Option<RecoverySnapshot>> {
102 let Some(bound) = self.snapshot_bound else {
103 return Ok(None);
104 };
105 let snapshot = self.storage.inner.bounded(self.storage.inner.recovery.read_snapshot(bound)).await?;
106 if let Some(snapshot) = &snapshot
107 && (!self.through.covers(&snapshot.through)? || encoded_size(snapshot)? > bound.get())
108 {
109 return Err(Error::InvalidInput { field: "state snapshot", reason: "invalid managed snapshot boundary or size" });
110 }
111 if let Some(snapshot) = &snapshot {
112 self.storage.check_state_dependency(&snapshot.required_data)?;
113 }
114 Ok(snapshot)
115 }
116
117 pub async fn page(&self, after: &RecoveryPosition) -> Result<RecoveryPage> {
118 let page = self.storage.inner.bounded(self.storage.inner.recovery.read_entries(after, &self.through, self.limits)).await?;
119 page.check(after, &self.through, self.limits, |entry| encoded_size(entry).unwrap_or(usize::MAX))?;
120 for entry in &page.entries {
121 self.storage.check_state_dependency(&entry.required_data)?;
122 }
123 Ok(page)
124 }
125
126 pub fn finish(self, position: Position) -> watch::Receiver<Position> {
127 let (saved, durable) = watch::channel(position);
128 let mut current = self.storage.inner.state.lock().unwrap();
129 current.journal_required_data = current.status.saved.clone();
130 current.journal_saved = Some(saved);
131 current.journal_limit = self.limits.bytes.get();
132 durable
133 }
134}
135
136