Skip to main content

harmos_runtime/operations/
resident.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10// Resident declarations, observers and services, run as instances of the
11// operation manager. One registry owns their definitions, the resources each
12// activation shares, the instances keyed by definition and resources, and the
13// leases that keep an instance running. A kind contributes only its driver:
14// observers tick at their declared interval, services run until stopped under
15// their restart policy.
16
17/* === Modules === */
18
19/* === Re-Exports === */
20
21/* === Imports === */
22
23// Standard Library Imports
24use 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};
30// Third Party Imports
31use tokio::sync::watch;
32// Local Imports
33use 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
41/* === Constants === */
42
43static NEXT_SCOPE: AtomicU64 = AtomicU64::new(1);
44
45const ROOT: ScopeId = ScopeId(0);
46
47/* === Macros === */
48
49/* === Type Aliases === */
50
51pub(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
55/* === Structs === */
56
57// The declaration a resident handle names when its definition is selected by
58// identity at runtime. It is never registered by its own identity.
59pub struct Activation;
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
62pub struct ScopeId(u64);
63
64// One resident instance per definition and shared resources.
65#[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    // Restart credit and deactivation history; services only.
78    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// Retains a retired scope's resources and instances until its caller has
128// consumed the outcome, so author Drop code never runs under a registry lock.
129#[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// A staged set of definitions that become visible when committed and are
138// withdrawn when dropped uncommitted.
139#[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
147// What a runner receives to start one instance.
148pub(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// Progress a kind's driver reports for its own instance.
164#[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/* === Enums === */
201
202#[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
219/* === Traits === */
220
221// A kind's driver. It spawns one instance over its resources and reports the
222// instance's progress; the registry owns everything else.
223pub(crate) trait Runner: Send + Sync + 'static {
224    fn resources(&self) -> TypeId;
225
226    // A definition whose resident can be created only once is not reactivated
227    // after its instance stops.
228    fn reactivates(&self) -> bool {
229        true
230    }
231
232    fn spawn(self: Arc<Self>, start: Start) -> (Task<Outcome>, Arc<dyn Settlement>);
233}
234
235// The physical cleanup a kind retains for one instance.
236pub(crate) trait Settlement: Send + Sync + 'static {
237    fn lifecycle(&self) -> &Arc<Instance>;
238
239    // Record the instance's logical outcome as its task completes.
240    fn record(&self, _outcome: &Outcome) {}
241
242    // Retry a cleanup that failed after the task returned; `None` once settled.
243    fn retry(self: Arc<Self>, report: Report) -> Option<Completion>;
244}
245
246/* === Implementations === */
247
248// The Resident Activation Of A ServiceContext; It Is Never Declared By Its Identity.
249impl 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    // The key of `definition`'s instance over the runtime's own resources.
265    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    // Register one definition visible at once, optionally bound to one scope.
398    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    // Reserve definitions bound to one scope; they become visible on commit.
428    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    // The scope that shares these resources, opened on first use.
496    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    // Lease the instance of `id` over `scope`, starting it when none runs.
533    // A stopping instance is joined before its replacement starts.
534    #[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(&registration.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    // Start a definition that can run once, owned by the registry until the
670    // runtime shuts down.
671    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    // Stop every instance over a scope, settle them and forget the scope with
703    // the definitions bound to it. The first outcome is retained.
704    #[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            // Only observer retirement failures resurface at shutdown.
740            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                // Observer Controls Retain Their Publication Lease Until Carrier Cleanup.
776                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, &registration)) {
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    // Refuse new activations and stop observers and application services.
837    // Infrastructure services keep running until `shutdown`.
838    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        // Releasing the final resource reference can invoke author Drop code.
850        drop(scopes);
851    }
852
853    // Stop every instance of one kind and settle them; a failed cleanup stays
854    // retained and the next stop retries it.
855    #[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        // Context factories may retain runtime handles. Drop them outside
889        // the registry lock after every active use has joined.
890        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    // Forget scopes whose instances all settled; the root scope stays.
1032    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    // Move the instance's lifecycle to `phase` unless it is already stopping.
1107    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    // Close lease admission: a stopping instance is never joined again.
1118    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    // Publish a service's health; its lifecycle follows.
1129    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    // Record the instance's outcome as its task or a cleanup retry completes.
1150    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    // Settle this instance; holding the lease keeps it running.
1193    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    // Stop the instance for every lease holder.
1204    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    // Hold the lease until its scope retires or the runtime shuts down.
1213    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    // Release this lease and, when it was an infrastructure definition's last,
1226    // forget the definition once its instance settled.
1227    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        // self.entries retains every runner until this lock has been released.
1327    }
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/* === Public Functions === */
1345
1346/* === Private Functions === */
1347
1348#[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/* === Tests === */