1use std::any::{Any, TypeId};
25use std::collections::{BTreeMap, BTreeSet};
26use std::future::Future;
27use std::pin::Pin;
28use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
29use std::sync::{Arc, Mutex, Weak};
30use tokio::sync::watch;
32use super::caller::Identity;
34use super::instances::Instances;
35use super::{OperationGeneration, OperationInstanceId, OperationStatus};
36use crate::observers::ObserverDefinition;
37use crate::services::{ServiceDefinition, ServiceStatus};
38use crate::supervision::{self, Task, instance::Instance, instance::Phase};
39use crate::{Error, Result};
40
41static NEXT_SCOPE: AtomicU64 = AtomicU64::new(1);
44
45const ROOT: ScopeId = ScopeId(0);
46
47pub(crate) type Outcome = std::result::Result<(), Arc<Error>>;
52pub(crate) type Completion = Pin<Box<dyn Future<Output = Outcome> + Send>>;
53pub(crate) type Resources = Arc<dyn Any + Send + Sync>;
54
55pub struct Activation;
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
62pub struct ScopeId(u64);
63
64#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
66pub struct Key {
67 pub definition: String,
68 pub scope: ScopeId,
69}
70
71#[derive(Clone, Debug)]
72pub struct Snapshot {
73 pub instance: OperationInstanceId,
74 pub kind: Kind,
75 pub leases: usize,
76 pub lifecycle: OperationStatus,
77 pub health: Option<ServiceStatus>,
79 pub failure: Option<Arc<Error>>,
80}
81
82#[derive(Clone)]
83pub(crate) struct Registry {
84 shared: Arc<Shared>,
85}
86
87pub(crate) struct Shared {
88 identity: Arc<()>,
89 root_resources: Arc<()>,
90 index: Weak<Instances>,
91 root: Arc<Scope>,
92 state: Mutex<State>,
93 statuses: watch::Sender<BTreeMap<Key, Snapshot>>,
94 assembly: Mutex<Vec<Lease>>,
95}
96
97pub(crate) struct Scope {
98 owner: Weak<()>,
99 id: ScopeId,
100 address: usize,
101 resources: Resources,
102 closed: AtomicBool,
103 retirement: Mutex<Option<Outcome>>,
104 detached: Mutex<Vec<Lease>>,
105}
106
107pub(crate) struct Registration {
108 pub(crate) definition: Definition,
109 runner: Arc<dyn Runner>,
110 generation: OperationGeneration,
111 closing: AtomicBool,
112 scope: Option<ScopeId>,
113 internal: bool,
114 published: Arc<AtomicBool>,
115
116 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
117 plugin: Option<crate::plugin::PluginScope>,
118}
119
120pub(crate) struct Retirement {
123 outcome: Outcome,
124 _scope: Arc<Scope>,
125 _running: Vec<Running>,
126 _definitions: Vec<Arc<Registration>>,
127}
128
129#[must_use = "commit the resident catalog or drop it to release its reservations"]
132pub struct Staged {
133 owner: Weak<Shared>,
134 scope: Arc<Scope>,
135 entries: Vec<Arc<Registration>>,
136 published: Arc<AtomicBool>,
137}
138
139pub(crate) struct Start {
141 pub(crate) resources: Resources,
142 pub(crate) lifecycle: Arc<Instance>,
143 pub(crate) stopping: watch::Sender<bool>,
144 pub(crate) report: Report,
145 pub(crate) operations: super::OperationManager,
146}
147
148#[derive(Clone)]
149pub(crate) struct Running {
150 task: Arc<Task<Outcome>>,
151 settlement: Arc<dyn Settlement>,
152 report: Report,
153}
154
155#[derive(Clone)]
157pub(crate) struct Report {
158 owner: Weak<Shared>,
159 key: Key,
160 id: OperationInstanceId,
161 kind: Kind,
162}
163
164#[must_use = "a resident lease holds activation demand"]
165pub(crate) struct Lease {
166 running: Running,
167 demand: u64,
168 generation: OperationGeneration,
169 released: bool,
170 joins_release: bool,
171}
172
173struct State {
174 closed: bool,
175 retired_failure: Option<Arc<Error>>,
176 next: u64,
177 reserved: BTreeSet<String>,
178 definitions: BTreeMap<String, Arc<Registration>>,
179 scopes: BTreeMap<ScopeId, Arc<Scope>>,
180 addresses: BTreeMap<usize, ScopeId>,
181 instances: BTreeMap<Key, Entry>,
182}
183
184struct Entry {
185 snapshot: Snapshot,
186 internal: bool,
187 leases: BTreeSet<u64>,
188 running: Running,
189 stopping: bool,
190}
191
192#[derive(Clone, Copy, Debug, PartialEq, Eq)]
195pub enum Kind {
196 Observer,
197 Service,
198}
199
200#[derive(Clone, Debug)]
201pub enum Definition {
202 Observer(ObserverDefinition),
203 Service(ServiceDefinition),
204}
205
206enum Acquisition {
207 Lease(Lease),
208 Wait(OperationInstanceId, Running),
209}
210
211pub(crate) trait Runner: Send + Sync + 'static {
216 fn resources(&self) -> TypeId;
217
218 fn reactivates(&self) -> bool {
221 true
222 }
223
224 fn spawn(self: Arc<Self>, start: Start) -> (Task<Outcome>, Arc<dyn Settlement>);
225}
226
227pub(crate) trait Settlement: Send + Sync + 'static {
229 fn lifecycle(&self) -> &Arc<Instance>;
230
231 fn record(&self, _outcome: &Outcome) {}
233
234 fn retry(self: Arc<Self>, report: Report) -> Option<Completion>;
236}
237
238harmos_domain::postcard_product!(Activation {});
241
242impl crate::schema::Described for Activation {
243 const NAME: &'static str = "harmos::activation";
244 const FIELDS: &'static [crate::schema::declaration::Field] = &[];
245 const MARKER: &'static [u8] = &crate::schema::declaration::encode::<
246 { crate::schema::declaration::encoded_len(Self::NAME, Self::FIELDS) },
247 >(Self::NAME, Self::FIELDS);
248}
249
250impl crate::MemorySize for Activation {
251 fn memory_size(&self) -> usize {
252 0
253 }
254}
255
256impl super::OperationDeclaration for Activation {
258 type Response = ();
259 type Input = super::NoStream;
260 type Output = super::NoStream;
261 const VERSION: u32 = 1;
262 const ID: &'static str = "";
263 const POLICY: super::OperationPolicy = super::OperationPolicy {
264 timeout: harmos_domain::operations::UNBOUNDED,
265 attempt_timeout: harmos_domain::operations::UNBOUNDED,
266 retries: super::RetryPolicy::Never,
267 restart: super::RestartPolicy::Never,
268 };
269}
270
271impl Key {
272 pub fn root(definition: impl Into<String>) -> Self {
274 Self { definition: definition.into(), scope: ROOT }
275 }
276}
277
278impl Kind {
279 pub fn domain(self) -> &'static str {
280 match self {
281 Self::Observer => "observer",
282 Self::Service => "service",
283 }
284 }
285
286 fn scope(self) -> &'static str {
287 match self {
288 Self::Observer => "observer scope",
289 Self::Service => "service scope",
290 }
291 }
292
293 fn definition(self) -> &'static str {
294 match self {
295 Self::Observer => "observer definition",
296 Self::Service => "service definition",
297 }
298 }
299
300 fn catalog(self) -> &'static str {
301 match self {
302 Self::Observer => "observer catalog",
303 Self::Service => "service catalog",
304 }
305 }
306}
307
308impl Definition {
309 pub fn id(&self) -> &str {
310 match self {
311 Self::Observer(definition) => &definition.id,
312 Self::Service(definition) => &definition.id,
313 }
314 }
315
316 pub fn kind(&self) -> Kind {
317 match self {
318 Self::Observer(_) => Kind::Observer,
319 Self::Service(_) => Kind::Service,
320 }
321 }
322}
323
324impl Registration {
325 fn is_published(&self) -> bool {
326 if !self.published.load(Ordering::Acquire) {
327 return false;
328 }
329 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
330 if self.plugin.as_ref().is_some_and(|scope| !scope.is_ready()) {
331 return false;
332 }
333 true
334 }
335}
336
337impl Registry {
338 pub(crate) fn new(index: &Arc<Instances>, reserved: BTreeSet<String>, identity: Arc<()>) -> Self {
339 let root_resources = Arc::new(());
340 let resources: Resources = Arc::clone(&root_resources) as Resources;
341 let root = Arc::new(Scope {
342 owner: Arc::downgrade(&identity),
343 id: ROOT,
344 address: address(&resources),
345 resources,
346 closed: AtomicBool::new(false),
347 retirement: Mutex::new(None),
348 detached: Mutex::new(Vec::new()),
349 });
350 let state = State {
351 closed: false,
352 retired_failure: None,
353 next: 0,
354 reserved,
355 definitions: BTreeMap::new(),
356 scopes: BTreeMap::from([(ROOT, Arc::clone(&root))]),
357 addresses: BTreeMap::from([(root.address, ROOT)]),
358 instances: BTreeMap::new(),
359 };
360 Self {
361 shared: Arc::new(Shared {
362 identity,
363 root_resources,
364 index: Arc::downgrade(index),
365 root,
366 state: Mutex::new(state),
367 statuses: watch::channel(BTreeMap::new()).0,
368 assembly: Mutex::new(Vec::new()),
369 }),
370 }
371 }
372
373 pub(crate) fn identity(&self) -> Arc<()> {
374 Arc::clone(&self.shared.identity)
375 }
376
377 pub(crate) fn root_resources(&self) -> Arc<()> {
378 Arc::clone(&self.shared.root_resources)
379 }
380
381 pub(crate) fn root(&self) -> Arc<Scope> {
382 Arc::clone(&self.shared.root)
383 }
384
385 fn state(&self) -> std::sync::MutexGuard<'_, State> {
386 self.shared.state.lock().expect("resident registry is healthy")
387 }
388
389 pub(crate) fn register(
391 &self, definition: Definition, runner: Arc<dyn Runner>, scope: Option<&Arc<Scope>>, internal: bool, reserved: bool,
392 ) -> Result<()> {
393 validate(&definition)?;
394 let registration = Arc::new(Registration {
395 definition,
396 runner,
397 generation: OperationGeneration::fresh()?,
398 closing: AtomicBool::new(false),
399 scope: scope.map(|scope| scope.id),
400 internal,
401 published: Arc::new(AtomicBool::new(true)),
402 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
403 plugin: None,
404 });
405 let mut state = self.state();
406 state.admit()?;
407 let id = registration.definition.id().to_owned();
408 if state.definitions.contains_key(&id) || (!reserved && state.reserved.contains(&id)) {
409 return Err(Error::AlreadyRegistered { domain: registration.definition.kind().domain(), name: id });
410 }
411 state.reserved.remove(&id);
412 state.definitions.insert(id, registration);
413 Ok(())
414 }
415
416 pub(crate) fn stage(
418 &self, scope: &Arc<Scope>, entries: Vec<(Definition, Arc<dyn Runner>)>,
419 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))] plugin: Option<crate::plugin::PluginScope>,
420 ) -> Result<Staged> {
421 self.check_scope(scope)?;
422 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
423 if plugin.as_ref().is_some_and(|scope| scope.is_closed()) {
424 return Err(Error::Closing { domain: "plugin scope" });
425 }
426 let published = Arc::new(AtomicBool::new(false));
427 let mut staged = Vec::new();
428 let mut ids = BTreeSet::new();
429 for (definition, runner) in entries {
430 validate(&definition)?;
431 if !ids.insert(definition.id().to_owned()) {
432 return Err(Error::AlreadyRegistered { domain: definition.kind().domain(), name: definition.id().to_owned() });
433 }
434 staged.push(Arc::new(Registration {
435 definition,
436 runner,
437 generation: OperationGeneration::fresh()?,
438 closing: AtomicBool::new(false),
439 scope: Some(scope.id),
440 internal: false,
441 published: Arc::clone(&published),
442 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
443 plugin: plugin.clone(),
444 }));
445 }
446
447 let mut state = self.state();
448 state.admit()?;
449 if scope.closed.load(Ordering::Acquire) {
450 return Err(Error::Closing { domain: "resident scope" });
451 }
452 if let Some(entry) = staged.iter().find(|entry| state.definitions.contains_key(entry.definition.id())) {
453 return Err(Error::AlreadyRegistered { domain: entry.definition.kind().domain(), name: entry.definition.id().to_owned() });
454 }
455 for entry in &staged {
456 state.definitions.insert(entry.definition.id().to_owned(), Arc::clone(entry));
457 }
458 Ok(Staged { owner: Arc::downgrade(&self.shared), scope: Arc::clone(scope), entries: staged, published })
459 }
460
461 pub(crate) fn definition(&self, id: &str) -> Option<Definition> {
462 self.state().definitions.get(id).filter(|item| item.is_published()).map(|item| item.definition.clone())
463 }
464
465 pub(crate) fn definitions(&self) -> Vec<Definition> {
466 self.state().definitions.values().filter(|item| item.is_published()).map(|item| item.definition.clone()).collect()
467 }
468
469 pub(crate) fn scope<R: Send + Sync + 'static>(&self, resources: Arc<R>) -> Result<Arc<Scope>> {
471 let resources: Resources = resources;
472 let address = address(&resources);
473 let mut state = self.state();
474 state.admit()?;
475 if let Some(scope) = state.addresses.get(&address).and_then(|id| state.scopes.get(id)) {
476 return Ok(Arc::clone(scope));
477 }
478 let id = ScopeId(
479 NEXT_SCOPE
480 .try_update(Ordering::Relaxed, Ordering::Relaxed, |value| value.checked_add(1))
481 .map_err(|_| Error::InvalidInput { field: "resident scope", reason: "identity space exhausted" })?,
482 );
483 let scope = Arc::new(Scope {
484 owner: Arc::downgrade(&self.shared.identity),
485 id,
486 address,
487 resources,
488 closed: AtomicBool::new(false),
489 retirement: Mutex::new(None),
490 detached: Mutex::new(Vec::new()),
491 });
492 state.scopes.insert(id, Arc::clone(&scope));
493 state.addresses.insert(address, id);
494 Ok(scope)
495 }
496
497 pub(crate) fn scope_of<R: Send + Sync + 'static>(&self, resources: &Arc<R>) -> Option<Arc<Scope>> {
498 let address = Arc::as_ptr(resources) as *const () as usize;
499 let state = self.state();
500 state.addresses.get(&address).and_then(|id| state.scopes.get(id)).cloned()
501 }
502
503 #[tracing::instrument(target = "harmos::operations", name = "operations.activate", level = "debug", skip_all)]
506 pub(super) async fn activate(
507 &self, operations: &super::OperationManager, id: &str, scope: &Arc<Scope>, resources: TypeId, holder: Identity, internal: bool,
508 ) -> Result<Lease> {
509 let mut joined = None;
510 loop {
511 match self.acquire(operations, id, scope, resources, joined, internal, &holder)? {
512 Acquisition::Lease(lease) => return Ok(lease),
513 Acquisition::Wait(instance, running) => {
514 let result = settle(vec![running.clone()]).await;
515 if !running.settlement.lifecycle().is_settled() {
516 return Err(result.err().unwrap_or(Error::NotReady { domain: "resident cleanup" }));
517 }
518 joined = Some(instance);
519 }
520 }
521 }
522 }
523
524 #[allow(clippy::too_many_arguments)]
525 #[tracing::instrument(target = "harmos::operations", name = "operations.acquire", level = "debug", skip_all)]
526 fn acquire(
527 &self, operations: &super::OperationManager, id: &str, scope: &Arc<Scope>, resources: TypeId, joined: Option<OperationInstanceId>,
528 internal: bool, holder: &Identity,
529 ) -> Result<Acquisition> {
530 self.check_scope(scope)?;
531 let mut state = self.state();
532 let Some(registration) = state.definitions.get(id).cloned() else {
533 state.admit()?;
534 return Err(Error::NotRegistered { domain: "resident", name: id.to_owned() });
535 };
536 let kind = registration.definition.kind();
537 if state.closed {
538 return Err(Error::Closing { domain: kind.domain() });
539 }
540 if scope.closed.load(Ordering::Acquire) {
541 return Err(Error::Closing { domain: kind.scope() });
542 }
543 if registration.internal && !internal {
544 return Err(Error::InvalidInput { field: kind.definition(), reason: "infrastructure demand belongs to its runtime owner" });
545 }
546 if !registration.is_published() {
547 return Err(Error::NotReady { domain: kind.catalog() });
548 }
549 if registration.closing.load(Ordering::Acquire) {
550 return Err(Error::Closing { domain: kind.definition() });
551 }
552 if registration.scope.is_some_and(|owner| owner != scope.id) || registration.runner.resources() != resources {
553 return Err(Error::InvalidInput { field: kind.scope(), reason: "definition belongs to another resource scope or type" });
554 }
555 let key = Key { definition: id.to_owned(), scope: scope.id };
556 let demand = state.next()?;
557 if let Some(entry) = state.instances.get_mut(&key) {
558 let lifecycle = entry.running.settlement.lifecycle().status();
559 if !entry.stopping && lifecycle.outcome().is_none() && !lifecycle.phase().is_terminal() {
560 entry.leases.insert(demand);
561 entry.snapshot.leases = entry.leases.len();
562 let instance = entry.snapshot.instance;
563 let lease = Lease::new(entry.running.clone(), demand, registration.generation);
564 self.track(instance, entry.running.settlement.lifecycle(), holder);
565 self.shared.publish(&state);
566 return Ok(Acquisition::Lease(lease));
567 }
568 if joined != Some(entry.snapshot.instance) {
569 return Ok(Acquisition::Wait(entry.snapshot.instance, entry.running.clone()));
570 }
571 if !registration.runner.reactivates() {
572 return Err(Error::NotReady { domain: "retired assembly service" });
573 }
574 }
575
576 let instance = super::handler::fresh_instance()?;
578 let stopping = watch::channel(false).0;
579 let index = self.shared.index.clone();
580 let lifecycle = Arc::new(Instance::queued(stopping.clone()).resident().on_settled(move || {
581 if let Some(index) = index.upgrade() {
582 index.forget_resident(instance);
583 }
584 }));
585 lifecycle.trace().record("instance_id", instance.value());
586 lifecycle.trace().record("operation", tracing::field::display(registration.definition.id()));
587 lifecycle.trace().record("generation", tracing::field::debug(registration.generation));
588 let _trace = lifecycle.trace().entered();
589 let report = Report { owner: Arc::downgrade(&self.shared), key: key.clone(), id: instance, kind };
590 let (task, settlement) = Arc::clone(®istration.runner).spawn(Start {
591 resources: Arc::clone(&scope.resources),
592 lifecycle: Arc::clone(&lifecycle),
593 stopping,
594 report: report.clone(),
595 operations: operations.clone(),
596 });
597 let running = Running { task: Arc::new(task), settlement, report };
598 let entry = Entry {
599 snapshot: Snapshot {
600 instance,
601 kind: registration.definition.kind(),
602 leases: 1,
603 lifecycle: lifecycle.status(),
604 health: (registration.definition.kind() == Kind::Service).then_some(ServiceStatus::Starting),
605 failure: None,
606 },
607 internal: registration.internal,
608 leases: BTreeSet::from([demand]),
609 running: running.clone(),
610 stopping: false,
611 };
612 let lease = Lease::new(running, demand, registration.generation);
613 self.track(instance, &lifecycle, holder);
614 let retired = state.instances.insert(key, entry);
615 self.shared.publish(&state);
616 drop(state);
617 drop(retired);
618 Ok(Acquisition::Lease(lease))
619 }
620
621 fn track(&self, id: OperationInstanceId, lifecycle: &Arc<Instance>, holder: &Identity) {
622 if let Some(index) = self.shared.index.upgrade() {
623 index.track_resident(id, lifecycle, holder.clone());
624 }
625 }
626
627 pub(crate) fn start_once(
630 &self, operations: &super::OperationManager, definition: Definition, runner: Arc<dyn Runner>, internal: bool, reserved: bool,
631 ) -> Result<Lease> {
632 let id = definition.id().to_owned();
633 let resources = runner.resources();
634 self.register(definition, runner, Some(&self.root()), internal, reserved)?;
635 let root = self.root();
636 match self.acquire(operations, &id, &root, resources, None, internal, &Identity::Host)? {
637 Acquisition::Lease(lease) => Ok(lease),
638 Acquisition::Wait(..) => {
639 unreachable!("a newly registered resident definition has no instance")
640 }
641 }
642 }
643
644 pub(crate) fn hold(&self, lease: Lease) {
645 self.shared.assembly.lock().expect("resident assembly is healthy").push(lease);
646 }
647
648 pub(crate) fn status(&self, key: &Key) -> Option<Snapshot> {
649 self.shared.statuses.borrow().get(key).cloned()
650 }
651
652 pub(crate) fn statuses(&self) -> BTreeMap<Key, Snapshot> {
653 self.shared.statuses.borrow().clone()
654 }
655
656 pub(crate) fn subscribe(&self) -> watch::Receiver<BTreeMap<Key, Snapshot>> {
657 self.shared.statuses.subscribe()
658 }
659
660 #[tracing::instrument(target = "harmos::operations", name = "operations.retire_scope", level = "debug", skip_all)]
663 pub(crate) async fn retire_scope(&self, scope: &Arc<Scope>) -> Result<Retirement> {
664 self.check_scope(scope)?;
665 if let Some(outcome) = scope.retirement.lock().expect("resident scope is healthy").clone() {
666 return Ok(Retirement { outcome, _scope: Arc::clone(scope), _running: Vec::new(), _definitions: Vec::new() });
667 }
668 let (running, definitions) = {
669 let mut state = self.state();
670 scope.closed.store(true, Ordering::Release);
671 let definitions: Vec<_> = state.definitions.values().filter(|entry| entry.scope == Some(scope.id)).cloned().collect();
672 for definition in &definitions {
673 definition.closing.store(true, Ordering::Release);
674 }
675 let running = state.stop(|key, _, _| key.scope == scope.id);
676 self.shared.publish(&state);
677 (running, definitions)
678 };
679 let outcome = settle(running.clone()).await.map_err(Arc::new);
680 let (original, detached) = {
681 let mut state = self.state();
682 if state.instances.iter().any(|(key, entry)| key.scope == scope.id && !entry.running.settlement.lifecycle().is_settled()) {
683 return Err(match &outcome {
684 Err(source) => Error::TaskFailed { domain: "resident cleanup", source: source.clone() },
685 Ok(()) => Error::NotReady { domain: "resident cleanup" },
686 });
687 }
688 if let Err(source) = &outcome
690 && running.iter().any(|running| running.report.kind == Kind::Observer)
691 {
692 state.retired_failure.get_or_insert_with(|| Arc::clone(source));
693 }
694 let original = state.scopes.remove(&scope.id).unwrap_or_else(|| Arc::clone(scope));
695 state.addresses.remove(&original.address);
696 state.instances.retain(|key, _| key.scope != scope.id);
697 for definition in &definitions {
698 if state.definitions.get(definition.definition.id()).is_some_and(|current| Arc::ptr_eq(current, definition)) {
699 state.definitions.remove(definition.definition.id());
700 }
701 }
702 self.shared.publish(&state);
703 let detached = std::mem::take(&mut *original.detached.lock().expect("resident scope is healthy"));
704 (original, detached)
705 };
706 drop(detached);
707 let outcome = scope.retirement.lock().expect("resident scope is healthy").get_or_insert(outcome).clone();
708 Ok(Retirement { outcome, _scope: original, _running: running, _definitions: definitions })
709 }
710
711 #[tracing::instrument(target = "harmos::operations", name = "operations.unregister", level = "debug", skip_all)]
712 pub(crate) async fn unregister(&self, id: &str) -> Result<()> {
713 let (registration, running) = {
714 let mut state = self.state();
715 let registration =
716 state.definitions.get(id).cloned().ok_or_else(|| Error::NotRegistered { domain: "resident", name: id.to_owned() })?;
717 registration.closing.store(true, Ordering::Release);
718 let running = state.stop(|key, _, _| key.definition == id);
719 self.shared.publish(&state);
720 (registration, running)
721 };
722 let result = settle(running.clone()).await;
723 if running.iter().any(|running| !running.settlement.lifecycle().is_settled()) {
724 return result;
725 }
726 let mut state = self.state();
727 let mut retired = Vec::new();
728 if state.definitions.get(id).is_some_and(|current| Arc::ptr_eq(current, ®istration)) {
729 state.definitions.remove(id);
730 let keys: Vec<_> = state.instances.keys().filter(|key| key.definition == id).cloned().collect();
731 retired.extend(keys.into_iter().filter_map(|key| state.instances.remove(&key)));
732 self.shared.publish(&state);
733 }
734 drop(state);
735 drop((registration, retired));
736 result
737 }
738
739 pub(crate) fn cleanup_pending(&self, selected: impl Fn(Kind, bool) -> bool) -> bool {
740 self.state()
741 .instances
742 .values()
743 .any(|entry| selected(entry.snapshot.kind, entry.internal) && !entry.running.settlement.lifecycle().is_settled())
744 }
745
746 pub(crate) fn close_admission(&self) {
749 let scopes = {
750 let mut state = self.state();
751 state.closed = true;
752 for scope in state.scopes.values() {
753 scope.closed.store(true, Ordering::Release);
754 }
755 state.stop(|_, _, internal| !internal);
756 self.shared.publish(&state);
757 state.release_scopes()
758 };
759 drop(scopes);
761 }
762
763 #[tracing::instrument(target = "harmos::operations", name = "operations.stop_kind", level = "debug", skip_all)]
766 pub(crate) async fn stop_kind(&self, kind: Kind) -> Result<()> {
767 let running = {
768 let mut state = self.state();
769 let running = state.stop(|_, selected, _| selected == kind);
770 self.shared.publish(&state);
771 running
772 };
773 settle(running).await
774 }
775
776 pub(crate) async fn shutdown_observers(&self) -> Result<()> {
777 self.close_admission();
778 let running = self.select(|kind, _| kind == Kind::Observer);
779 let result = settle(running).await;
780 let (definitions, scopes, retired_failure) = {
781 let mut state = self.state();
782 let ids: Vec<_> = state
783 .definitions
784 .iter()
785 .filter(|(id, registration)| {
786 registration.definition.kind() == Kind::Observer
787 && state
788 .instances
789 .iter()
790 .filter(|(key, _)| key.definition == **id)
791 .all(|(_, entry)| entry.running.settlement.lifecycle().is_settled())
792 })
793 .map(|(id, _)| id.clone())
794 .collect();
795 let definitions: Vec<_> = ids.into_iter().filter_map(|id| state.definitions.remove(&id)).collect();
796 (definitions, state.release_scopes(), state.retired_failure.clone())
797 };
798 drop((definitions, scopes));
801 result?;
802 match retired_failure {
803 Some(source) => Err(Error::TaskFailed { domain: "retired observer", source }),
804 None => Ok(()),
805 }
806 }
807
808 pub(crate) async fn shutdown_application(&self) -> Result<()> {
809 let running = {
810 let mut state = self.state();
811 state.closed = true;
812 let running = state.stop(|_, _, internal| !internal);
813 self.shared.publish(&state);
814 running
815 };
816 settle(running).await
817 }
818
819 #[tracing::instrument(target = "harmos::operations", name = "operations.shutdown", level = "debug", skip_all)]
820 pub(crate) async fn shutdown(&self) -> Result<()> {
821 let running = {
822 let mut state = self.state();
823 state.closed = true;
824 let running = state.stop(|_, _, _| true);
825 self.shared.publish(&state);
826 running
827 };
828 let retained = running.clone();
829 let result = settle(running).await;
830 if retained.iter().any(|running| !running.settlement.lifecycle().is_settled()) {
831 return result;
832 }
833 let (definitions, scopes) = {
834 let mut state = self.state();
835 for scope in state.scopes.values() {
836 scope.closed.store(true, Ordering::Release);
837 }
838 state.addresses.clear();
839 (std::mem::take(&mut state.definitions), std::mem::take(&mut state.scopes))
840 };
841 let assembly = std::mem::take(&mut *self.shared.assembly.lock().expect("resident assembly is healthy"));
842 let detached: Vec<_> =
843 scopes.values().flat_map(|scope| std::mem::take(&mut *scope.detached.lock().expect("resident scope is healthy"))).collect();
844 drop((definitions, scopes, assembly, detached));
845 result
846 }
847
848 fn select(&self, selected: impl Fn(Kind, bool) -> bool) -> Vec<Running> {
849 self.state()
850 .instances
851 .values()
852 .filter(|entry| selected(entry.snapshot.kind, entry.internal))
853 .map(|entry| entry.running.clone())
854 .collect()
855 }
856
857 fn check_scope(&self, scope: &Scope) -> Result<()> {
858 if !Weak::ptr_eq(&scope.owner, &Arc::downgrade(&self.shared.identity)) {
859 return Err(Error::InvalidInput { field: "resident scope", reason: "scope belongs to another manager" });
860 }
861 Ok(())
862 }
863}
864
865impl Shared {
866 fn publish(&self, state: &State) {
867 self.statuses.send_replace(
868 state
869 .instances
870 .iter()
871 .map(|(key, entry)| {
872 let mut snapshot = entry.snapshot.clone();
873 snapshot.lifecycle = entry.running.settlement.lifecycle().status();
874 (key.clone(), snapshot)
875 })
876 .collect(),
877 );
878 }
879
880 fn state(&self) -> std::sync::MutexGuard<'_, State> {
881 self.state.lock().expect("resident registry is healthy")
882 }
883
884 fn release(&self, key: &Key, id: OperationInstanceId, demand: u64) -> bool {
885 let mut state = self.state();
886 let Some(entry) = state.instances.get_mut(key).filter(|entry| entry.snapshot.instance == id) else {
887 return true;
888 };
889 entry.leases.remove(&demand);
890 entry.snapshot.leases = entry.leases.len();
891 let final_release = entry.leases.is_empty();
892 if final_release {
893 entry.stop();
894 }
895 self.publish(&state);
896 final_release
897 }
898
899 fn stop_instance(&self, key: &Key, id: OperationInstanceId) -> bool {
900 let mut state = self.state();
901 let Some(entry) = state.instances.get_mut(key).filter(|entry| entry.snapshot.instance == id) else {
902 return false;
903 };
904 entry.leases.clear();
905 entry.snapshot.leases = 0;
906 entry.stop();
907 self.publish(&state);
908 true
909 }
910}
911
912impl State {
913 fn admit(&self) -> Result<()> {
914 if self.closed { Err(Error::Closing { domain: "resident" }) } else { Ok(()) }
915 }
916
917 fn next(&mut self) -> Result<u64> {
918 self.next =
919 self.next.checked_add(1).ok_or(Error::InvalidInput { field: "resident identity", reason: "identity space exhausted" })?;
920 Ok(self.next)
921 }
922
923 fn stop(&mut self, selected: impl Fn(&Key, Kind, bool) -> bool) -> Vec<Running> {
924 self.instances
925 .iter_mut()
926 .filter(|(key, entry)| selected(key, entry.snapshot.kind, entry.internal))
927 .map(|(_, entry)| {
928 entry.leases.clear();
929 entry.snapshot.leases = 0;
930 entry.stop();
931 entry.running.clone()
932 })
933 .collect()
934 }
935
936 fn release_scopes(&mut self) -> Vec<Arc<Scope>> {
938 let ids: Vec<_> = self
939 .scopes
940 .keys()
941 .filter(|id| {
942 **id != ROOT
943 && self
944 .instances
945 .iter()
946 .filter(|(key, _)| key.scope == **id)
947 .all(|(_, entry)| entry.running.settlement.lifecycle().is_settled())
948 })
949 .copied()
950 .collect();
951 let scopes: Vec<_> = ids.into_iter().filter_map(|id| self.scopes.remove(&id)).collect();
952 for scope in &scopes {
953 self.addresses.remove(&scope.address);
954 }
955 scopes
956 }
957}
958
959impl Entry {
960 fn stop(&mut self) {
961 self.stopping = true;
962 if let Some(health) = &mut self.snapshot.health
963 && self.running.settlement.lifecycle().status().outcome().is_none()
964 {
965 *health = ServiceStatus::Stopping;
966 }
967 self.running.stop();
968 }
969}
970
971impl Running {
972 fn stop(&self) {
973 self.settlement.lifecycle().cancel();
974 }
975
976 #[tracing::instrument(target = "harmos::operations", name = "operations.settle", level = "debug", skip_all)]
977 async fn settle(&self) -> std::result::Result<Arc<Outcome>, Arc<tokio::task::JoinError>> {
978 if let Err(failure) = self.settlement.lifecycle().settle_dependents().await {
979 return Ok(Arc::new(Err(failure)));
980 }
981 let settlement = Arc::clone(&self.settlement);
982 let report = self.report.clone();
983 self.task.settle_retry(move || settlement.retry(report)).await
984 }
985
986 pub(crate) fn lifecycle(&self) -> &Arc<Instance> {
987 self.settlement.lifecycle()
988 }
989}
990
991impl Report {
992 pub(crate) fn id(&self) -> OperationInstanceId {
993 self.id
994 }
995
996 pub(crate) fn key(&self) -> &Key {
997 &self.key
998 }
999
1000 fn with_entry(&self, update: impl FnOnce(&mut Entry) -> bool) {
1001 let Some(owner) = self.owner.upgrade() else {
1002 return;
1003 };
1004 let mut state = owner.state();
1005 let changed = state.instances.get_mut(&self.key).filter(|entry| entry.snapshot.instance == self.id).map(update).unwrap_or(false);
1006 if changed {
1007 owner.publish(&state);
1008 }
1009 }
1010
1011 pub(crate) fn transition(&self, phase: Phase) {
1013 self.with_entry(|entry| {
1014 if entry.stopping && !matches!(phase, Phase::Stopping | Phase::Finalizing) {
1015 return false;
1016 }
1017 entry.running.lifecycle().transition(phase);
1018 true
1019 });
1020 }
1021
1022 pub(crate) fn quiesce(&self) {
1024 self.with_entry(|entry| {
1025 entry.stopping = true;
1026 if let Some(health) = &mut entry.snapshot.health {
1027 *health = ServiceStatus::Stopping;
1028 }
1029 true
1030 });
1031 }
1032
1033 pub(crate) fn health(&self, status: ServiceStatus) {
1035 self.with_entry(|entry| {
1036 if entry.stopping && !matches!(status, ServiceStatus::Stopped | ServiceStatus::Inactive { .. }) {
1037 return false;
1038 }
1039 let lifecycle = entry.running.lifecycle();
1040 match &status {
1041 ServiceStatus::Running { .. } => lifecycle.transition(Phase::Running),
1042 ServiceStatus::BackingOff => lifecycle.transition(Phase::Restarting),
1043 _ => {}
1044 }
1045 entry.snapshot.health = Some(status);
1046 true
1047 });
1048 }
1049
1050 pub(crate) fn publish(&self) {
1051 self.with_entry(|_| true);
1052 }
1053
1054 pub(crate) fn complete(&self, outcome: &Outcome) {
1056 self.with_entry(|entry| {
1057 entry.running.settlement.record(outcome);
1058 entry.snapshot.failure = outcome.as_ref().err().cloned();
1059 if let Some(health) = &mut entry.snapshot.health {
1060 if outcome.is_err() && !matches!(health, ServiceStatus::Inactive { .. }) {
1061 *health = ServiceStatus::Inactive { cause: crate::services::ServiceExit::Panicked, history: Vec::new() };
1062 } else if outcome.is_ok() {
1063 *health = ServiceStatus::Stopped;
1064 }
1065 }
1066 true
1067 });
1068 }
1069}
1070
1071impl Lease {
1072 fn new(running: Running, demand: u64, generation: OperationGeneration) -> Self {
1073 Self { running, demand, generation, released: false, joins_release: false }
1074 }
1075
1076 pub(crate) fn id(&self) -> OperationInstanceId {
1077 self.running.report.id
1078 }
1079
1080 pub(crate) fn generation(&self) -> OperationGeneration {
1081 self.generation
1082 }
1083
1084 pub(crate) fn lifecycle(&self) -> Arc<Instance> {
1085 Arc::clone(self.running.lifecycle())
1086 }
1087
1088 pub(crate) fn settlement(&self) -> impl Future<Output = Result<()>> + Send + 'static + use<> {
1090 let running = self.running.clone();
1091 async move { settle(vec![running]).await }
1092 }
1093
1094 #[cfg(feature = "plugin-sidecar")]
1095 pub(crate) fn transition(&self, phase: Phase) {
1096 self.running.report.transition(phase);
1097 }
1098
1099 pub(crate) fn stop(&self) {
1101 let report = &self.running.report;
1102 let stopped = report.owner.upgrade().is_some_and(|owner| owner.stop_instance(&report.key, report.id));
1103 if !stopped {
1104 self.running.stop();
1105 }
1106 }
1107
1108 pub(crate) fn detach(self) {
1110 let report = &self.running.report;
1111 let Some(owner) = report.owner.upgrade() else {
1112 return;
1113 };
1114 let scope = owner.state().scopes.get(&report.key.scope).cloned();
1115 match scope {
1116 Some(scope) => scope.detached.lock().expect("resident scope is healthy").push(self),
1117 None => drop(self),
1118 }
1119 }
1120
1121 pub(crate) fn retire(&mut self) -> impl Future<Output = Result<()>> + Send + 'static + use<> {
1124 let completion = self.begin_release();
1125 let report = self.running.report.clone();
1126 let lifecycle = Arc::clone(self.running.lifecycle());
1127 async move {
1128 let result = completion.await;
1129 if !lifecycle.is_settled() {
1130 return result;
1131 }
1132 if let Some(owner) = report.owner.upgrade() {
1133 let mut state = owner.state();
1134 let owns = state.instances.get(&report.key).is_some_and(|entry| entry.snapshot.instance == report.id && entry.internal);
1135 let retired = owns.then(|| (state.instances.remove(&report.key), state.definitions.remove(&report.key.definition)));
1136 owner.publish(&state);
1137 drop(state);
1138 drop(retired);
1139 }
1140 result
1141 }
1142 }
1143
1144 pub(crate) async fn release(mut self) -> Result<()> {
1145 self.begin_release().await
1146 }
1147
1148 fn begin_release(&mut self) -> impl Future<Output = Result<()>> + Send + 'static + use<> {
1149 self.request();
1150 let running = self.joins_release.then(|| self.running.clone());
1151 async move { settle(running.into_iter().collect()).await }
1152 }
1153
1154 fn request(&mut self) -> bool {
1155 if self.released {
1156 return self.joins_release;
1157 }
1158 self.released = true;
1159 let report = &self.running.report;
1160 self.joins_release = match report.owner.upgrade() {
1161 Some(owner) => owner.release(&report.key, report.id, self.demand),
1162 None => {
1163 self.running.stop();
1164 true
1165 }
1166 };
1167 self.joins_release
1168 }
1169}
1170
1171impl Drop for Lease {
1172 fn drop(&mut self) {
1173 self.request();
1174 }
1175}
1176
1177impl Staged {
1178 pub fn commit(self) -> Result<()> {
1179 let owner = self.owner.upgrade().ok_or(Error::Closing { domain: "resident" })?;
1180 let state = owner.state();
1181 state.admit()?;
1182 if self.scope.closed.load(Ordering::Acquire) {
1183 return Err(Error::Closing { domain: "resident scope" });
1184 }
1185 for entry in &self.entries {
1186 #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
1187 if entry.plugin.as_ref().is_some_and(|scope| scope.is_closed()) {
1188 return Err(Error::Closing { domain: "plugin scope" });
1189 }
1190 if entry.closing.load(Ordering::Acquire)
1191 || !state.definitions.get(entry.definition.id()).is_some_and(|current| Arc::ptr_eq(current, entry))
1192 {
1193 return Err(Error::NotRegistered { domain: "resident reservation", name: entry.definition.id().to_owned() });
1194 }
1195 }
1196 self.published.store(true, Ordering::Release);
1197 Ok(())
1198 }
1199}
1200
1201impl Drop for Staged {
1202 fn drop(&mut self) {
1203 if self.published.load(Ordering::Acquire) {
1204 return;
1205 }
1206 let Some(owner) = self.owner.upgrade() else {
1207 return;
1208 };
1209 let mut state = owner.state();
1210 for entry in &self.entries {
1211 if state.definitions.get(entry.definition.id()).is_some_and(|current| Arc::ptr_eq(current, entry)) {
1212 state.definitions.remove(entry.definition.id());
1213 }
1214 }
1215 }
1217}
1218
1219impl Retirement {
1220 pub(crate) fn into_outcome(self) -> Outcome {
1221 self.outcome
1222 }
1223}
1224
1225impl Drop for Shared {
1226 fn drop(&mut self) {
1227 let state = self.state.get_mut().expect("resident registry is healthy");
1228 state.stop(|_, _, _| true);
1229 }
1230}
1231
1232#[tracing::instrument(target = "harmos::operations", name = "operations.settle", level = "debug", skip_all)]
1237pub(crate) async fn settle(running: Vec<Running>) -> Result<()> {
1238 let pending = running.into_iter().map(|running| async move {
1239 let domain = running.report.kind.domain();
1240 match running.settle().await {
1241 Ok(outcome) => outcome.as_ref().clone().map_err(|source| Error::TaskFailed { domain, source }),
1242 Err(source) => Err(Error::TaskFailed {
1243 domain: match running.report.kind {
1244 Kind::Observer => "observer supervisor",
1245 Kind::Service => "service supervisor",
1246 },
1247 source,
1248 }),
1249 }
1250 });
1251 supervision::settle_all(pending).await
1252}
1253
1254fn address(resources: &Resources) -> usize {
1255 Arc::as_ptr(resources) as *const () as usize
1256}
1257
1258fn validate(definition: &Definition) -> Result<()> {
1259 match definition {
1260 Definition::Observer(definition) => crate::observers::validate(definition),
1261 Definition::Service(definition) => crate::services::validate(definition),
1262 }
1263}
1264
1265