harmos_storage/state/
reader.rs1use std::num::NonZeroUsize;
18
19use tokio::sync::{RwLockReadGuard, watch};
21
22use super::{RecoveryPage, RecoverySnapshot};
24use crate::storage::manager::{Lifecycle, encoded_size};
25use crate::storage::{ReadLimits, RecoveryPosition, StorageManager};
26use crate::{Error, Result};
27use harmos_domain::state::Position;
28
29pub struct RecoveryReader<'a, C> {
38 context: crate::execution::Context,
39 storage: &'a StorageManager<C>,
40 _lifecycle: RwLockReadGuard<'a, Lifecycle>,
41 through: RecoveryPosition,
42 limits: ReadLimits,
43 snapshot_bound: Option<NonZeroUsize>,
44}
45
46impl<C> StorageManager<C> {
53 #[tracing::instrument(name = "storage.state.reader.recovery_reader", level = "debug", skip_all)]
54 pub async fn recovery_reader(&self, limits: ReadLimits, snapshot_bytes: Option<NonZeroUsize>) -> Result<RecoveryReader<'_, C>> {
55 let lifecycle = tracing::Instrument::instrument(self.inner.lifecycle.read(), tracing::trace_span!("storage.lock.wait")).await;
56 let invalid = |reason| Error::InvalidInput { field: "state recovery", reason };
57 let snapshot_bound = snapshot_bytes;
58 let through = {
59 let mut current = self.inner.state.lock().unwrap();
60 current.collect();
61 if current.status.running || current.status.closing || current.journal_saved.is_some() {
62 return Err(invalid("managed recovery requires a dormant acquired manager"));
63 }
64 current.status.recovery_saved.clone()
65 };
66 Ok(RecoveryReader {
67 context: crate::execution::Context::current(),
68 storage: self,
69 _lifecycle: lifecycle,
70 through,
71 limits,
72 snapshot_bound,
73 })
74 }
75}
76
77impl<C> RecoveryReader<'_, C> {
78 pub fn through(&self) -> &RecoveryPosition {
79 &self.through
80 }
81
82 pub async fn snapshot(&self) -> Result<Option<crate::ReadBatch<RecoverySnapshot>>> {
83 let context = self.context.clone();
84 let span = context.sync(|| tracing::trace_span!("storage.reader.page"));
85 let future = async {
86 let Some(bound) = self.snapshot_bound else {
87 return Ok(None);
88 };
89 let snapshot = self.storage.inner.bounded("recovery.read_snapshot", self.storage.inner.recovery.read_snapshot(bound)).await?;
90 if let Some(snapshot) = &snapshot
91 && (!self.through.covers(&snapshot.through)? || encoded_size(snapshot)? > bound.get())
92 {
93 return Err(Error::InvalidInput {
94 field: "state snapshot",
95 reason: "invalid managed snapshot boundary or size",
96 });
97 }
98 if let Some(snapshot) = &snapshot {
99 self.storage.check_state_dependency(&snapshot.required_data)?;
100 }
101 Ok(snapshot.map(crate::ReadBatch::new))
102 };
103 context.scope(tracing::Instrument::instrument(future, span)).await
104 }
105
106 pub async fn page(&self, after: &RecoveryPosition) -> Result<crate::ReadBatch<RecoveryPage>> {
107 let context = self.context.clone();
108 let span = context.sync(|| tracing::trace_span!("storage.reader.page"));
109 let future = async {
110 let page = self
111 .storage
112 .inner
113 .bounded("recovery.read_entries", self.storage.inner.recovery.read_entries(after, &self.through, self.limits))
114 .await?;
115 page.check(after, &self.through, self.limits, |entry| encoded_size(entry).unwrap_or(usize::MAX))?;
116 for entry in &page.entries {
117 self.storage.check_state_dependency(&entry.required_data)?;
118 }
119 Ok(crate::ReadBatch::new(page))
120 };
121 context.scope(tracing::Instrument::instrument(future, span)).await
122 }
123
124 pub fn finish(self, position: Position) -> watch::Receiver<Position> {
125 let (saved, durable) = watch::channel(position);
126 let mut current = self.storage.inner.state.lock().unwrap();
127 current.journal_required_data = current.status.saved.clone();
128 current.journal_saved = Some(saved);
129 current.journal_limit = self.limits.bytes.get();
130 durable
131 }
132}
133
134