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