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