1mod boot;
13mod lifecycle;
14
15pub use boot::{BootstrapCleanup, BootstrapStatus, Startup};
18
19use std::fmt;
23use std::future::Future;
24use std::pin::Pin;
25use std::sync::atomic::AtomicBool;
26use std::sync::{Arc, Mutex, OnceLock, Weak};
27use std::task::Poll;
28use std::time::Duration;
29use tokio::sync::{mpsc, oneshot, watch};
31use tokio::task::JoinHandle;
32use crate::evidence::{Declaration, Evidence, Vault};
34use crate::services::{Once as DeclaredService, ServiceDefinition};
35use crate::state::dispatch::Dispatch;
36use crate::state::{Feed, Inverting, Origin, ReadRequest, Tail, Undos, replay};
37use crate::storage::attachments::{Attachments, Consumers, Horizon, Payload, Source, declare};
38use crate::storage::consumer::{JournalConsumer, Series, StreamConsumer};
39use crate::streams::{Definition, Holding, Request, StreamManager, Table, Tier};
40use crate::{
41 Application, DefinitionId, Error, operations::LaneSet, operations::OperationPolicy, services::Resident, state::At,
42 state::CaptureLimits, state::DecodeError, state::Entry, state::JournalCapture, state::Kind, state::Position, state::StateManager,
43 storage::Cursor,
44};
45
46const REQUESTS: usize = 64;
49
50const STOP_GRACE: Duration = Duration::from_millis(100);
51
52pub struct Inspection<A: Application> {
59 state: Arc<At<A::State>>,
60 reads: crate::state::Reads<A>,
61}
62
63pub struct Inspector<A: Application> {
64 pub state: Inspection<A>,
65 source: JoinHandle<()>,
66}
67
68pub struct Runtime<A: Application> {
69 pub state: StateManager<A>,
70 pub records: crate::records::RecordManager<A>,
71 pub streams: StreamManager<A>,
72 pub operations: crate::operations::OperationManager,
73
74 #[cfg(all(feature = "plugin", not(target_arch = "wasm32")))]
75 pub plugin: crate::plugin::PluginHost<A>,
76
77 #[cfg(feature = "grpc")]
78 grpc: crate::grpc::GrpcManager,
79
80 pub evidence: Evidence<A>,
81 stopping: Mutex<Stopping<A>>,
82 closing: AtomicBool,
83 storage: Arc<OnceLock<crate::storage::StorageManager<crate::storage::ErasedCatalog>>>,
84 journal_handles: Weak<()>,
85 participants: Participants,
86 notices: watch::Sender<Option<String>>,
87}
88
89pub struct Handle<A: Application> {
90 pub state: StateManager<A>,
91 pub records: crate::records::RecordManager<A>,
92 pub streams: StreamManager<A>,
93 pub operations: crate::operations::OperationManager,
94
95 #[cfg(all(feature = "plugin", not(target_arch = "wasm32")))]
96 pub plugin: crate::plugin::PluginHost<A>,
97
98 #[cfg(feature = "grpc")]
99 grpc: crate::grpc::GrpcManager,
100
101 pub evidence: Evidence<A>,
102 storage: Arc<OnceLock<crate::storage::StorageManager<crate::storage::ErasedCatalog>>>,
103}
104
105pub struct Builder<A: Application> {
106 origin: Origin<A::State>,
107 resources: Arc<A::Resources>,
108 inverses: Inverting<A>,
109 catalog: Dispatch<A, Payload<A>>,
110 definitions: Vec<Definition>,
111 attachments: Attachments<A>,
112 managed_state: Option<crate::storage::ReadLimits>,
113 record_projection: Option<crate::storage::capture::Projection>,
114 reconciliation: Option<boot::Reconciliation<A>>,
115 initializers: Vec<boot::Initializer<A>>,
116 snapshots: Option<crate::state::snapshot::Snapshots<A>>,
117 lanes: LaneSet,
118 operations: Vec<crate::operations::CatalogBinding>,
119 operation_settings: crate::operations::Settings,
120 operation_middleware: Vec<Arc<dyn crate::operations::middleware::Middleware>>,
121 services: Vec<DeclaredService<A>>,
122 evidence: Option<Declaration>,
123
124 #[cfg(feature = "simulation")]
125 simulation: crate::simulation::Configuration<A>,
126
127 #[cfg(feature = "simulation")]
128 live_only: Vec<&'static str>,
129}
130
131#[derive(Debug, thiserror::Error)]
132#[error("{cause}")]
133pub struct Incompatible {
134 #[source]
135 cause: Cause,
136
137 cleanup: Option<boot::Cleanup>,
138}
139
140struct Supervision<A: Application> {
141 consumers: Consumers<A>,
142 feed: Feed<A>,
143 progress: watch::Receiver<Position>,
144 streams: StreamManager<A>,
145 delivery: watch::Receiver<()>,
146 demand: Vec<Holding>,
147 writer: JoinHandle<()>,
148 catalog: Arc<Dispatch<A, Payload<A>>>,
149 inverses: Inverting<A>,
150 requests: mpsc::Receiver<ReadRequest<A>>,
151 served: mpsc::Receiver<Request>,
152 ready: oneshot::Sender<()>,
153}
154
155#[derive(Clone)]
156struct Participants {
157 stopping: watch::Sender<bool>,
158 active: watch::Sender<usize>,
159}
160
161pub struct StopParticipant {
162 stopping: watch::Receiver<bool>,
163 active: watch::Sender<usize>,
164 released: bool,
165}
166
167enum Stopping<A: Application> {
170 Running(Option<JoinHandle<Position>>),
171 Closing {
172 cleanup: Arc<crate::supervision::cleanup::Cleanup<lifecycle::Shutdown<A>>>,
173 outcome: Arc<OnceLock<lifecycle::Outcome>>,
174 progress: watch::Sender<&'static str>,
175 },
176}
177
178#[allow(clippy::type_complexity)]
179pub enum Catalog<A: Application> {
180 Transactions {
181 definitions: Vec<(Kind, DefinitionId, u32)>,
182 decode: fn(&DefinitionId, u32, &[u8]) -> Result<Option<A::Transaction>, DecodeError>,
183 inverses: Option<fn(&A::Transaction, &A::State) -> Option<A::Transaction>>,
184 },
185 Streams(Vec<Definition>),
186 Lanes(LaneSet),
187}
188
189#[derive(Debug, thiserror::Error)]
190enum Cause {
191 #[error("the stored history cannot be replayed by this build: {0}")]
192 Undeclared(DecodeError),
193
194 #[error("the stored history cannot be replayed by this build: {0}")]
195 Unreadable(String),
196
197 #[error("the stream catalog cannot be honored: {0}")]
198 Dishonorable(crate::streams::Refused),
199
200 #[error("the work declarations cannot be honored: {0}")]
201 Work(String),
202
203 #[error("the operation declarations cannot be honored: {0}")]
204 Operations(#[source] crate::Error),
205
206 #[error("runtime initialization failed: {0}")]
207 Initialization(#[source] Box<dyn std::error::Error + Send + Sync>),
208
209 #[error("{source}; bootstrap cleanup also failed: {failure}")]
210 Cleanup {
211 #[source]
212 source: Box<Incompatible>,
213
214 failure: crate::Error,
215 },
216
217 #[error("application storage reconciliation failed: {0}")]
218 Reconciliation(String),
219
220 #[error("the evidence declarations cannot be honored: {0}")]
221 Kept(String),
222
223 #[cfg(feature = "simulation")]
224 #[error("this build cannot be simulated after attaching {0}")]
225 Simulation(String),
226}
227
228enum Turn<A: Application> {
229 Advanced { journal: bool, streams: bool },
230 Read(ReadRequest<A>),
231 Serve(Box<Request>),
232 Stopped,
233}
234
235pub trait Registration<A: Application>: Clone + Send {
238 fn register(self) -> Catalog<A>;
239}
240
241impl<A, S, T> Registration<A> for harmos_domain::state::TransactionDefinitions<S, T>
244where
245 A: Application<State = S, Transaction = T>,
246{
247 fn register(self) -> Catalog<A> {
248 Catalog::Transactions { definitions: self.definitions, decode: self.decode, inverses: Some(self.inverses) }
249 }
250}
251
252impl<A, R> Registration<A> for harmos_domain::state::RecordDefinitions<R>
253where
254 A: Application<Record = R>,
255{
256 fn register(self) -> Catalog<A> {
257 Catalog::Transactions { definitions: self.definitions, decode: |_, _, _| Ok(None), inverses: None }
258 }
259}
260
261impl<A: Application> Inspection<A> {
262 pub async fn read<R>(&self, read: impl FnOnce(&A::State) -> R) -> Result<At<R>, Error> {
263 Ok(At { value: read(&self.state.value), position: self.state.position })
264 }
265
266 pub async fn state_at(&self, origin: Origin<A::State>, at: Position) -> Result<At<A::State>, Error> {
267 self.reads.state(origin, at).await
268 }
269
270 pub async fn query<X>(&self, after: Position) -> impl Iterator<Item = Result<Entry<X>, Error>>
271 where
272 X: Clone + Send + 'static,
273 {
274 self.query_through(after, self.state.position).await
275 }
276
277 pub async fn query_through<X>(&self, after: Position, through: Position) -> impl Iterator<Item = Result<Entry<X>, Error>>
278 where
279 X: Clone + Send + 'static,
280 {
281 if through > self.state.position {
282 return vec![Err(Error::FrontierAhead)].into_iter();
283 }
284 if after >= through {
285 return Vec::new().into_iter();
286 }
287
288 match self.reads.entries(after, through).await {
289 Ok(entries) => entries
290 .into_iter()
291 .filter_map(|entry| {
292 entry
293 .value
294 .select::<X>()
295 .map(|value| Ok(Entry { prepared: None, position: entry.position, metadata: entry.metadata, value }))
296 })
297 .collect::<Vec<_>>()
298 .into_iter(),
299 Err(refusal) => vec![Err(refusal)].into_iter(),
300 }
301 }
302
303 pub fn applied(&self) -> Position {
304 self.state.position
305 }
306
307 pub fn stored(&self) -> Position {
308 self.state.position
309 }
310}
311
312impl<A: Application> Drop for Inspector<A> {
313 fn drop(&mut self) {
314 self.source.abort();
315 }
316}
317
318impl<A: Application> Clone for Handle<A> {
321 fn clone(&self) -> Self {
322 Self {
323 state: self.state.clone(),
324 records: self.records.clone(),
325 streams: self.streams.clone(),
326 operations: self.operations.clone(),
327 #[cfg(all(feature = "plugin", not(target_arch = "wasm32")))]
328 plugin: self.plugin.clone(),
329 #[cfg(feature = "grpc")]
330 grpc: self.grpc.clone(),
331 evidence: self.evidence.clone(),
332 storage: self.storage.clone(),
333 }
334 }
335}
336
337impl<A: Application> Handle<A> {
338 #[cfg(feature = "grpc")]
339 pub fn grpc(&self) -> &crate::grpc::GrpcManager {
340 &self.grpc
341 }
342}
343
344impl<A: Application> Runtime<A> {
345 #[cfg(feature = "grpc")]
346 pub fn grpc(&self) -> &crate::grpc::GrpcManager {
347 &self.grpc
348 }
349
350 pub fn builder(origin: impl Into<Origin<A::State>>, resources: impl Into<Arc<A::Resources>>) -> Builder<A> {
351 Builder {
352 origin: origin.into(),
353 resources: resources.into(),
354 inverses: none::<A>,
355 catalog: Dispatch::default(),
356 definitions: Vec::new(),
357 attachments: Attachments::new(),
358 managed_state: None,
359 record_projection: None,
360 reconciliation: None,
361 initializers: Vec::new(),
362 snapshots: None,
363 lanes: LaneSet::default(),
364 operations: Vec::new(),
365 operation_settings: crate::operations::Settings::default(),
366 operation_middleware: Vec::new(),
367 services: Vec::new(),
368 evidence: None,
369 #[cfg(feature = "simulation")]
370 simulation: crate::simulation::Configuration::new(),
371 #[cfg(feature = "simulation")]
372 live_only: Vec::new(),
373 }
374 }
375
376 pub async fn inspect(origin: impl Into<Origin<A::State>>, source: impl Source<A>) -> Result<Inspector<A>, Incompatible> {
377 let mut attachments = Attachments::new();
378 attachments.recover(source);
379 let mut consumers = attachments.supervised();
380 let recovery = consumers.load().await?;
381 let origin = origin.into();
382 let anchor = origin.position;
383 let mut state = At { value: origin.value, position: anchor };
384 let entries = recovery.entries.into_iter().filter(|entry| entry.position > anchor).collect();
385 replay(None, entries, &mut state, &mut Undos::default(), none::<A>, 0).map_err(Incompatible::of)?;
386 state.position = state.position.max(recovery.frontier).max(anchor);
387
388 let (reads, requests) = crate::state::Reads::channel(REQUESTS);
389 let source = tokio::spawn(inspect_reads(consumers, requests));
390
391 Ok(Inspector { state: Inspection { state: Arc::new(state), reads }, source })
392 }
393
394 pub fn stop_participant(&self) -> StopParticipant {
395 self.participants.register()
396 }
397
398 pub fn notices(&self) -> watch::Receiver<Option<String>> {
399 self.notices.subscribe()
400 }
401
402 pub fn handle(&self) -> Handle<A> {
403 Handle {
404 state: self.state.clone(),
405 records: self.records.clone(),
406 streams: self.streams.clone(),
407 operations: self.operations.clone(),
408 #[cfg(all(feature = "plugin", not(target_arch = "wasm32")))]
409 plugin: self.plugin.clone(),
410 #[cfg(feature = "grpc")]
411 grpc: self.grpc.clone(),
412 evidence: self.evidence.clone(),
413 storage: self.storage.clone(),
414 }
415 }
416}
417
418impl<A: Application> Registration<A> for Vec<Definition> {
419 fn register(self) -> Catalog<A> {
420 Catalog::Streams(self)
421 }
422}
423
424impl<A: Application, const N: usize> Registration<A> for [Definition; N] {
425 fn register(self) -> Catalog<A> {
426 Catalog::Streams(self.into())
427 }
428}
429
430impl<A: Application> Registration<A> for LaneSet {
431 fn register(self) -> Catalog<A> {
432 Catalog::Lanes(self)
433 }
434}
435
436impl<A: Application> Builder<A> {
437 pub fn register<C: Registration<A> + 'static>(mut self, catalog: C) -> Self {
438 #[cfg(feature = "simulation")]
439 self.simulation.register(catalog.clone());
440
441 match catalog.register() {
442 Catalog::Transactions { definitions, decode, inverses } => {
443 if let Some(inverses) = inverses {
444 self.inverses = inverses;
445 }
446 for (kind, id, version) in definitions {
447 declare(&mut self.catalog, kind, id, version, decode);
448 }
449 }
450 Catalog::Streams(definitions) => self.definitions.extend(definitions),
451 Catalog::Lanes(declared) => self.lanes.extend(declared),
452 }
453 self
454 }
455
456 pub fn operation<T>(self, owner: impl Into<String>, lane: impl crate::operations::Lane, resources: Arc<T::Resources>) -> Self
457 where
458 T: for<'ctx> crate::operations::Operation<Context<'ctx> = crate::operations::Context<'ctx>>,
459 {
460 let binding = crate::operations::CatalogBinding::operation::<T>(owner.into(), lane, resources);
461 self.bind_operations(binding)
462 }
463
464 pub fn operations<C, R>(self, owner: impl Into<String>, lane: impl crate::operations::Lane, resources: Arc<R>) -> Self
465 where
466 C: crate::catalog::Catalog<crate::operations::AuthoredDeclarations>
467 + crate::catalog::Catalog<crate::operations::Implementations<R>>,
468 R: Send + Sync + 'static,
469 {
470 let binding = crate::operations::CatalogBinding::local::<C, R>(owner.into(), lane, resources);
471 self.bind_operations(binding)
472 }
473
474 pub fn wire_operations<C, R>(self, owner: impl Into<String>, lane: impl crate::operations::Lane, resources: Arc<R>) -> Self
475 where
476 C: crate::catalog::Catalog<crate::operations::AuthoredDeclarations>
477 + crate::catalog::Catalog<crate::operations::WireImplementations<R>>,
478 R: Send + Sync + 'static,
479 {
480 let binding = crate::operations::CatalogBinding::wire::<C, R>(owner.into(), lane, resources);
481 self.bind_operations(binding)
482 }
483
484 pub fn operation_middleware(mut self, middleware: Arc<dyn crate::operations::middleware::Middleware>) -> Self {
485 #[cfg(feature = "simulation")]
486 self.simulation.operation_middleware(middleware.clone());
487 self.operation_middleware.push(middleware);
488 self
489 }
490
491 pub fn operation_settings(mut self, settings: crate::operations::Settings) -> Self {
492 #[cfg(feature = "simulation")]
493 self.simulation.operation_settings(settings);
494 self.operation_settings = settings;
495 self
496 }
497
498 pub(crate) fn bind_operations(mut self, binding: crate::operations::CatalogBinding) -> Self {
499 #[cfg(feature = "simulation")]
500 self.simulation.operations(binding.clone());
501 self.operations.push(binding);
502 self
503 }
504
505 pub fn service<R>(self, name: impl Into<String>, resident: R) -> Self
506 where
507 R: Resident<A>,
508 {
509 self.service_with(name, crate::services::POLICY, resident)
510 }
511
512 pub fn service_with<R>(mut self, name: impl Into<String>, policy: OperationPolicy, resident: R) -> Self
513 where
514 R: Resident<A>,
515 {
516 #[cfg(feature = "simulation")]
517 self.live_only.push("resident services");
518
519 let definition = ServiceDefinition { id: name.into(), policy };
520 self.services.push(DeclaredService::new(definition, resident));
521 self
522 }
523
524 pub fn evidence(mut self, vault: impl Vault, kinds: impl IntoIterator<Item = (DefinitionId, u32)>) -> Self {
525 #[cfg(feature = "simulation")]
526 self.live_only.push("an evidence vault");
527
528 self.evidence = Some(Declaration::new(vault, kinds));
529 self
530 }
531
532 pub fn attach(mut self, consumer: impl JournalConsumer<A>) -> Self {
533 #[cfg(feature = "simulation")]
534 self.live_only.push("an attached fold");
535
536 self.attachments.attach(consumer);
537 self
538 }
539
540 pub fn attach_streams(mut self, consumer: impl StreamConsumer<A>) -> Self {
541 #[cfg(feature = "simulation")]
542 self.live_only.push("an attached stream fold");
543
544 self.attachments.attach_streams(consumer);
545 self
546 }
547
548 pub fn series(mut self, series: impl Series<A>) -> Self {
549 #[cfg(feature = "simulation")]
550 self.live_only.push("a series copy");
551
552 self.attachments.series(series);
553 self
554 }
555
556 pub fn recover(mut self, source: impl Source<A>) -> Self {
557 #[cfg(feature = "simulation")]
558 self.live_only.push("a recovery source");
559
560 self.attachments.recover(source);
561 self
562 }
563
564 pub fn snapshot(mut self, snapshot: impl JournalConsumer<A>) -> Self {
565 #[cfg(feature = "simulation")]
566 self.live_only.push("a snapshot fold");
567
568 self.attachments.snapshot(snapshot);
569 self
570 }
571
572 pub fn require_capture(mut self, limits: CaptureLimits, consumer: impl JournalCapture<A>) -> Result<Self, crate::state::CaptureError> {
573 #[cfg(feature = "simulation")]
574 self.live_only.push("required capture");
575
576 self.attachments.require_capture(limits, consumer)?;
577
578 Ok(self)
579 }
580
581 pub fn retain(mut self, horizon: Horizon) -> Self {
582 self.attachments.retain(horizon);
583 self
584 }
585
586 pub fn tail(mut self, entries: usize) -> Self {
587 self.attachments.tail(entries);
588 self
589 }
590
591 #[cfg(feature = "simulation")]
592 pub fn simulated_service<R>(mut self, name: impl Into<String>, resident: R) -> Self
593 where
594 R: Resident<A> + Clone,
595 {
596 self.simulation.service(name, resident);
597 self
598 }
599
600 #[cfg(feature = "simulation")]
601 pub fn simulated_service_with<R>(mut self, name: impl Into<String>, policy: OperationPolicy, resident: R) -> Self
602 where
603 R: Resident<A> + Clone,
604 {
605 self.simulation.service_with(name, policy, resident);
606 self
607 }
608
609 #[cfg(feature = "simulation")]
610 pub fn simulated_evidence(mut self, vault: impl Vault + Clone, kinds: impl IntoIterator<Item = (DefinitionId, u32)>) -> Self {
611 self.simulation.evidence(vault, kinds);
612 self
613 }
614
615 #[cfg(feature = "simulation")]
616 pub fn entropy_seed(mut self, seed: u64) -> Self {
617 self.simulation.entropy_seed(seed);
618 self
619 }
620
621 #[cfg(feature = "simulation")]
622 pub fn simulate(self) -> Result<crate::simulation::SimulatedRuntime<A>, Incompatible>
623 where
624 A::State: Clone + fmt::Debug + PartialEq,
625 A::Transaction: Clone + fmt::Debug,
626 A::Record: Clone + fmt::Debug,
627 {
628 if !self.live_only.is_empty() {
629 return Err(Incompatible::simulation(self.live_only.join(", ")));
630 }
631
632 self.simulation.simulate(self.origin, self.resources)
633 }
634
635 #[tracing::instrument(level = "debug", skip_all)]
636 pub fn runtime(self) -> Startup<A> {
637 Startup::new(self, None)
638 }
639
640 pub fn managed_state(mut self, limits: crate::storage::ReadLimits) -> Self {
641 self.managed_state = Some(limits);
642 #[cfg(feature = "simulation")]
643 self.live_only.push("managed storage recovery");
644 self
645 }
646
647 pub fn store_records(mut self, limits: crate::storage::ReadLimits, projection: impl crate::storage::RecordProjection) -> Self {
648 self.record_projection = Some(crate::storage::capture::Projection { limits, handler: Box::new(projection) });
649 #[cfg(feature = "simulation")]
650 self.live_only.push("managed primary record capture");
651 self
652 }
653
654 pub fn managed_snapshots(mut self, policy: crate::state::SnapshotPolicy) -> Self
655 where
656 A::State: serde::Serialize + serde::de::DeserializeOwned,
657 {
658 self.snapshots = Some(crate::state::snapshot::Snapshots::new(policy));
659 #[cfg(feature = "simulation")]
660 self.live_only.push("managed state snapshots");
661 self
662 }
663
664 pub fn reconcile<F, Fut>(mut self, reconcile: F) -> Self
665 where
666 F: FnOnce(StateManager<A>, crate::storage::StorageManager<crate::storage::ErasedCatalog>) -> Fut + Send + 'static,
667 Fut: Future<Output = crate::Result<()>> + Send + 'static,
668 {
669 self.reconciliation = Some(Box::new(move |journal, storage| Box::pin(reconcile(journal, storage))));
670 #[cfg(feature = "simulation")]
671 self.live_only.push("saved application data reconciliation");
672 self
673 }
674
675 pub fn initialize<F, Fut, E>(mut self, timeout: Duration, initialize: F) -> Self
676 where
677 F: Fn(Handle<A>) -> Fut + Clone + Send + Sync + 'static,
678 Fut: Future<Output = std::result::Result<(), E>> + Send + 'static,
679 E: std::error::Error + Send + Sync + 'static,
680 {
681 #[cfg(feature = "simulation")]
682 self.simulation.initialize(timeout, initialize.clone());
683 self.initializers.push(boot::Initializer::new(timeout, initialize));
684 self
685 }
686
687 pub fn runtime_with_storage<C>(self, storage: &crate::storage::StorageManager<C>) -> Startup<A> {
688 Startup::new(self, Some(storage.erased()))
689 }
690
691 async fn boot(
692 self, storage: Option<&crate::storage::StorageManager<crate::storage::ErasedCatalog>>, cleanup: BootstrapCleanup,
693 ) -> Result<Runtime<A>, Incompatible> {
694 if self
695 .initializers
696 .iter()
697 .any(|initializer| initializer.timeout.is_zero() || tokio::time::Instant::now().checked_add(initializer.timeout).is_none())
698 {
699 let source: crate::Error = Error::InvalidInput {
700 field: "runtime initialization timeout",
701 reason: "requires a positive representable finite duration",
702 };
703 return Err(Incompatible::initialization(Box::new(source)));
704 }
705 if self.snapshots.is_some() && self.managed_state.is_none() {
706 return Err(Incompatible::unreadable("managed snapshots require managed state"));
707 }
708 if self.reconciliation.is_some() && self.managed_state.is_none() {
709 return Err(Incompatible::reconciliation("reconciliation requires managed state"));
710 }
711 if self.record_projection.is_some() && self.managed_state.is_none() {
712 return Err(Incompatible::unreadable("primary record capture requires managed state"));
713 }
714 if self.managed_state.is_some() && (storage.is_none() || self.attachments.has_recovery_owner()) {
715 return Err(Incompatible::unreadable("managed state requires storage and exclusive recovery ownership"));
716 }
717 let mut boot = boot::Boot::new(storage, cleanup.clone()).map_err(Incompatible::work)?;
718 if let Err(error) = self.boot_owned(&mut boot).await {
719 return match boot.close().await {
720 Ok(()) => Err(error),
721 Err(failure) => {
722 let failure = Arc::new(failure);
723 let retained = boot.retain_cleanup();
724 cleanup.failed(retained.clone(), Arc::clone(&failure));
725 Err(Incompatible {
726 cause: Cause::Cleanup {
727 source: Box::new(error),
728 failure: Error::TaskFailed { domain: "bootstrap cleanup", source: failure },
729 },
730 cleanup: retained,
731 })
732 }
733 };
734 }
735 Ok(boot.finish())
736 }
737
738 async fn boot_owned(self, boot: &mut boot::Boot<A>) -> Result<(), Incompatible> {
739 boot.acquire().await.map_err(Incompatible::work)?;
740 let Self {
741 origin,
742 resources,
743 inverses,
744 catalog,
745 definitions,
746 mut attachments,
747 managed_state,
748 record_projection,
749 reconciliation,
750 initializers,
751 snapshots,
752 lanes,
753 operations: bindings,
754 operation_settings,
755 operation_middleware,
756 services,
757 evidence,
758 ..
759 } = self;
760 let tail = attachments.tail_window()?;
761 let mut consumers = attachments.supervised();
762 let recovery = consumers.load().await?;
763 let (resume, selections) = consumers.recordings().fold((Cursor::ORIGIN, Vec::new()), |(resume, mut selections), recording| {
764 selections.push(recording.selections());
765
766 (resume.max(recording.resume()), selections)
767 });
768 let anchor = origin.position;
769 let mut state = At { value: origin.value, position: anchor };
770 let mut undos = Undos::default();
771 let entries = recovery.entries.into_iter().filter(|entry| entry.position > anchor).collect();
772 let mut recovered = replay(Some(&catalog), entries, &mut state, &mut undos, inverses, tail).map_err(Incompatible::of)?;
773 let stored = recovery.encoded.into_iter().filter(|(position, _)| *position > anchor).collect();
774 state.position = state.position.max(recovery.frontier).max(anchor);
775 let mut managed_durable = None;
776 if let Some(limits) = managed_state {
777 let storage = boot.storage.as_ref().expect("managed state requires storage");
778 let loaded = StateManager::recover(storage, state, &catalog, inverses, tail, limits, snapshots.as_ref()).await?;
779 state = loaded.state;
780 undos = loaded.undos;
781 recovered = loaded.tail;
782 managed_durable = Some(loaded.durable);
783 attachments.capture = Some(crate::state::capture::Declaration::Managed {
784 storage: storage.clone(),
785 receipts: loaded.receipts,
786 projection: record_projection,
787 snapshots,
788 });
789 }
790 let windows = Table::resuming(definitions, state.position, resume).map_err(Incompatible::dishonored)?;
791 let demand = windows.hold(selections).map_err(Incompatible::dishonored)?;
792 let replayed = recovered.clone();
793 consumers.redelivering(recovered.clone(), stored, state.position);
794
795 let durability = managed_durable.or_else(|| consumers.durability(state.position));
796 let tail = Tail::recovered(state.position, tail, recovered);
797 let catalog = Arc::new(catalog);
798 let (journal, writer, requests) = StateManager::start(
799 state,
800 inverses,
801 undos,
802 REQUESTS,
803 tail,
804 durability,
805 Arc::clone(&catalog),
806 attachments.capture.take(),
807 boot.storage.clone(),
808 crate::state::Timestamp::now,
809 );
810 let journal_handles = journal.handle_probe();
811 let records = crate::records::RecordManager::new(journal.clone());
812 boot.writer = Some(writer);
813 boot.state = Some(journal.clone());
814 let evidence = Evidence::wired(journal.clone(), evidence, &replayed).map_err(Incompatible::kept)?;
818 let (tier, served) = Tier::channel(REQUESTS);
822 let streams = StreamManager::wired(windows, journal.progress(), tier);
823 let delivery = streams.delivery();
824 operation_settings.validate().map_err(|source| Incompatible { cause: Cause::Operations(source), cleanup: None })?;
825 let lanes = crate::operations::lane::Lanes::freeze(lanes).map_err(Incompatible::work)?;
826 let storage = Arc::new(boot.storage.clone().map(OnceLock::from).unwrap_or_default());
827 let handles = crate::services::Handles {
828 storage: storage.clone(),
829 state: journal.clone(),
830 records: records.clone(),
831 streams: streams.clone(),
832 evidence: evidence.clone(),
833 };
834 let instances = crate::operations::instances::Instances::start(lanes, Box::new(handles.clone()), resources, &operation_settings);
835 let mut reserved = std::collections::BTreeSet::new();
836 for service in &services {
837 crate::services::validate(&service.definition()).map_err(Incompatible::work)?;
838 if !reserved.insert(service.id().to_owned()) {
839 let duplicate: crate::Error = crate::Error::AlreadyRegistered { domain: "service", name: service.id().to_owned() };
840 return Err(Incompatible::work(duplicate));
841 }
842 }
843 let operations = crate::operations::OperationManager::new(instances, &operation_settings, reserved, boot.identity.clone());
844 for middleware in operation_middleware {
845 operations.register_middleware(middleware).map_err(Incompatible::work)?;
846 }
847 for binding in bindings {
848 binding.bind(&operations).map_err(|source| Incompatible { cause: Cause::Operations(source), cleanup: None })?;
849 }
850 if let Some(storage) = &boot.storage {
851 storage.start_on::<A>(&operations).await.map_err(Incompatible::work)?;
852 }
853 let (ready, booted) = oneshot::channel();
854 let supervisor = tokio::spawn(supervise(Supervision {
855 consumers,
856 feed: journal.feed(),
857 progress: journal.progress(),
858 streams: streams.clone(),
859 delivery,
860 demand,
861 writer: boot.writer.take().expect("boot owns writer"),
862 catalog,
863 inverses,
864 requests,
865 served,
866 ready,
867 }));
868 #[cfg(feature = "grpc")]
869 let grpc = crate::grpc::GrpcManager::new::<A>(&operations);
870 #[cfg(all(feature = "plugin", not(target_arch = "wasm32")))]
871 let plugin = crate::plugin::PluginHost::new(
872 operations.clone(),
873 #[cfg(feature = "plugin-sidecar")]
874 handles,
875 )
876 .map_err(Incompatible::work)?;
877 boot.runtime = Some(Runtime {
878 state: journal,
879 records,
880 streams,
881 operations,
882 #[cfg(all(feature = "plugin", not(target_arch = "wasm32")))]
883 plugin,
884 #[cfg(feature = "grpc")]
885 grpc,
886 evidence,
887 stopping: Mutex::new(Stopping::Running(Some(supervisor))),
888 closing: AtomicBool::new(false),
889 storage,
890 journal_handles,
891 participants: Participants::new(),
892 notices: watch::channel(None).0,
893 });
894 boot.state.take();
895 booted.await.map_err(Incompatible::work)?;
896 if let Some(reconcile) = reconciliation {
897 let runtime = boot.runtime.as_ref().expect("boot owns runtime");
898 let storage = runtime.storage().expect("reconciliation requires storage");
899 reconcile(runtime.state.clone(), storage.clone()).await.map_err(Incompatible::reconciliation)?;
900 storage.flush().await.map_err(Incompatible::reconciliation)?;
901 storage.flush_recovery().await.map_err(Incompatible::reconciliation)?;
902 }
903 let operations = &boot.runtime.as_ref().expect("runtime is owned during boot").operations;
904 for service in services {
905 let lease = operations.start_service(service, false, true).map_err(Incompatible::work)?;
906 operations.hold_resident(lease);
907 }
908 for initializer in initializers {
909 let handle = boot.runtime.as_ref().expect("runtime is owned during initialization").handle();
910 let initialize = (initializer.initialize)(handle);
911 match tokio::time::timeout(initializer.timeout, initialize).await {
912 Ok(result) => result.map_err(Incompatible::initialization)?,
913 Err(source) => return Err(Incompatible::initialization(Box::new(source))),
914 }
915 }
916 Ok(())
917 }
918}
919
920impl Incompatible {
921 fn initialization(source: Box<dyn std::error::Error + Send + Sync>) -> Self {
922 Self { cause: Cause::Initialization(source), cleanup: None }
923 }
924
925 pub fn cleanup_error(&self) -> Option<&crate::Error> {
926 match (&self.cleanup, &self.cause) {
927 (Some(_), Cause::Cleanup { failure, .. }) => Some(failure),
928 _ => None,
929 }
930 }
931
932 pub async fn retry_cleanup(&mut self) -> crate::Result<()> {
933 if let Some(cleanup) = &self.cleanup {
934 cleanup.close().await?;
935 self.cleanup.take();
936 }
937 Ok(())
938 }
939
940 fn reconciliation(cause: impl fmt::Display) -> Self {
941 Self { cause: Cause::Reconciliation(cause.to_string()), cleanup: None }
942 }
943
944 pub fn unreadable(cause: impl fmt::Display) -> Self {
945 Self { cause: Cause::Unreadable(cause.to_string()), cleanup: None }
946 }
947
948 fn of(cause: DecodeError) -> Self {
949 Self { cause: Cause::Undeclared(cause), cleanup: None }
950 }
951
952 pub(crate) fn dishonored(cause: crate::streams::Refused) -> Self {
953 Self { cause: Cause::Dishonorable(cause), cleanup: None }
954 }
955
956 pub(crate) fn work(cause: impl fmt::Display) -> Self {
957 Self { cause: Cause::Work(cause.to_string()), cleanup: None }
958 }
959
960 #[cfg(feature = "simulation")]
961 fn simulation(live_only: String) -> Self {
962 Self { cause: Cause::Simulation(live_only), cleanup: None }
963 }
964
965 fn kept(cause: String) -> Self {
966 Self { cause: Cause::Kept(cause), cleanup: None }
967 }
968}
969
970#[cfg(feature = "plugin-sidecar")]
971impl From<harmos_sidecar::ExecutableError> for Incompatible {
972 fn from(error: harmos_sidecar::ExecutableError) -> Self {
973 Self::work(error)
974 }
975}
976
977impl Participants {
978 fn new() -> Self {
979 Self { stopping: watch::channel(false).0, active: watch::channel(0).0 }
980 }
981
982 fn register(&self) -> StopParticipant {
983 self.active.send_modify(|active| *active += 1);
984
985 StopParticipant { stopping: self.stopping.subscribe(), active: self.active.clone(), released: false }
986 }
987
988 async fn stop(&self) {
989 self.stopping.send_replace(true);
990 let mut active = self.active.subscribe();
991 while *active.borrow_and_update() != 0 {
992 if active.changed().await.is_err() {
993 break;
994 }
995 }
996 }
997}
998
999impl StopParticipant {
1000 pub fn is_stopping(&self) -> bool {
1001 *self.stopping.borrow()
1002 }
1003
1004 pub async fn stopping(&mut self) {
1005 while !*self.stopping.borrow_and_update() {
1006 if self.stopping.changed().await.is_err() {
1007 break;
1008 }
1009 }
1010 }
1011
1012 pub fn finish(mut self) {
1013 self.release();
1014 }
1015
1016 fn release(&mut self) {
1017 if !self.released {
1018 self.active.send_modify(|active| *active = active.saturating_sub(1));
1019 self.released = true;
1020 }
1021 }
1022}
1023
1024impl Drop for StopParticipant {
1025 fn drop(&mut self) {
1026 self.release();
1027 }
1028}
1029
1030fn none<A: Application>(_change: &A::Transaction, _before: &A::State) -> Option<A::Transaction> {
1035 None
1036}
1037
1038async fn supervise<A: Application>(supervision: Supervision<A>) -> Position {
1039 let Supervision {
1040 mut consumers,
1041 feed,
1042 mut progress,
1043 streams,
1044 mut delivery,
1045 demand: _demand,
1046 mut writer,
1047 catalog,
1048 inverses,
1049 mut requests,
1050 mut served,
1051 ready,
1052 } = supervision;
1053 let booted = *progress.borrow_and_update();
1057 consumers.drain(&feed, booted).await;
1058 deliver(&mut consumers, &streams).await;
1059 stand_windows(&streams, &mut consumers, booted).await;
1060 let _ = ready.send(());
1061
1062 loop {
1063 match turn(&mut progress, &mut delivery, &mut requests, &mut served, &mut writer).await {
1064 Turn::Advanced { journal, streams: rows } => {
1065 if journal {
1066 let applied = *progress.borrow_and_update();
1067 consumers.drain(&feed, applied).await;
1068 }
1069 if rows {
1070 delivery.borrow_and_update();
1071 deliver(&mut consumers, &streams).await;
1072 }
1073 }
1074 Turn::Read(request) => answer(request, &mut consumers, Some(&catalog), inverses).await,
1075 Turn::Serve(request) => serve(*request, &mut consumers).await,
1076 Turn::Stopped => break,
1077 }
1078 }
1079
1080 let applied = *progress.borrow();
1081 consumers.drain(&feed, applied).await;
1082 deliver(&mut consumers, &streams).await;
1083 consumers.flush().await;
1084
1085 consumers.settle(applied).await
1088}
1089
1090async fn deliver<A: Application>(consumers: &mut Consumers<A>, streams: &StreamManager<A>) {
1091 for recording in consumers.recordings() {
1092 let resume = recording.resume();
1093 let batch = streams.batch(resume);
1094
1095 if batch.reaches() > resume {
1096 recording.drain(batch).await;
1097 }
1098 }
1099}
1100
1101async fn stand_windows<A: Application>(streams: &StreamManager<A>, consumers: &mut Consumers<A>, booted: Position) {
1102 for wired in streams.declared() {
1103 let durable = consumers.sealed(wired.definition.clone(), &[]).await;
1104
1105 if durable > Position::ORIGIN && durable <= booted {
1106 wired.window.stands_on(durable);
1107 }
1108 }
1109}
1110
1111async fn turn<A: Application>(
1112 progress: &mut watch::Receiver<Position>, delivery: &mut watch::Receiver<()>, requests: &mut mpsc::Receiver<ReadRequest<A>>,
1113 served: &mut mpsc::Receiver<Request>, writer: &mut JoinHandle<()>,
1114) -> Turn<A> {
1115 let mut advanced = Box::pin(progress.changed());
1116 let mut published = Box::pin(delivery.changed());
1117
1118 std::future::poll_fn(|context| {
1119 if Pin::new(&mut *writer).poll(context).is_ready() {
1120 return Poll::Ready(Turn::Stopped);
1121 }
1122 let journal = match advanced.as_mut().poll(context) {
1123 Poll::Ready(Ok(())) => true,
1124 Poll::Ready(Err(_)) => return Poll::Ready(Turn::Stopped),
1125 Poll::Pending => false,
1126 };
1127 let streams = match published.as_mut().poll(context) {
1128 Poll::Ready(Ok(())) => true,
1129 Poll::Ready(Err(_)) => return Poll::Ready(Turn::Stopped),
1130 Poll::Pending => false,
1131 };
1132
1133 if journal || streams {
1134 Poll::Ready(Turn::Advanced { journal, streams })
1135 } else {
1136 match requests.poll_recv(context) {
1137 Poll::Ready(Some(request)) => Poll::Ready(Turn::Read(request)),
1138 Poll::Ready(None) | Poll::Pending => match served.poll_recv(context) {
1139 Poll::Ready(Some(request)) => Poll::Ready(Turn::Serve(Box::new(request))),
1140 Poll::Ready(None) | Poll::Pending => Poll::Pending,
1141 },
1142 }
1143 }
1144 })
1145 .await
1146}
1147
1148async fn answer<A: Application>(
1149 request: ReadRequest<A>, consumers: &mut Consumers<A>, catalog: Option<&Dispatch<A, Payload<A>>>, inverses: Inverting<A>,
1150) {
1151 match request {
1152 ReadRequest::Entries { after, through, reply } => {
1153 let answer = consumers.read(after, through).await.map(|recovery| {
1154 recovery
1155 .entries
1156 .into_iter()
1157 .map(|envelope| Entry {
1158 prepared: envelope.payload.prepared.clone(),
1159 position: envelope.position,
1160 metadata: envelope.metadata,
1161 value: envelope.payload.value,
1162 })
1163 .collect()
1164 });
1165 let _ = reply.send(answer);
1166 }
1167 ReadRequest::State { origin, at, reply } => {
1168 let answer = async {
1169 let recovery = consumers.read(origin.position, at).await?;
1170 if recovery
1171 .commits
1172 .iter()
1173 .copied()
1174 .chain(recovery.entries.iter().filter_map(|entry| entry.metadata.commit))
1175 .any(|(first, end)| (first <= at && at < end) || (first <= origin.position && origin.position < end))
1176 {
1177 return Err(crate::Error::IncompleteCommit);
1178 }
1179 if recovery.frontier < at {
1180 return Err(crate::Error::BeyondTail);
1181 }
1182 let mut state = At { value: origin.value, position: origin.position };
1183 replay(catalog, recovery.entries, &mut state, &mut Undos::default(), inverses, 0).map_err(|_| crate::Error::Unavailable)?;
1184 state.position = at;
1185
1186 Ok(state)
1187 }
1188 .await;
1189 let _ = reply.send(answer);
1190 }
1191 }
1192}
1193
1194async fn serve<A: Application>(request: Request, consumers: &mut Consumers<A>) {
1195 match request {
1196 Request::Reader { stream, track, start, end, limits, permit, reply } => {
1197 if !reply.is_closed() {
1198 let answer = consumers.row_reader(stream, &track, start, end, limits).await;
1199 let _ = reply.send(crate::streams::Opened { source: answer, permit });
1200 }
1201 }
1202 Request::Rows { stream, track, start, end, reply } => {
1203 let answer = consumers.served(stream, &track, start, end).await;
1204 let _ = reply.send(answer);
1205 }
1206 Request::Buckets { stream, track, start, end, budget, reply } => {
1207 let answer = consumers.surveyed(stream, &track, start, end, budget).await;
1208 let _ = reply.send(answer);
1209 }
1210 Request::Aggregate { stream, track, start, end, kind, reply } => {
1211 let answer = consumers.aggregated(stream, &track, start, end, kind).await;
1212 let _ = reply.send(answer);
1213 }
1214 Request::Seal { stream, track, reply } => {
1215 let _ = reply.send(consumers.sealed(stream, &track).await);
1216 }
1217 }
1218}
1219
1220async fn inspect_reads<A: Application>(mut consumers: Consumers<A>, mut requests: mpsc::Receiver<ReadRequest<A>>) {
1221 while let Some(request) = requests.recv().await {
1222 answer(request, &mut consumers, None, none::<A>).await;
1223 }
1224}
1225
1226