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