1use std::collections::{BTreeMap, VecDeque};
18use std::fmt;
19use std::future::Future;
20use std::num::NonZeroUsize;
21use std::pin::Pin;
22use std::sync::Arc;
23use std::task::{Context, Poll};
24use futures_core::Stream;
26use tokio::sync::{mpsc, oneshot, watch};
27use tracing::Instrument;
28use super::TrackedState;
30use super::entry::{At, Entry, Link, Position, PrincipalId};
31use super::envelope::{Envelope, Recorded};
32use super::transaction::dispatch::Dispatch;
33use super::transaction::{DecodeError, Inverting};
34use crate::Application;
35use crate::error::Error;
36use crate::supervision::executor::InContext;
37
38const SHARED_PAGE_BYTES: usize = 8 * 1024 * 1024;
41
42type Predicate<A> = Arc<dyn Fn(&<A as Application>::Transaction) -> bool + Send + Sync>;
47
48type Step<A, X> = Pin<Box<dyn Future<Output = (Parts<A>, Result<Vec<Entry<X>>, Error>)> + Send>>;
49
50pub struct Origin<S> {
53 pub value: S,
54 pub position: Position,
55}
56
57pub(crate) struct Reads<A: Application> {
58 sender: mpsc::Sender<ReadRequest<A>>,
59}
60
61pub struct Focus<A: Application> {
62 predicate: Option<Predicate<A>>,
63}
64
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub struct Reversals {
67 pub undoable: bool,
68 pub redoable: bool,
69}
70
71pub(crate) struct Undoable<A: Application> {
72 pub(crate) principal: PrincipalId,
73 pub(crate) forward: crate::operations::Response<A::Transaction>,
74 pub(crate) inverse: Option<crate::operations::Response<A::Transaction>>,
75 pub(crate) undone: Option<Position>,
76}
77
78pub(crate) struct Undos<A: Application> {
79 pub(crate) entries: BTreeMap<Position, Undoable<A>>,
80}
81
82pub(crate) struct Tail<A: Application> {
83 pub(crate) entries: VecDeque<Arc<Entry<Recorded<A>>>>,
84 capacity: usize,
85 floor: Position,
86}
87
88pub(crate) struct SharedPage<A: Application> {
89 pub(crate) entries: Vec<Arc<Entry<Recorded<A>>>>,
90 pub(crate) reaches: Position,
91}
92
93struct Parts<A: Application> {
94 trace: crate::supervision::executor::SpanContext,
95 tracked: Arc<TrackedState<A>>,
96 frontier: Position,
97 doorbell: watch::Receiver<Position>,
98}
99
100struct Watching<A: Application, X> {
101 parts: Option<Parts<A>>,
102 step: Option<Step<A, X>>,
103 pending: VecDeque<Entry<X>>,
104 done: bool,
105}
106
107pub(crate) enum ReadRequest<A: Application> {
110 Entries {
111 trace: crate::supervision::executor::SpanContext,
112 after: Position,
113 through: Position,
114 reply: oneshot::Sender<Result<Vec<Entry<Recorded<A>>>, Error>>,
115 },
116 State {
117 trace: crate::supervision::executor::SpanContext,
118 origin: Origin<A::State>,
119 at: Position,
120 reply: oneshot::Sender<Result<At<A::State>, Error>>,
121 },
122}
123
124pub(crate) trait ReplayPayload<A: Application>: Sized {
127 fn prepared(&self) -> Option<crate::state::Prepared> {
128 None
129 }
130
131 fn forward(&self) -> Option<crate::operations::Response<A::Transaction>>;
132 fn restore(
133 envelope: Envelope<Self>, catalog: Option<&Dispatch<A, Self>>, state: &mut A::State,
134 ) -> Result<Option<Recorded<A>>, DecodeError>;
135}
136
137impl<S> Origin<S> {
140 pub fn initial(value: S) -> Self {
141 Self { value, position: Position::ORIGIN }
142 }
143
144 pub fn at(value: S, position: Position) -> Self {
145 Self { value, position }
146 }
147}
148
149impl<S> From<S> for Origin<S> {
150 fn from(value: S) -> Self {
151 Self::initial(value)
152 }
153}
154
155impl<S> From<At<S>> for Origin<S> {
156 fn from(origin: At<S>) -> Self {
157 Self::at(origin.value, origin.position)
158 }
159}
160
161impl<A: Application> Reads<A> {
162 pub(crate) fn channel(capacity: usize) -> (Self, mpsc::Receiver<ReadRequest<A>>) {
163 let (sender, receiver) = mpsc::channel(capacity.max(1));
164
165 (Self { sender }, receiver)
166 }
167
168 #[tracing::instrument(target = "harmos::state", name = "state.entries", level = "debug", skip_all)]
169 pub(crate) async fn entries(&self, after: Position, through: Position) -> Result<Vec<Entry<Recorded<A>>>, Error> {
170 let (reply, answer) = oneshot::channel();
171 self.sender
172 .send(ReadRequest::Entries {
173 after,
174 through,
175 reply,
176 trace: crate::supervision::executor::SpanContext::new(tracing::Span::current(), tracing::Span::none()),
177 })
178 .await
179 .map_err(|_| Error::BeyondTail)?;
180
181 answer.await.map_err(|_| Error::Unavailable)?
182 }
183
184 #[tracing::instrument(target = "harmos::state", name = "state.state", level = "debug", skip_all)]
185 pub(crate) async fn state(&self, origin: Origin<A::State>, at: Position) -> Result<At<A::State>, Error> {
186 if origin.position > at {
187 return Err(Error::OriginAhead);
188 }
189 if origin.position == at {
190 return Ok(At { value: origin.value, position: at });
191 }
192
193 let (reply, answer) = oneshot::channel();
194 self.sender
195 .send(ReadRequest::State {
196 origin,
197 at,
198 reply,
199 trace: crate::supervision::executor::SpanContext::new(tracing::Span::current(), tracing::Span::none()),
200 })
201 .await
202 .map_err(|_| Error::BeyondTail)?;
203
204 answer.await.map_err(|_| Error::Unavailable)?
205 }
206}
207
208impl<A: Application> Clone for Reads<A> {
209 fn clone(&self) -> Self {
210 Self { sender: self.sender.clone() }
211 }
212}
213
214impl<A: Application> Focus<A> {
215 pub fn all() -> Self {
216 Self { predicate: None }
217 }
218
219 pub fn matching(predicate: impl Fn(&A::Transaction) -> bool + Send + Sync + 'static) -> Self {
220 Self { predicate: Some(Arc::new(predicate)) }
221 }
222
223 fn admits(&self, transaction: &A::Transaction) -> bool {
224 self.predicate.as_ref().is_none_or(|predicate| predicate(transaction))
225 }
226}
227
228impl<A: Application> Clone for Focus<A> {
229 fn clone(&self) -> Self {
230 Self { predicate: self.predicate.clone() }
231 }
232}
233
234impl<A: Application> fmt::Debug for Focus<A> {
235 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
236 formatter.write_str(match self.predicate {
237 Some(_) => "Focus::matching(..)",
238 None => "Focus::all()",
239 })
240 }
241}
242
243impl<A: Application> Undos<A> {
244 pub(crate) fn record(
245 &mut self, position: Position, principal: Option<&PrincipalId>, link: Option<Link>,
246 forward: &crate::operations::Response<A::Transaction>, inverse: Option<A::Transaction>,
247 ) {
248 match link {
249 Some(Link::UndoOf(original)) => {
250 if let Some(entry) = self.entries.get_mut(&original) {
251 entry.undone = Some(position);
252 }
253 }
254 Some(Link::RedoOf(original)) => {
255 if let Some(entry) = self.entries.get_mut(&original) {
256 entry.undone = None;
257 entry.inverse = inverse.map(crate::operations::Response::new);
261 }
262 }
263 None => {
264 let Some(principal) = principal else {
265 return;
266 };
267 self.entries.retain(|_, entry| entry.principal != *principal || entry.undone.is_none());
273 self.entries.insert(
274 position,
275 Undoable {
276 principal: principal.clone(),
277 forward: forward.clone(),
278 inverse: inverse.map(crate::operations::Response::new),
279 undone: None,
280 },
281 );
282 }
283 }
284 }
285
286 pub(crate) fn undoable(
287 &self, principal: &PrincipalId, focus: &Focus<A>,
288 ) -> Option<(Position, crate::operations::Response<A::Transaction>)> {
289 for (position, entry) in self.entries.iter().rev() {
290 if entry.principal != *principal {
291 continue;
292 }
293 if entry.undone.is_some() || !focus.admits(&entry.forward) {
294 continue;
295 }
296 let inverse = entry.inverse.as_ref()?;
297 return Some((*position, inverse.clone()));
298 }
299 None
300 }
301
302 pub(crate) fn redoable(
303 &self, principal: &PrincipalId, focus: &Focus<A>,
304 ) -> Option<(Position, crate::operations::Response<A::Transaction>)> {
305 self.entries
306 .iter()
307 .filter(|(_, entry)| entry.principal == *principal && focus.admits(&entry.forward))
308 .filter_map(|(position, entry)| entry.undone.map(|at| (at, *position, entry.forward.clone())))
309 .max_by_key(|(at, _position, _forward)| *at)
310 .map(|(_at, position, forward)| (position, forward))
311 }
312
313 pub(crate) fn reversals(&self, principal: &PrincipalId, focus: &Focus<A>) -> Reversals {
314 Reversals {
315 undoable: self.undoable(principal, focus).is_some(),
316 redoable: self.redoable(principal, focus).is_some(),
317 }
318 }
319}
320
321impl<A: Application> Default for Undos<A> {
322 fn default() -> Self {
323 Self { entries: BTreeMap::new() }
324 }
325}
326
327impl<A: Application> Tail<A> {
328 pub(crate) fn new(origin: Position, capacity: usize) -> Self {
329 Self {
330 entries: VecDeque::new(),
331 capacity: capacity.max(1),
332 floor: origin,
333 }
334 }
335
336 pub(crate) fn recovered(frontier: Position, capacity: usize, entries: Vec<Arc<Entry<Recorded<A>>>>) -> Self {
337 if entries.is_empty() {
338 return Self::new(frontier, capacity);
339 }
340 let floor = entries.first().map_or(frontier, |entry| Position(entry.position.0.saturating_sub(1)));
341
342 Self {
343 entries: entries.into(),
344 capacity: capacity.max(1),
345 floor,
346 }
347 }
348
349 pub(crate) fn push_group(&mut self, entries: Vec<Entry<Recorded<A>>>) -> Vec<Arc<Entry<Recorded<A>>>> {
350 while self.entries.len() + entries.len() > self.capacity && !self.entries.is_empty() {
351 let evicted = self.entries.pop_front().expect("nonempty tail");
352 let frontier = evicted.metadata.commit.map_or(evicted.position, |(_, end)| end);
353 self.floor = frontier;
354 while self.entries.front().is_some_and(|entry| entry.position <= frontier) {
355 self.entries.pop_front();
356 }
357 }
358 let entries: Vec<_> = entries.into_iter().map(Arc::new).collect();
359 self.entries.extend(entries.iter().cloned());
360 entries
361 }
362
363 pub(crate) fn splits_commit(&self, position: Position) -> bool {
364 self.entries.iter().any(|entry| entry.metadata.commit.is_some_and(|(first, end)| first <= position && position < end))
365 }
366
367 pub(crate) fn since(&self, after: Position) -> Result<Vec<Arc<Entry<Recorded<A>>>>, Error> {
368 if after < self.floor {
369 return Err(Error::BeyondTail);
370 }
371
372 Ok(self.entries.iter().filter(|entry| entry.position > after).map(Arc::clone).collect())
373 }
374
375 pub(crate) fn scan<X: Clone + 'static>(&self, after: Position) -> Result<(Vec<Entry<X>>, Position), Error> {
376 self.scan_through(after, Position(u64::MAX))
377 }
378
379 pub(crate) fn scan_through<X: Clone + 'static>(&self, after: Position, through: Position) -> Result<(Vec<Entry<X>>, Position), Error> {
380 let mut frontier = after;
381 let mut found = Vec::new();
382 loop {
383 let page = self.shared_page(frontier, through, NonZeroUsize::new(1024).unwrap())?;
384 if page.entries.is_empty() {
385 break;
386 }
387 frontier = page.reaches;
388 for entry in page.entries {
389 if let Some(value) = entry.value.select::<X>() {
390 found.push(Entry {
391 prepared: None,
392 position: entry.position,
393 metadata: entry.metadata.clone(),
394 value,
395 });
396 }
397 }
398 }
399
400 Ok((found, frontier))
401 }
402
403 #[tracing::instrument(target = "harmos::state", name = "state.shared_page", level = "debug", skip_all)]
404 pub(crate) fn shared_page(&self, after: Position, through: Position, max_rows: NonZeroUsize) -> Result<SharedPage<A>, Error> {
405 self.shared_page_sized(after, through, max_rows, |_| 0)
406 }
407
408 pub(crate) fn shared_page_sized(
409 &self, after: Position, through: Position, max_rows: NonZeroUsize, size: impl Fn(&A::Record) -> usize,
410 ) -> Result<SharedPage<A>, Error> {
411 if after < self.floor {
412 return Err(Error::BeyondTail);
413 }
414 let start = self.entries.partition_point(|entry| entry.position <= after);
415 let count = max_rows.get().min(self.entries.len() - start);
416 let mut entries = Vec::with_capacity(count);
417 let mut reaches = after;
418 let mut bytes = 0usize;
419 for entry in self.entries.iter().skip(start).take(count) {
420 if entry.position > through {
421 break;
422 }
423 if let Recorded::Record(record) = &entry.value {
424 let count = size(record);
425 if !entries.is_empty() && count > SHARED_PAGE_BYTES.saturating_sub(bytes) {
426 break;
427 }
428 bytes = bytes.saturating_add(count);
429 }
430 reaches = entry.position;
431 entries.push(Arc::clone(entry));
432 }
433 Ok(SharedPage { entries, reaches })
434 }
435}
436
437impl<A: Application, X> Unpin for Watching<A, X> {}
438
439impl<A, X> Stream for Watching<A, X>
440where
441 A: Application,
442 X: Clone + Send + 'static,
443{
444 type Item = Result<Entry<X>, Error>;
445
446 fn poll_next(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
447 let watching = self.get_mut();
448
449 if let Some(entry) = watching.pending.pop_front() {
450 return Poll::Ready(Some(Ok(entry)));
451 }
452 if watching.done {
453 return Poll::Ready(None);
454 }
455
456 let mut step = match watching.step.take() {
457 Some(step) => step,
458 None => match watching.parts.take() {
459 Some(parts) => {
460 let dispatcher = parts.trace.dispatcher.clone();
461 let span = tracing::dispatcher::with_default(
462 &dispatcher,
463 || tracing::trace_span!(target: "harmos::state", parent: &parts.trace.span, "state.watch_batch", after = parts.frontier.0),
464 );
465 Box::pin(next(parts).instrument(span).in_context(dispatcher))
466 }
467 None => {
468 watching.done = true;
469 return Poll::Ready(None);
470 }
471 },
472 };
473
474 match step.as_mut().poll(context) {
475 Poll::Pending => {
476 watching.step = Some(step);
477 Poll::Pending
478 }
479 Poll::Ready((parts, Ok(found))) => {
480 watching.parts = Some(parts);
481 watching.pending.extend(found);
482 match watching.pending.pop_front() {
483 Some(entry) => Poll::Ready(Some(Ok(entry))),
484 None => {
485 watching.done = true;
486 Poll::Ready(None)
487 }
488 }
489 }
490 Poll::Ready((parts, Err(refusal))) => {
491 watching.parts = Some(parts);
492 watching.done = true;
493 Poll::Ready(Some(Err(refusal)))
494 }
495 }
496 }
497}
498
499#[tracing::instrument(target = "harmos::state", name = "state.replay", level = "debug", skip_all)]
502pub(crate) fn replay<A, W>(
503 catalog: Option<&Dispatch<A, W>>, recorded: Vec<Envelope<W>>, state: &mut At<A::State>, undos: &mut Undos<A>, inverses: Inverting<A>,
504 tail: usize,
505) -> Result<Vec<Arc<Entry<Recorded<A>>>>, DecodeError>
506where
507 A: Application,
508 W: ReplayPayload<A>,
509{
510 let mut redelivered = VecDeque::new();
511
512 for envelope in recorded {
513 let prepared = envelope.payload.prepared();
514 let position = envelope.position;
515 let metadata = envelope.metadata.clone();
516 let link = metadata.link;
517 let principal = metadata.principal.clone();
518 let forward = envelope.payload.forward();
519 let inverse = forward.as_ref().and_then(|change| inverses(change, &state.value));
520 let recovered = W::restore(envelope, catalog, &mut state.value)?;
521
522 if let Some(forward) = &forward {
523 undos.record(position, principal.as_ref(), link, forward, inverse);
524 }
525 if let Some(value) = recovered
526 && tail != 0
527 {
528 let first = metadata.commit.map_or(position, |(first, _)| first);
530 while redelivered.len() >= tail && redelivered.front().is_some_and(|entry: &Arc<Entry<Recorded<A>>>| entry.position < first) {
531 let end = redelivered.front().map(|entry| entry.metadata.commit.map_or(entry.position, |(_, end)| end)).expect("nonempty");
532 while redelivered.front().is_some_and(|entry| entry.position <= end) {
533 redelivered.pop_front();
534 }
535 }
536 redelivered.push_back(Arc::new(Entry { prepared, position, metadata, value }));
537 }
538 state.position = position;
539 }
540
541 Ok(redelivered.into())
542}
543
544pub(crate) fn watching<A, X>(tracked: Arc<TrackedState<A>>, after: Position) -> impl Stream<Item = Result<Entry<X>, Error>>
545where
546 A: Application,
547 X: Clone + Send + 'static,
548{
549 let doorbell = tracked.applied.subscribe();
550
551 Watching {
552 parts: Some(Parts {
553 tracked,
554 frontier: after,
555 doorbell,
556 trace: crate::supervision::executor::SpanContext::new(tracing::Span::current(), tracing::Span::none()),
557 }),
558 step: None,
559 pending: VecDeque::new(),
560 done: false,
561 }
562}
563
564async fn next<A, X>(mut parts: Parts<A>) -> (Parts<A>, Result<Vec<Entry<X>>, Error>)
567where
568 A: Application,
569 X: Clone + Send + 'static,
570{
571 loop {
572 if parts.tracked.poisoned() {
573 return (parts, Err(Error::Unavailable));
574 }
575
576 let scanned = {
577 let tail = parts.tracked.tail.read().await;
578 tail.scan::<X>(parts.frontier)
579 };
580
581 match scanned {
582 Err(refusal) => return (parts, Err(refusal)),
583 Ok((found, frontier)) => {
584 parts.frontier = frontier;
585 if !found.is_empty() {
586 return (parts, Ok(found));
587 }
588 }
589 }
590
591 if parts.doorbell.changed().await.is_err() {
592 return (parts, Err(Error::Unavailable));
593 }
594 }
595}
596
597#[cfg(test)]
600mod tests {
601 use super::*;
602 use crate::state::{CorrelationId, Metadata};
603 use serde::{Deserialize, Serialize};
604
605 #[crate::record(id = "listener.page")]
606 #[derive(Clone, Serialize, Deserialize)]
607 struct PageRecord(u64);
608
609 struct Model;
610
611 #[crate::lanes]
612 enum Lanes {
613 #[default]
614 Default,
615 }
616
617 impl Application for Model {
618 type Lanes = Lanes;
619 type State = ();
620 type Transaction = crate::state::Never;
621 type Record = PageRecord;
622 type Streams = ();
623 type Resources = ();
624 }
625
626 #[test]
627 fn listener_pages_bound_bytes_without_losing_the_next_record() {
628 let mut tail = Tail::<Model>::new(Position::ORIGIN, 600);
629 for sequence in 1..=600 {
630 tail.push_group(vec![Entry {
631 prepared: None,
632 position: Position(sequence),
633 value: Recorded::Record(PageRecord(1024 * 1024)),
634 metadata: Metadata {
635 commit: None,
636 principal: None,
637 correlation: CorrelationId(Position(sequence)),
638 causation: None,
639 request: None,
640 link: None,
641 at: std::time::UNIX_EPOCH,
642 },
643 }]);
644 }
645 let mut after = Position::ORIGIN;
646 let mut delivered = 0;
647 while after < Position(600) {
648 let page = tail.shared_page_sized(after, Position(600), NonZeroUsize::new(512).unwrap(), |record| record.0 as usize).unwrap();
649 assert_eq!(page.entries.len(), 8);
650 for (index, entry) in page.entries.iter().enumerate() {
651 assert_eq!(entry.position.0, after.0 + index as u64 + 1);
652 }
653 delivered += page.entries.len();
654 after = page.reaches;
655 }
656 assert_eq!(delivered, 600);
657 assert_eq!(tail.shared_page(Position::ORIGIN, Position(600), NonZeroUsize::new(512).unwrap()).unwrap().entries.len(), 512);
658 }
659}