1use std::sync::atomic::{AtomicBool, Ordering};
18use std::sync::{Arc, Weak};
19use futures_core::Stream;
21use tokio::sync::{RwLock, mpsc, oneshot, watch};
22use tokio::task::JoinHandle;
23use tracing::Instrument;
24use super::ManagedState;
26use super::transaction::TransactionError;
27use super::writer::{Admission, Direction, Refusal, Request};
28use super::{
29 At, CausationId, CorrelationId, DecodeError, DefinitionId, Entry, Error, Focus, Inverting, Position, PreparationError, Prepared,
30 PrincipalId, ReadRequest, Reads, Receipt, Recorded, RequestKey, Reversals, Tail, Transaction, Undos, capture, dispatch, history,
31 writer,
32};
33use crate::Application;
34
35pub(crate) struct TrackedState<A: Application> {
44 pub(crate) state: RwLock<At<A::State>>,
45 pub(crate) tail: RwLock<Tail<A>>,
46 pub(super) applied: watch::Sender<Position>,
47 pub(super) stored: watch::Receiver<Position>,
48 pub(super) poisoned: AtomicBool,
49 pub(crate) sealing: watch::Sender<bool>,
50 pub(super) capture_failed: Arc<AtomicBool>,
51 pub(super) capture_progress: Option<watch::Receiver<Result<Position, crate::state::CaptureError>>>,
52 pub(super) catalog: Arc<dispatch::Dispatch<A, crate::storage::Payload<A>>>,
53 pub(super) reads: Reads<A>,
54 pub(super) record_preparation: Arc<tokio::sync::Semaphore>,
55 pub(super) clock: fn() -> super::Timestamp,
56}
57
58#[derive(Clone, Debug, Default)]
59pub(crate) struct Context {
60 pub(super) principal: Option<PrincipalId>,
61 pub(super) correlation: Option<CorrelationId>,
62 pub(super) causation: Option<CausationId>,
63}
64
65pub struct StateManager<A: Application> {
66 handles: Arc<()>,
67 record_schemas: Arc<crate::records::Registry>,
68 writer: mpsc::Sender<writer::QueuedRequest<A>>,
69 context: Context,
70 pub(super) tracked: Arc<TrackedState<A>>,
71}
72
73pub(crate) struct Feed<A: Application> {
74 pub(super) tracked: Arc<TrackedState<A>>,
75}
76
77impl<A: Application> TrackedState<A> {
84 pub(super) fn unavailable<E>(&self) -> Error<E> {
85 if self.capture_failed.load(Ordering::Acquire) { Error::CaptureFailed } else { Error::Unavailable }
86 }
87
88 pub(super) fn poison(&self) {
89 self.poisoned.store(true, Ordering::Release);
90 }
91
92 pub(crate) fn poisoned(&self) -> bool {
93 self.poisoned.load(Ordering::Acquire)
94 }
95}
96
97impl<A: Application> StateManager<A> {
98 pub fn state(&self) -> ManagedState<A> {
99 ManagedState::new(Arc::clone(&self.tracked))
100 }
101
102 pub(crate) fn handle_probe(&self) -> Weak<()> {
103 Arc::downgrade(&self.handles)
104 }
105
106 pub(crate) fn record_schemas(&self) -> Arc<crate::records::Registry> {
107 Arc::clone(&self.record_schemas)
108 }
109
110 #[allow(clippy::too_many_arguments)]
112 pub(crate) fn start(
113 origin: At<A::State>, inverses: Inverting<A>, undos: Undos<A>, requests: usize, tail: Tail<A>,
114 durability: Option<watch::Receiver<Position>>, catalog: Arc<dispatch::Dispatch<A, crate::storage::Payload<A>>>,
115 capture: Option<capture::Declaration<A>>, storage: Option<crate::storage::StorageManager<crate::storage::ErasedCatalog>>,
116 schema_settings: Arc<crate::config::SchemaSettings>, clock: fn() -> super::Timestamp,
117 ) -> (Self, JoinHandle<()>, mpsc::Receiver<ReadRequest<A>>) {
118 let position = origin.position;
119 let (sender, receiver) = mpsc::channel(requests.max(1));
120 let (reads, history) = Reads::channel(requests);
121 let (applied, _) = watch::channel(position);
122 let (durable, bridged) = watch::channel(position);
123 let stored = durability.unwrap_or(bridged);
124 let capture_failed = Arc::new(AtomicBool::new(false));
125 let capture = capture.map(|capture| capture.start(capture_failed.clone(), position));
126 let tracked = Arc::new(TrackedState {
127 state: RwLock::new(origin),
128 tail: RwLock::new(tail),
129 applied,
130 stored,
131 poisoned: AtomicBool::new(false),
132 sealing: watch::channel(false).0,
133 capture_failed,
134 capture_progress: capture.as_ref().map(capture::Capture::progress),
135 record_preparation: Arc::new(tokio::sync::Semaphore::new(1)),
136 catalog,
137 reads,
138 clock,
139 });
140 let running = tokio::spawn(writer::run(receiver, Arc::clone(&tracked), durable, inverses, undos, capture));
141
142 (
143 Self {
144 handles: Arc::new(()),
145 record_schemas: Arc::new(crate::records::Registry::new(storage, schema_settings)),
146 writer: sender,
147 context: Context::default(),
148 tracked,
149 },
150 running,
151 history,
152 )
153 }
154
155 #[tracing::instrument(target = "harmos::state", name = "state.commit", level = "debug", skip_all)]
156 pub async fn commit<T>(&self, transaction: T) -> Result<Receipt, Error<T::Error>>
157 where
158 T: Transaction<A::State> + Into<A::Transaction>,
159 T::Error: From<TransactionError<A>>,
160 {
161 self.request(None, crate::operations::Response::new(transaction.into())).await
162 }
163
164 #[tracing::instrument(target = "harmos::state", name = "state.commit_keyed", level = "debug", skip_all)]
165 pub async fn commit_keyed<T>(&self, key: RequestKey, transaction: T) -> Result<Receipt, Error<T::Error>>
166 where
167 T: Transaction<A::State> + Into<A::Transaction>,
168 T::Error: From<TransactionError<A>>,
169 {
170 self.request(Some(key), crate::operations::Response::new(transaction.into())).await
171 }
172
173 pub async fn commit_owned(
179 &self, transaction: crate::operations::Response<A::Transaction>,
180 ) -> Result<Receipt, Error<<A::Transaction as Transaction<A::State>>::Error>> {
181 self.request(None, transaction).await
182 }
183
184 pub async fn commit_keyed_owned(
190 &self, key: RequestKey, transaction: crate::operations::Response<A::Transaction>,
191 ) -> Result<Receipt, Error<<A::Transaction as Transaction<A::State>>::Error>> {
192 self.request(Some(key), transaction).await
193 }
194
195 pub(crate) fn record_resources(&self) -> (Arc<dispatch::Dispatch<A, crate::storage::Payload<A>>>, Arc<tokio::sync::Semaphore>) {
196 (Arc::clone(&self.tracked.catalog), Arc::clone(&self.tracked.record_preparation))
197 }
198
199 #[tracing::instrument(target = "harmos::state", name = "state.append_prepared", level = "debug", skip_all)]
200 pub(crate) async fn append_prepared(
201 &self, value: Recorded<A>, prepared: Prepared, permit: tokio::sync::OwnedSemaphorePermit,
202 ) -> Result<Receipt, Error> {
203 let (reply, answer) = oneshot::channel();
204 let request = Request::Record {
205 value,
206 prepared,
207 preparation: permit,
208 context: self.context.clone(),
209 reply,
210 };
211
212 let waiting = self.writer.reserve().instrument(tracing::debug_span!(target: "harmos::state", "state.admission_wait"));
213 let permit = match waiting.await {
214 Ok(permit) => permit,
215 Err(_) => return Err(self.unavailable()),
216 };
217 let queued = tracing::debug_span!(target: "harmos::state", "state.queue");
218 permit.send((request, crate::supervision::executor::SpanContext::new(tracing::Span::current(), queued)));
219 answer.await.map_err(|_| self.unavailable())?.map_err(preparation)
220 }
221
222 pub fn decode_transaction(&self, id: &DefinitionId, version: u32, bytes: &[u8]) -> Result<A::Transaction, DecodeError> {
223 self.tracked.catalog.decode_transaction(id, version, bytes)
224 }
225
226 pub async fn query<X>(&self, after: Position) -> impl Iterator<Item = Result<Entry<X>, Error>>
227 where
228 X: Clone + Send + 'static,
229 {
230 self.query_through(after, self.applied()).await
231 }
232
233 #[tracing::instrument(target = "harmos::state", name = "state.query_through", level = "debug", skip_all)]
234 pub async fn query_through<X>(&self, after: Position, through: Position) -> impl Iterator<Item = Result<Entry<X>, Error>>
235 where
236 X: Clone + Send + 'static,
237 {
238 if self.tracked.poisoned() {
239 return vec![Err(self.unavailable())].into_iter();
240 }
241
242 if through > self.applied() {
243 return vec![Err(Error::FrontierAhead)].into_iter();
244 }
245 let scanned = {
246 let tail = self.tracked.tail.read().await;
247 tail.scan_through::<X>(after, through)
248 };
249
250 match scanned {
251 Ok((found, _)) => found.into_iter().map(Ok).collect::<Vec<_>>().into_iter(),
252 Err(Error::BeyondTail) => match self.tracked.reads.entries(after, through).await {
253 Ok(entries) => entries
254 .into_iter()
255 .filter_map(|entry| {
256 entry.value.select::<X>().map(|value| {
257 Ok(Entry {
258 prepared: None,
259 position: entry.position,
260 metadata: entry.metadata,
261 value,
262 })
263 })
264 })
265 .collect::<Vec<_>>()
266 .into_iter(),
267 Err(refusal) => vec![Err(refusal)].into_iter(),
268 },
269 Err(refusal) => vec![Err(refusal)].into_iter(),
270 }
271 }
272
273 #[tracing::instrument(target = "harmos::state", name = "state.watch", level = "debug", skip_all)]
274 pub fn watch<X>(&self, after: Position) -> impl Stream<Item = Result<Entry<X>, Error>>
275 where
276 X: Clone + Send + 'static,
277 {
278 history::watching(Arc::clone(&self.tracked), after)
279 }
280
281 pub fn applied(&self) -> Position {
282 *self.tracked.applied.borrow()
283 }
284
285 pub(crate) fn progress(&self) -> watch::Receiver<Position> {
286 self.tracked.applied.subscribe()
287 }
288
289 pub(crate) fn seal(&self) {
290 self.tracked.sealing.send_replace(true);
291 }
292
293 pub(crate) fn feed(&self) -> Feed<A> {
294 Feed { tracked: Arc::clone(&self.tracked) }
295 }
296
297 pub fn stored(&self) -> Position {
298 *self.tracked.stored.borrow()
299 }
300
301 #[tracing::instrument(target = "harmos::state", name = "state.wait_until_stored", level = "debug", skip_all)]
302 pub async fn wait_until_stored(&self, at: Position) {
303 let mut stored = self.tracked.stored.clone();
304
305 while *stored.borrow_and_update() < at {
306 if stored.changed().await.is_err() {
307 return;
308 }
309 }
310 }
311
312 #[tracing::instrument(target = "harmos::state", name = "state.undo", level = "debug", skip_all)]
313 pub async fn undo(&self, principal: PrincipalId, focus: Focus<A>) -> Result<Receipt, Error<TransactionError<A>>> {
314 self.revert(principal, focus, Direction::Undo).await
315 }
316
317 #[tracing::instrument(target = "harmos::state", name = "state.redo", level = "debug", skip_all)]
318 pub async fn redo(&self, principal: PrincipalId, focus: Focus<A>) -> Result<Receipt, Error<TransactionError<A>>> {
319 self.revert(principal, focus, Direction::Redo).await
320 }
321
322 #[tracing::instrument(target = "harmos::state", name = "state.reversals", level = "debug", skip_all)]
323 pub async fn reversals(&self, principal: PrincipalId, focus: Focus<A>) -> Result<At<Reversals>, Error> {
324 if self.tracked.poisoned() {
325 return Err(self.unavailable());
326 }
327
328 let (reply, answer) = oneshot::channel();
329 let request = Request::Reversals { principal, focus, reply };
330
331 let waiting = self.writer.reserve().instrument(tracing::debug_span!(target: "harmos::state", "state.admission_wait"));
332 let permit = match waiting.await {
333 Ok(permit) => permit,
334 Err(_) => return Err(self.unavailable()),
335 };
336 let queued = tracing::debug_span!(target: "harmos::state", "state.queue");
337 permit.send((request, crate::supervision::executor::SpanContext::new(tracing::Span::current(), queued)));
338
339 answer.await.map_err(|_| self.unavailable())
340 }
341
342 pub fn as_principal(&self, principal: PrincipalId) -> Self {
343 let mut scoped = self.clone();
344 scoped.context.principal = Some(principal);
345 scoped
346 }
347
348 pub fn caused_by(&self, cause: Position) -> Self {
349 let mut scoped = self.clone();
350 scoped.context.correlation = Some(CorrelationId(cause));
351 scoped.context.causation = Some(CausationId(cause));
352 scoped
353 }
354
355 #[tracing::instrument(target = "harmos::state", name = "state.revert", level = "debug", skip_all)]
356 async fn revert(&self, principal: PrincipalId, focus: Focus<A>, direction: Direction) -> Result<Receipt, Error<TransactionError<A>>> {
357 let (reply, answer) = oneshot::channel();
358 let request = Request::Revert {
359 principal,
360 focus,
361 direction,
362 context: self.context.clone(),
363 reply,
364 };
365
366 let waiting = self.writer.reserve().instrument(tracing::debug_span!(target: "harmos::state", "state.admission_wait"));
367 let permit = match waiting.await {
368 Ok(permit) => permit,
369 Err(_) => return Err(self.unavailable()),
370 };
371 let queued = tracing::debug_span!(target: "harmos::state", "state.queue");
372 permit.send((request, crate::supervision::executor::SpanContext::new(tracing::Span::current(), queued)));
373 match answer.await {
374 Ok(Ok(receipt)) => Ok(receipt),
375 Ok(Err(Refusal::Preparation(refusal))) => Err(preparation(refusal)),
376 Ok(Err(Refusal::Conflict(refusal))) => Err(match direction {
377 Direction::Undo => Error::UndoConflict(refusal),
378 Direction::Redo => Error::RedoConflict(refusal),
379 }),
380 Ok(Err(Refusal::Nothing)) => Err(match direction {
381 Direction::Undo => Error::NothingToUndo,
382 Direction::Redo => Error::NothingToRedo,
383 }),
384 Err(_) => Err(self.unavailable()),
385 }
386 }
387
388 #[tracing::instrument(target = "harmos::state", name = "state.wait_until_captured", level = "debug", skip_all)]
389 pub async fn wait_until_captured(&self, through: Position) -> Result<(), crate::state::CaptureError> {
390 let Some(mut progress) = self.tracked.capture_progress.clone() else {
391 return Ok(());
392 };
393 loop {
394 if (*progress.borrow_and_update())? >= through {
395 return Ok(());
396 }
397 if progress.changed().await.is_err() {
398 return Err(crate::state::CaptureError::Failed);
399 }
400 }
401 }
402
403 pub fn capture_status(&self) -> Result<(), crate::state::CaptureError> {
404 if self.tracked.capture_failed.load(Ordering::Acquire) { Err(crate::state::CaptureError::Failed) } else { Ok(()) }
405 }
406
407 fn unavailable<E>(&self) -> Error<E> {
408 if self.capture_status().is_err() { Error::CaptureFailed } else { Error::Unavailable }
409 }
410
411 async fn request<E>(
412 &self, key: Option<RequestKey>, transaction: crate::operations::Response<A::Transaction>,
413 ) -> Result<Receipt, Error<E>>
414 where
415 E: From<TransactionError<A>>,
416 {
417 let (reply, answer) = oneshot::channel();
418 let request = Request::Commit {
419 transaction,
420 key,
421 context: self.context.clone(),
422 reply,
423 };
424
425 let waiting = self.writer.reserve().instrument(tracing::debug_span!(target: "harmos::state", "state.admission_wait"));
426 let permit = match waiting.await {
427 Ok(permit) => permit,
428 Err(_) => return Err(self.unavailable()),
429 };
430 let queued = tracing::debug_span!(target: "harmos::state", "state.queue");
431 permit.send((request, crate::supervision::executor::SpanContext::new(tracing::Span::current(), queued)));
432 match answer.await {
433 Ok(Ok(receipt)) => Ok(receipt),
434 Ok(Err(Admission::Rejected(refusal))) => Err(Error::Rejected(E::from(refusal))),
435 Ok(Err(Admission::Preparation(refusal))) => Err(preparation(refusal)),
436 Err(_) => Err(self.unavailable()),
437 }
438 }
439}
440
441impl<A: Application> Feed<A> {
442 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
443 #[tracing::instrument(target = "harmos::state", name = "state.shared_page", level = "debug", skip_all)]
444 pub(crate) async fn shared_page(
445 &self, after: Position, through: Position, max_rows: std::num::NonZeroUsize, bytes: impl Fn(&A::Record) -> usize,
446 ) -> Result<history::SharedPage<A>, Error> {
447 self.tracked.tail.read().await.shared_page_sized(after, through, max_rows, bytes)
448 }
449
450 #[tracing::instrument(target = "harmos::state", name = "state.since", level = "debug", skip_all)]
451 pub(crate) async fn since(&self, after: Position) -> Result<Vec<Arc<Entry<Recorded<A>>>>, Error> {
452 let tail = self.tracked.tail.read().await;
453
454 tail.since(after)
455 }
456}
457
458impl<A: Application> Clone for StateManager<A> {
459 fn clone(&self) -> Self {
460 Self {
461 handles: Arc::clone(&self.handles),
462 record_schemas: Arc::clone(&self.record_schemas),
463 writer: self.writer.clone(),
464 context: self.context.clone(),
465 tracked: Arc::clone(&self.tracked),
466 }
467 }
468}
469
470fn preparation<E>(error: PreparationError) -> Error<E> {
475 match error {
476 PreparationError::CaptureFailed => Error::CaptureFailed,
477 error => Error::Preparation(error),
478 }
479}
480
481