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