1use std::future::Future;
18use std::pin::Pin;
19use std::sync::Arc;
20use tokio::sync::watch;
22use crate::state::dispatch::{Decode, Dispatch, Restored};
24use crate::state::{Feed, ReplayPayload};
25use crate::storage::consumer::{Attached, JournalConsumer, Recording, Series, Serving, StreamConsumer};
26use crate::{
27 Application, DefinitionId, Error, runtime::Incompatible, state::DecodeError, state::Entry, state::Envelope, state::Kind,
28 state::Position, state::Recorded, state::Transaction,
29};
30
31pub struct Payload<A: Application> {
40 pub prepared: Option<crate::state::Prepared>,
41 pub id: DefinitionId,
42 pub version: u32,
43 pub value: Recorded<A>,
44}
45
46pub struct Recovery<A: Application> {
47 pub commits: Vec<(Position, Position)>,
48 pub frontier: Position,
49 pub entries: Vec<Envelope<Payload<A>>>,
50 pub encoded: Vec<(Position, Arc<Vec<u8>>)>,
51}
52
53pub(crate) struct Attachments<A: Application> {
54 pub(crate) capture: Option<crate::state::capture::Declaration<A>>,
55 attached: Vec<Box<dyn Attached<A>>>,
56 streams: Vec<Box<dyn Recording<A>>>,
57 series: Option<Box<dyn Serving<A>>>,
58 recovery: Option<Box<dyn Recovering<A>>>,
59 snapshot: Option<Box<dyn Attached<A>>>,
60 retention: Option<Horizon>,
61 tail: usize,
62}
63
64pub(crate) struct Consumers<A: Application> {
65 attached: Vec<Box<dyn Attached<A>>>,
66 streams: Vec<Box<dyn Recording<A>>>,
67 series: Option<Box<dyn Serving<A>>>,
68 recovery: Option<Box<dyn Recovering<A>>>,
69 snapshot: Option<Box<dyn Attached<A>>>,
70 retention: Option<Horizon>,
71 durable: Option<watch::Sender<Position>>,
72 recovered: Vec<Arc<Entry<Recorded<A>>>>,
73 stored: Vec<(Position, Arc<Vec<u8>>)>,
74 frontier: Position,
75 source_frontier: Position,
76 snapshot_frontier: Position,
77 pruned: Position,
78}
79
80#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83pub enum Horizon {
84 BeneathSnapshot,
85}
86
87pub trait Source<A: Application>: JournalConsumer<A> {
90 fn load(&mut self) -> impl Future<Output = Result<Recovery<A>, Incompatible>> + Send;
91
92 fn read(&mut self, _after: Position, _through: Position) -> impl Future<Output = Result<Recovery<A>, Error>> + Send {
93 async { Err(Error::BeyondTail) }
94 }
95
96 fn prune(&mut self, _beneath: Position) -> impl Future<Output = ()> + Send {
97 async {}
98 }
99}
100
101pub(crate) trait Recovering<A: Application>: Attached<A> {
102 fn load(&mut self) -> Pin<Box<dyn Future<Output = Result<Recovery<A>, Incompatible>> + Send + '_>>;
103
104 fn read(&mut self, after: Position, through: Position) -> Pin<Box<dyn Future<Output = Result<Recovery<A>, Error>> + Send + '_>>;
105
106 fn prune(&mut self, beneath: Position) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
107}
108
109impl<A: Application> Default for Recovery<A> {
112 fn default() -> Self {
113 Self {
114 commits: Vec::new(),
115 frontier: Position::ORIGIN,
116 entries: Vec::new(),
117 encoded: Vec::new(),
118 }
119 }
120}
121
122impl<A: Application, S: Source<A>> Recovering<A> for S {
123 fn load(&mut self) -> Pin<Box<dyn Future<Output = Result<Recovery<A>, Incompatible>> + Send + '_>> {
124 Box::pin(Source::load(self))
125 }
126
127 fn read(&mut self, after: Position, through: Position) -> Pin<Box<dyn Future<Output = Result<Recovery<A>, Error>> + Send + '_>> {
128 Box::pin(Source::read(self, after, through))
129 }
130
131 fn prune(&mut self, beneath: Position) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
132 Box::pin(Source::prune(self, beneath))
133 }
134}
135
136impl<A: Application> ReplayPayload<A> for Payload<A> {
137 fn prepared(&self) -> Option<crate::state::Prepared> {
138 self.prepared.clone()
139 }
140
141 fn forward(&self) -> Option<crate::operations::Response<A::Transaction>> {
142 match &self.value {
143 Recorded::Transaction(transaction) => Some(transaction.clone()),
144 Recorded::Record(_) | Recorded::System(_) => None,
145 }
146 }
147
148 fn restore(
149 envelope: Envelope<Self>, catalog: Option<&Dispatch<A, Self>>, state: &mut A::State,
150 ) -> Result<Option<Recorded<A>>, DecodeError> {
151 let system = match &envelope.payload.value {
152 Recorded::System(fact) => Some(fact.clone()),
153 Recorded::Transaction(_) | Recorded::Record(_) => None,
154 };
155 if let Some(fact) = system {
156 return Ok(Some(Recorded::System(fact)));
157 }
158 let forward = match &envelope.payload.value {
159 Recorded::Transaction(transaction) => Some(transaction.clone()),
160 Recorded::Record(_) | Recorded::System(_) => None,
161 };
162
163 let Some(catalog) = catalog else {
164 let Payload { value, .. } = envelope.payload;
165 return Ok(match value {
166 Recorded::Transaction(transaction) => {
167 transaction.apply(state);
168 Some(Recorded::Transaction(transaction))
169 }
170 Recorded::Record(record) => Some(Recorded::Record(record)),
171 Recorded::System(fact) => Some(Recorded::System(fact)),
172 });
173 };
174
175 Ok(match catalog.restore(envelope, state)? {
176 Restored::Applied => forward.map(Recorded::Transaction),
177 Restored::Recorded(record) => Some(Recorded::Record(record)),
178 Restored::Skipped => None,
179 })
180 }
181}
182
183impl<A: Application> Attachments<A> {
184 pub(crate) fn has_recovery_owner(&self) -> bool {
185 self.capture.is_some() || self.recovery.is_some() || self.snapshot.is_some() || self.retention.is_some()
186 }
187
188 pub(crate) fn new() -> Self {
189 Self {
190 capture: None,
191 attached: Vec::new(),
192 streams: Vec::new(),
193 series: None,
194 recovery: None,
195 snapshot: None,
196 retention: None,
197 tail: 1024,
198 }
199 }
200
201 pub(crate) fn require_capture(
202 &mut self, limits: crate::state::CaptureLimits, consumer: impl crate::state::JournalCapture<A>,
203 ) -> Result<(), crate::state::CaptureError> {
204 if limits.pending_bytes == 0
205 || limits.max_group_bytes == 0
206 || limits.max_group_bytes > limits.pending_bytes
207 || limits.pending_bytes as usize > tokio::sync::Semaphore::MAX_PERMITS
208 {
209 return Err(crate::state::CaptureError::InvalidLimits);
210 }
211 self.capture = Some(crate::state::capture::Declaration::Callback(crate::state::capture::CallbackDeclaration {
212 limits,
213 consumer: Box::new(consumer),
214 }));
215
216 Ok(())
217 }
218
219 pub(crate) fn attach(&mut self, consumer: impl JournalConsumer<A>) {
220 self.attached.push(Box::new(consumer));
221 }
222
223 pub(crate) fn attach_streams(&mut self, consumer: impl StreamConsumer<A>) {
224 self.streams.push(Box::new(consumer));
225 }
226
227 pub(crate) fn series(&mut self, series: impl Series<A>) {
228 if let Some(demoted) = self.series.replace(Box::new(series)) {
229 self.streams.push(demoted);
230 }
231 }
232
233 pub(crate) fn recover(&mut self, source: impl Source<A>) {
234 if let Some(demoted) = self.recovery.replace(Box::new(source)) {
235 self.attached.push(demoted);
236 }
237 }
238
239 pub(crate) fn snapshot(&mut self, snapshot: impl JournalConsumer<A>) {
240 if let Some(demoted) = self.snapshot.replace(Box::new(snapshot)) {
241 self.attached.push(demoted);
242 }
243 }
244
245 pub(crate) fn retain(&mut self, horizon: Horizon) {
246 self.retention = Some(horizon);
247 }
248
249 pub(crate) fn tail(&mut self, entries: usize) {
250 self.tail = entries.max(1);
251 }
252
253 pub(crate) fn tail_window(&self) -> Result<usize, Incompatible> {
254 if self.retention == Some(Horizon::BeneathSnapshot) && self.snapshot.is_none() {
255 return Err(Incompatible::unreadable("retention beneath a snapshot requires a snapshot declaration"));
256 }
257
258 Ok(self.tail)
259 }
260
261 pub(crate) fn supervised(&mut self) -> Consumers<A> {
262 let snapshot_frontier = self.snapshot.as_ref().map_or(Position::ORIGIN, |snapshot| snapshot.resume());
263
264 Consumers {
265 attached: std::mem::take(&mut self.attached),
266 streams: std::mem::take(&mut self.streams),
267 series: self.series.take(),
268 recovery: self.recovery.take(),
269 snapshot: self.snapshot.take(),
270 retention: self.retention,
271 durable: None,
272 recovered: Vec::new(),
273 stored: Vec::new(),
274 frontier: Position::ORIGIN,
275 source_frontier: Position::ORIGIN,
276 snapshot_frontier,
277 pruned: Position::ORIGIN,
278 }
279 }
280}
281
282impl<A: Application> Default for Attachments<A> {
283 fn default() -> Self {
284 Self::new()
285 }
286}
287
288impl<A: Application> Consumers<A> {
289 pub(crate) fn durability(&mut self, at: Position) -> Option<watch::Receiver<Position>> {
290 let (durable, stored) = watch::channel(at);
291 self.durable = self.recovery.as_ref().map(|_| durable);
292
293 self.durable.as_ref().map(|_| stored)
294 }
295
296 #[tracing::instrument(target = "harmos::storage", name = "storage.load", level = "debug", skip_all)]
297 pub(crate) async fn load(&mut self) -> Result<Recovery<A>, Incompatible> {
298 let recovery = match &mut self.recovery {
299 Some(source) => source.load().await,
300 None => Ok(Recovery::default()),
301 }?;
302 self.source_frontier = recovery.frontier;
303
304 Ok(recovery)
305 }
306
307 #[tracing::instrument(target = "harmos::storage", name = "storage.read", level = "debug", skip_all)]
308 pub(crate) async fn read(&mut self, after: Position, through: Position) -> Result<Recovery<A>, Error> {
309 match &mut self.recovery {
310 Some(source) => source.read(after, through).await,
311 None => Err(Error::BeyondTail),
312 }
313 }
314
315 pub(crate) fn redelivering(
316 &mut self, recovered: Vec<Arc<Entry<Recorded<A>>>>, stored: Vec<(Position, Arc<Vec<u8>>)>, frontier: Position,
317 ) {
318 self.recovered = recovered;
319 self.stored = stored;
320 self.frontier = frontier;
321 }
322
323 #[tracing::instrument(target = "harmos::storage", name = "storage.drain", level = "debug", skip_all)]
324 pub(crate) async fn drain(&mut self, feed: &Feed<A>, applied: Position) -> Position {
325 let mut settled = applied;
326
327 if let Some(source) = &mut self.recovery {
328 let durable = fold(source.as_mut(), &self.recovered, &self.stored, self.frontier, feed, applied).await;
329
330 if let Some(stored) = &self.durable {
331 stored.send_replace(durable);
332 }
333 self.source_frontier = durable;
334 settled = settled.min(durable);
335 }
336 for consumer in &mut self.attached {
337 settled = settled.min(fold(consumer.as_mut(), &self.recovered, &self.stored, self.frontier, feed, applied).await);
338 }
339 if let Some(snapshot) = &mut self.snapshot {
340 let durable = fold(snapshot.as_mut(), &self.recovered, &self.stored, self.frontier, feed, applied).await;
341 self.snapshot_frontier = durable;
342 settled = settled.min(durable);
343 }
344 self.prune().await;
345 self.forget_redelivered();
346
347 settled
348 }
349
350 #[tracing::instrument(target = "harmos::storage", name = "storage.settle", level = "debug", skip_all)]
351 pub(crate) async fn settle(&mut self, applied: Position) -> Position {
352 let mut settled = applied;
353
354 if let Some(source) = &mut self.recovery {
355 let durable = source.drain(Vec::new(), applied).await;
356
357 if let Some(stored) = &self.durable {
358 stored.send_replace(durable);
359 }
360 self.source_frontier = durable;
361 settled = settled.min(durable);
362 }
363 for consumer in &mut self.attached {
364 settled = settled.min(consumer.drain(Vec::new(), applied).await);
365 }
366 if let Some(snapshot) = &mut self.snapshot {
367 let durable = snapshot.drain(Vec::new(), applied).await;
368 self.snapshot_frontier = durable;
369 settled = settled.min(durable);
370 }
371 self.prune().await;
372
373 settled
374 }
375
376 pub(crate) fn recordings(&mut self) -> impl Iterator<Item = &mut dyn Recording<A>> {
377 self.streams
378 .iter_mut()
379 .map(|recording| recording.as_mut() as &mut dyn Recording<A>)
380 .chain(self.series.iter_mut().map(|series| series.as_mut() as &mut dyn Recording<A>))
381 }
382
383 #[tracing::instrument(target = "harmos::storage", name = "storage.row_reader", level = "debug", skip_all)]
384 pub(crate) async fn row_reader(
385 &mut self, stream: crate::streams::Definition, track: &[String], start: std::ops::Bound<Position>, end: std::ops::Bound<Position>,
386 limits: crate::streams::ReadLimits,
387 ) -> Result<Box<dyn crate::streams::RowSource>, crate::streams::ReadError> {
388 match &mut self.series {
389 Some(series) => series.reader(stream, track, start, end, limits).await,
390 None => Err(crate::streams::ReadError::Unserved),
391 }
392 }
393
394 #[tracing::instrument(target = "harmos::storage", name = "storage.served", level = "debug", skip_all)]
395 pub(crate) async fn served(
396 &mut self, stream: crate::streams::Definition, track: &[String], start: std::ops::Bound<Position>, end: std::ops::Bound<Position>,
397 ) -> Result<Vec<crate::state::At<Arc<dyn std::any::Any + Send + Sync>>>, crate::streams::BeneathWindow> {
398 match &mut self.series {
399 Some(series) => series.rows(stream, track, start, end).await,
400 None => Err(crate::streams::BeneathWindow::Unserved),
401 }
402 }
403
404 #[tracing::instrument(target = "harmos::storage", name = "storage.surveyed", level = "debug", skip_all)]
405 pub(crate) async fn surveyed(
406 &mut self, stream: crate::streams::Definition, track: &[String], start: std::ops::Bound<u64>, end: std::ops::Bound<u64>,
407 budget: usize,
408 ) -> Result<Vec<crate::streams::Bucket>, crate::streams::BeneathWindow> {
409 match &mut self.series {
410 Some(series) => series.buckets(stream, track, start, end, budget).await,
411 None => Err(crate::streams::BeneathWindow::Unserved),
412 }
413 }
414
415 #[tracing::instrument(target = "harmos::storage", name = "storage.aggregated", level = "debug", skip_all)]
416 pub(crate) async fn aggregated(
417 &mut self, stream: crate::streams::Definition, track: &[String], start: std::ops::Bound<u64>, end: std::ops::Bound<u64>,
418 kind: crate::Aggregate,
419 ) -> Result<Vec<crate::streams::Aggregated>, crate::streams::BeneathWindow> {
420 match &mut self.series {
421 Some(series) => series.aggregate(stream, track, start, end, kind).await,
422 None => Err(crate::streams::BeneathWindow::Unserved),
423 }
424 }
425
426 #[tracing::instrument(target = "harmos::storage", name = "storage.sealed", level = "debug", skip_all)]
427 pub(crate) async fn sealed(&mut self, stream: crate::streams::Definition, track: &[String]) -> Position {
428 match &mut self.series {
429 Some(series) => series.seal(stream, track).await,
430 None => Position::ORIGIN,
431 }
432 }
433
434 #[tracing::instrument(target = "harmos::storage", name = "storage.flush", level = "debug", skip_all)]
435 pub(crate) async fn flush(&mut self) {
436 if let Some(source) = &mut self.recovery {
437 source.flush().await;
438 }
439 for consumer in &mut self.attached {
440 consumer.flush().await;
441 }
442 if let Some(snapshot) = &mut self.snapshot {
443 snapshot.flush().await;
444 }
445 for consumer in self.recordings() {
446 consumer.flush().await;
447 }
448 }
449
450 fn forget_redelivered(&mut self) {
451 let passed = self
452 .recovery
453 .iter()
454 .map(|source| source.resume())
455 .chain(self.attached.iter().map(|consumer| consumer.resume()))
456 .chain(self.snapshot.iter().map(|snapshot| snapshot.resume()))
457 .min()
458 .is_none_or(|behind| behind >= self.frontier);
459
460 if passed {
461 self.recovered = Vec::new();
462 self.stored = Vec::new();
463 }
464 }
465
466 async fn prune(&mut self) {
467 let Some(Horizon::BeneathSnapshot) = self.retention else {
468 return;
469 };
470 let beneath = self.snapshot_frontier;
471 if beneath <= self.pruned || self.source_frontier < beneath {
472 return;
473 }
474 let Some(source) = &mut self.recovery else {
475 return;
476 };
477
478 source.prune(beneath).await;
479 self.pruned = beneath;
480 }
481}
482
483pub(crate) fn declare<A: Application>(
486 catalog: &mut Dispatch<A, Payload<A>>, kind: Kind, id: DefinitionId, version: u32, decode: Decode<A>,
487) {
488 match kind {
489 Kind::Transaction => catalog.set_transaction(id, version, apply, decode),
490 Kind::Record => catalog.set_record(id, version, retain),
491 }
492}
493
494fn apply<A: Application>(payload: Payload<A>, state: &mut A::State) -> Result<(), DecodeError> {
497 let Payload { prepared, id, version, value } = payload;
498 match value {
499 Recorded::Transaction(transaction) => {
500 if let Some(prepared) = prepared {
501 crate::state::TransactionCatalog::replay(&*transaction, &prepared, state)
502 } else {
503 transaction.apply(state);
504 Ok(())
505 }
506 }
507 Recorded::Record(_) | Recorded::System(_) => Err(mismatched(id, version)),
508 }
509}
510
511fn retain<A: Application>(payload: Payload<A>) -> Result<A::Record, DecodeError> {
512 match payload.value {
513 Recorded::Record(record) => Ok(record),
514 Recorded::Transaction(_) => Err(mismatched(payload.id, payload.version)),
515 Recorded::System(_) => Err(mismatched(payload.id, payload.version)),
516 }
517}
518
519fn mismatched(id: DefinitionId, version: u32) -> DecodeError {
520 DecodeError::Malformed { id, version }
521}
522
523async fn fold<A: Application>(
524 consumer: &mut dyn Attached<A>, recovered: &[Arc<Entry<Recorded<A>>>], stored: &[(Position, Arc<Vec<u8>>)], frontier: Position,
525 feed: &Feed<A>, applied: Position,
526) -> Position {
527 let resume = consumer.resume();
528 let Ok(live) = feed.since(resume.max(frontier)).await else {
529 return consumer.drain(Vec::new(), resume).await;
530 };
531 let owed = stored.partition_point(|(position, _)| *position <= resume);
534 if owed < stored.len() {
535 consumer.redelivered(&stored[owed..]);
536 }
537 let mut entries: Vec<Arc<Entry<Recorded<A>>>> = recovered.iter().filter(|entry| entry.position > resume).map(Arc::clone).collect();
538 entries.extend(live);
539
540 consumer.drain(entries, applied).await
541}
542
543