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