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    pub(super) async fn activate(
506        &self, operations: &super::OperationManager, id: &str, scope: &Arc<Scope>, resources: TypeId, holder: Identity, internal: bool,
507    ) -> Result<Lease> {
508        let mut joined = None;
509        loop {
510            match self.acquire(operations, id, scope, resources, joined, internal, &holder)? {
511                Acquisition::Lease(lease) => return Ok(lease),
512                Acquisition::Wait(instance, running) => {
513                    let result = settle(vec![running.clone()]).await;
514                    if !running.settlement.lifecycle().is_settled() {
515                        return Err(result.err().unwrap_or(Error::NotReady { domain: "resident cleanup" }));
516                    }
517                    joined = Some(instance);
518                }
519            }
520        }
521    }
522
523    #[allow(clippy::too_many_arguments)]
524    fn acquire(
525        &self, operations: &super::OperationManager, id: &str, scope: &Arc<Scope>, resources: TypeId, joined: Option<OperationInstanceId>,
526        internal: bool, holder: &Identity,
527    ) -> Result<Acquisition> {
528        self.check_scope(scope)?;
529        let mut state = self.state();
530        let Some(registration) = state.definitions.get(id).cloned() else {
531            state.admit()?;
532            return Err(Error::NotRegistered { domain: "resident", name: id.to_owned() });
533        };
534        let kind = registration.definition.kind();
535        if state.closed {
536            return Err(Error::Closing { domain: kind.domain() });
537        }
538        if scope.closed.load(Ordering::Acquire) {
539            return Err(Error::Closing { domain: kind.scope() });
540        }
541        if registration.internal && !internal {
542            return Err(Error::InvalidInput { field: kind.definition(), reason: "infrastructure demand belongs to its runtime owner" });
543        }
544        if !registration.is_published() {
545            return Err(Error::NotReady { domain: kind.catalog() });
546        }
547        if registration.closing.load(Ordering::Acquire) {
548            return Err(Error::Closing { domain: kind.definition() });
549        }
550        if registration.scope.is_some_and(|owner| owner != scope.id) || registration.runner.resources() != resources {
551            return Err(Error::InvalidInput { field: kind.scope(), reason: "definition belongs to another resource scope or type" });
552        }
553        let key = Key { definition: id.to_owned(), scope: scope.id };
554        let demand = state.next()?;
555        if let Some(entry) = state.instances.get_mut(&key) {
556            let lifecycle = entry.running.settlement.lifecycle().status();
557            if !entry.stopping && lifecycle.outcome().is_none() && !lifecycle.phase().is_terminal() {
558                entry.leases.insert(demand);
559                entry.snapshot.leases = entry.leases.len();
560                let instance = entry.snapshot.instance;
561                let lease = Lease::new(entry.running.clone(), demand, registration.generation);
562                self.track(instance, entry.running.settlement.lifecycle(), holder);
563                self.shared.publish(&state);
564                return Ok(Acquisition::Lease(lease));
565            }
566            if joined != Some(entry.snapshot.instance) {
567                return Ok(Acquisition::Wait(entry.snapshot.instance, entry.running.clone()));
568            }
569            if !registration.runner.reactivates() {
570                return Err(Error::NotReady { domain: "retired assembly service" });
571            }
572        }
573
574        // Start One Instance Over The Shared Resources
575        let instance = super::handler::fresh_instance()?;
576        let stopping = watch::channel(false).0;
577        let index = self.shared.index.clone();
578        let lifecycle = Arc::new(Instance::queued(stopping.clone()).resident().on_settled(move || {
579            if let Some(index) = index.upgrade() {
580                index.forget_resident(instance);
581            }
582        }));
583        let report = Report { owner: Arc::downgrade(&self.shared), key: key.clone(), id: instance, kind };
584        let (task, settlement) = Arc::clone(&registration.runner).spawn(Start {
585            resources: Arc::clone(&scope.resources),
586            lifecycle: Arc::clone(&lifecycle),
587            stopping,
588            report: report.clone(),
589            operations: operations.clone(),
590        });
591        let running = Running { task: Arc::new(task), settlement, report };
592        let entry = Entry {
593            snapshot: Snapshot {
594                instance,
595                kind: registration.definition.kind(),
596                leases: 1,
597                lifecycle: lifecycle.status(),
598                health: (registration.definition.kind() == Kind::Service).then_some(ServiceStatus::Starting),
599                failure: None,
600            },
601            internal: registration.internal,
602            leases: BTreeSet::from([demand]),
603            running: running.clone(),
604            stopping: false,
605        };
606        let lease = Lease::new(running, demand, registration.generation);
607        self.track(instance, &lifecycle, holder);
608        let retired = state.instances.insert(key, entry);
609        self.shared.publish(&state);
610        drop(state);
611        drop(retired);
612        Ok(Acquisition::Lease(lease))
613    }
614
615    fn track(&self, id: OperationInstanceId, lifecycle: &Arc<Instance>, holder: &Identity) {
616        if let Some(index) = self.shared.index.upgrade() {
617            index.track_resident(id, lifecycle, holder.clone());
618        }
619    }
620
621    // Start a definition that can run once, owned by the registry until the
622    // runtime shuts down.
623    pub(crate) fn start_once(
624        &self, operations: &super::OperationManager, definition: Definition, runner: Arc<dyn Runner>, internal: bool, reserved: bool,
625    ) -> Result<Lease> {
626        let id = definition.id().to_owned();
627        let resources = runner.resources();
628        self.register(definition, runner, Some(&self.root()), internal, reserved)?;
629        let root = self.root();
630        match self.acquire(operations, &id, &root, resources, None, internal, &Identity::Host)? {
631            Acquisition::Lease(lease) => Ok(lease),
632            Acquisition::Wait(..) => {
633                unreachable!("a newly registered resident definition has no instance")
634            }
635        }
636    }
637
638    pub(crate) fn hold(&self, lease: Lease) {
639        self.shared.assembly.lock().expect("resident assembly is healthy").push(lease);
640    }
641
642    pub(crate) fn status(&self, key: &Key) -> Option<Snapshot> {
643        self.shared.statuses.borrow().get(key).cloned()
644    }
645
646    pub(crate) fn statuses(&self) -> BTreeMap<Key, Snapshot> {
647        self.shared.statuses.borrow().clone()
648    }
649
650    pub(crate) fn subscribe(&self) -> watch::Receiver<BTreeMap<Key, Snapshot>> {
651        self.shared.statuses.subscribe()
652    }
653
654    // Stop every instance over a scope, settle them and forget the scope with
655    // the definitions bound to it. The first outcome is retained.
656    pub(crate) async fn retire_scope(&self, scope: &Arc<Scope>) -> Result<Retirement> {
657        self.check_scope(scope)?;
658        if let Some(outcome) = scope.retirement.lock().expect("resident scope is healthy").clone() {
659            return Ok(Retirement { outcome, _scope: Arc::clone(scope), _running: Vec::new(), _definitions: Vec::new() });
660        }
661        let (running, definitions) = {
662            let mut state = self.state();
663            scope.closed.store(true, Ordering::Release);
664            let definitions: Vec<_> = state.definitions.values().filter(|entry| entry.scope == Some(scope.id)).cloned().collect();
665            for definition in &definitions {
666                definition.closing.store(true, Ordering::Release);
667            }
668            let running = state.stop(|key, _, _| key.scope == scope.id);
669            self.shared.publish(&state);
670            (running, definitions)
671        };
672        let outcome = settle(running.clone()).await.map_err(Arc::new);
673        let (original, detached) = {
674            let mut state = self.state();
675            if state.instances.iter().any(|(key, entry)| key.scope == scope.id && !entry.running.settlement.lifecycle().is_settled()) {
676                return Err(match &outcome {
677                    Err(source) => Error::TaskFailed { domain: "resident cleanup", source: source.clone() },
678                    Ok(()) => Error::NotReady { domain: "resident cleanup" },
679                });
680            }
681            // Only observer retirement failures resurface at shutdown, as before.
682            if let Err(source) = &outcome
683                && running.iter().any(|running| running.report.kind == Kind::Observer)
684            {
685                state.retired_failure.get_or_insert_with(|| Arc::clone(source));
686            }
687            let original = state.scopes.remove(&scope.id).unwrap_or_else(|| Arc::clone(scope));
688            state.addresses.remove(&original.address);
689            state.instances.retain(|key, _| key.scope != scope.id);
690            for definition in &definitions {
691                if state.definitions.get(definition.definition.id()).is_some_and(|current| Arc::ptr_eq(current, definition)) {
692                    state.definitions.remove(definition.definition.id());
693                }
694            }
695            self.shared.publish(&state);
696            let detached = std::mem::take(&mut *original.detached.lock().expect("resident scope is healthy"));
697            (original, detached)
698        };
699        drop(detached);
700        let outcome = scope.retirement.lock().expect("resident scope is healthy").get_or_insert(outcome).clone();
701        Ok(Retirement { outcome, _scope: original, _running: running, _definitions: definitions })
702    }
703
704    pub(crate) async fn unregister(&self, id: &str) -> Result<()> {
705        let (registration, running) = {
706            let mut state = self.state();
707            let registration =
708                state.definitions.get(id).cloned().ok_or_else(|| Error::NotRegistered { domain: "resident", name: id.to_owned() })?;
709            registration.closing.store(true, Ordering::Release);
710            let running = state.stop(|key, _, _| key.definition == id);
711            self.shared.publish(&state);
712            (registration, running)
713        };
714        let result = settle(running.clone()).await;
715        if running.iter().any(|running| !running.settlement.lifecycle().is_settled()) {
716            return result;
717        }
718        let mut state = self.state();
719        let mut retired = Vec::new();
720        if state.definitions.get(id).is_some_and(|current| Arc::ptr_eq(current, &registration)) {
721            state.definitions.remove(id);
722            let keys: Vec<_> = state.instances.keys().filter(|key| key.definition == id).cloned().collect();
723            retired.extend(keys.into_iter().filter_map(|key| state.instances.remove(&key)));
724            self.shared.publish(&state);
725        }
726        drop(state);
727        drop((registration, retired));
728        result
729    }
730
731    pub(crate) fn cleanup_pending(&self, selected: impl Fn(Kind, bool) -> bool) -> bool {
732        self.state()
733            .instances
734            .values()
735            .any(|entry| selected(entry.snapshot.kind, entry.internal) && !entry.running.settlement.lifecycle().is_settled())
736    }
737
738    // Refuse new activations and stop observers and application services.
739    // Infrastructure services keep running until `shutdown`.
740    pub(crate) fn close_admission(&self) {
741        let scopes = {
742            let mut state = self.state();
743            state.closed = true;
744            for scope in state.scopes.values() {
745                scope.closed.store(true, Ordering::Release);
746            }
747            state.stop(|_, _, internal| !internal);
748            self.shared.publish(&state);
749            state.release_scopes()
750        };
751        // Releasing the final resource reference can invoke author Drop code.
752        drop(scopes);
753    }
754
755    // Stop every instance of one kind and settle them; a failed cleanup stays
756    // retained and the next stop retries it.
757    pub(crate) async fn stop_kind(&self, kind: Kind) -> Result<()> {
758        let running = {
759            let mut state = self.state();
760            let running = state.stop(|_, selected, _| selected == kind);
761            self.shared.publish(&state);
762            running
763        };
764        settle(running).await
765    }
766
767    pub(crate) async fn shutdown_observers(&self) -> Result<()> {
768        self.close_admission();
769        let running = self.select(|kind, _| kind == Kind::Observer);
770        let result = settle(running).await;
771        let (definitions, scopes, retired_failure) = {
772            let mut state = self.state();
773            let ids: Vec<_> = state
774                .definitions
775                .iter()
776                .filter(|(id, registration)| {
777                    registration.definition.kind() == Kind::Observer
778                        && state
779                            .instances
780                            .iter()
781                            .filter(|(key, _)| key.definition == **id)
782                            .all(|(_, entry)| entry.running.settlement.lifecycle().is_settled())
783                })
784                .map(|(id, _)| id.clone())
785                .collect();
786            let definitions: Vec<_> = ids.into_iter().filter_map(|id| state.definitions.remove(&id)).collect();
787            (definitions, state.release_scopes(), state.retired_failure.clone())
788        };
789        // Context factories may retain runtime handles. Drop them outside
790        // the registry lock after every active use has joined.
791        drop((definitions, scopes));
792        result?;
793        match retired_failure {
794            Some(source) => Err(Error::TaskFailed { domain: "retired observer", source }),
795            None => Ok(()),
796        }
797    }
798
799    pub(crate) async fn shutdown_application(&self) -> Result<()> {
800        let running = {
801            let mut state = self.state();
802            state.closed = true;
803            let running = state.stop(|_, _, internal| !internal);
804            self.shared.publish(&state);
805            running
806        };
807        settle(running).await
808    }
809
810    pub(crate) async fn shutdown(&self) -> Result<()> {
811        let running = {
812            let mut state = self.state();
813            state.closed = true;
814            let running = state.stop(|_, _, _| true);
815            self.shared.publish(&state);
816            running
817        };
818        let retained = running.clone();
819        let result = settle(running).await;
820        if retained.iter().any(|running| !running.settlement.lifecycle().is_settled()) {
821            return result;
822        }
823        let (definitions, scopes) = {
824            let mut state = self.state();
825            for scope in state.scopes.values() {
826                scope.closed.store(true, Ordering::Release);
827            }
828            state.addresses.clear();
829            (std::mem::take(&mut state.definitions), std::mem::take(&mut state.scopes))
830        };
831        let assembly = std::mem::take(&mut *self.shared.assembly.lock().expect("resident assembly is healthy"));
832        let detached: Vec<_> =
833            scopes.values().flat_map(|scope| std::mem::take(&mut *scope.detached.lock().expect("resident scope is healthy"))).collect();
834        drop((definitions, scopes, assembly, detached));
835        result
836    }
837
838    fn select(&self, selected: impl Fn(Kind, bool) -> bool) -> Vec<Running> {
839        self.state()
840            .instances
841            .values()
842            .filter(|entry| selected(entry.snapshot.kind, entry.internal))
843            .map(|entry| entry.running.clone())
844            .collect()
845    }
846
847    fn check_scope(&self, scope: &Scope) -> Result<()> {
848        if !Weak::ptr_eq(&scope.owner, &Arc::downgrade(&self.shared.identity)) {
849            return Err(Error::InvalidInput { field: "resident scope", reason: "scope belongs to another manager" });
850        }
851        Ok(())
852    }
853}
854
855impl Shared {
856    fn publish(&self, state: &State) {
857        self.statuses.send_replace(
858            state
859                .instances
860                .iter()
861                .map(|(key, entry)| {
862                    let mut snapshot = entry.snapshot.clone();
863                    snapshot.lifecycle = entry.running.settlement.lifecycle().status();
864                    (key.clone(), snapshot)
865                })
866                .collect(),
867        );
868    }
869
870    fn state(&self) -> std::sync::MutexGuard<'_, State> {
871        self.state.lock().expect("resident registry is healthy")
872    }
873
874    fn release(&self, key: &Key, id: OperationInstanceId, demand: u64) -> bool {
875        let mut state = self.state();
876        let Some(entry) = state.instances.get_mut(key).filter(|entry| entry.snapshot.instance == id) else {
877            return true;
878        };
879        entry.leases.remove(&demand);
880        entry.snapshot.leases = entry.leases.len();
881        let final_release = entry.leases.is_empty();
882        if final_release {
883            entry.stop();
884        }
885        self.publish(&state);
886        final_release
887    }
888
889    fn stop_instance(&self, key: &Key, id: OperationInstanceId) -> bool {
890        let mut state = self.state();
891        let Some(entry) = state.instances.get_mut(key).filter(|entry| entry.snapshot.instance == id) else {
892            return false;
893        };
894        entry.leases.clear();
895        entry.snapshot.leases = 0;
896        entry.stop();
897        self.publish(&state);
898        true
899    }
900}
901
902impl State {
903    fn admit(&self) -> Result<()> {
904        if self.closed { Err(Error::Closing { domain: "resident" }) } else { Ok(()) }
905    }
906
907    fn next(&mut self) -> Result<u64> {
908        self.next =
909            self.next.checked_add(1).ok_or(Error::InvalidInput { field: "resident identity", reason: "identity space exhausted" })?;
910        Ok(self.next)
911    }
912
913    fn stop(&mut self, selected: impl Fn(&Key, Kind, bool) -> bool) -> Vec<Running> {
914        self.instances
915            .iter_mut()
916            .filter(|(key, entry)| selected(key, entry.snapshot.kind, entry.internal))
917            .map(|(_, entry)| {
918                entry.leases.clear();
919                entry.snapshot.leases = 0;
920                entry.stop();
921                entry.running.clone()
922            })
923            .collect()
924    }
925
926    // Forget scopes whose instances all settled; the root scope stays.
927    fn release_scopes(&mut self) -> Vec<Arc<Scope>> {
928        let ids: Vec<_> = self
929            .scopes
930            .keys()
931            .filter(|id| {
932                **id != ROOT
933                    && self
934                        .instances
935                        .iter()
936                        .filter(|(key, _)| key.scope == **id)
937                        .all(|(_, entry)| entry.running.settlement.lifecycle().is_settled())
938            })
939            .copied()
940            .collect();
941        let scopes: Vec<_> = ids.into_iter().filter_map(|id| self.scopes.remove(&id)).collect();
942        for scope in &scopes {
943            self.addresses.remove(&scope.address);
944        }
945        scopes
946    }
947}
948
949impl Entry {
950    fn stop(&mut self) {
951        self.stopping = true;
952        if let Some(health) = &mut self.snapshot.health
953            && self.running.settlement.lifecycle().status().outcome().is_none()
954        {
955            *health = ServiceStatus::Stopping;
956        }
957        self.running.stop();
958    }
959}
960
961impl Running {
962    fn stop(&self) {
963        self.settlement.lifecycle().cancel();
964    }
965
966    async fn settle(&self) -> std::result::Result<Arc<Outcome>, Arc<tokio::task::JoinError>> {
967        if let Err(failure) = self.settlement.lifecycle().settle_dependents().await {
968            return Ok(Arc::new(Err(failure)));
969        }
970        let settlement = Arc::clone(&self.settlement);
971        let report = self.report.clone();
972        self.task.settle_retry(move || settlement.retry(report)).await
973    }
974
975    pub(crate) fn lifecycle(&self) -> &Arc<Instance> {
976        self.settlement.lifecycle()
977    }
978}
979
980impl Report {
981    pub(crate) fn id(&self) -> OperationInstanceId {
982        self.id
983    }
984
985    pub(crate) fn key(&self) -> &Key {
986        &self.key
987    }
988
989    fn with_entry(&self, update: impl FnOnce(&mut Entry) -> bool) {
990        let Some(owner) = self.owner.upgrade() else {
991            return;
992        };
993        let mut state = owner.state();
994        let changed = state.instances.get_mut(&self.key).filter(|entry| entry.snapshot.instance == self.id).map(update).unwrap_or(false);
995        if changed {
996            owner.publish(&state);
997        }
998    }
999
1000    // Move the instance's lifecycle to `phase` unless it is already stopping.
1001    pub(crate) fn transition(&self, phase: Phase) {
1002        self.with_entry(|entry| {
1003            if entry.stopping && !matches!(phase, Phase::Stopping | Phase::Finalizing) {
1004                return false;
1005            }
1006            entry.running.lifecycle().transition(phase);
1007            true
1008        });
1009    }
1010
1011    // Close lease admission: a stopping instance is never joined again.
1012    pub(crate) fn quiesce(&self) {
1013        self.with_entry(|entry| {
1014            entry.stopping = true;
1015            if let Some(health) = &mut entry.snapshot.health {
1016                *health = ServiceStatus::Stopping;
1017            }
1018            true
1019        });
1020    }
1021
1022    // Publish a service's health; its lifecycle follows.
1023    pub(crate) fn health(&self, status: ServiceStatus) {
1024        self.with_entry(|entry| {
1025            if entry.stopping && !matches!(status, ServiceStatus::Stopped | ServiceStatus::Inactive { .. }) {
1026                return false;
1027            }
1028            let lifecycle = entry.running.lifecycle();
1029            match &status {
1030                ServiceStatus::Running { .. } => lifecycle.transition(Phase::Running),
1031                ServiceStatus::BackingOff => lifecycle.transition(Phase::Restarting),
1032                _ => {}
1033            }
1034            entry.snapshot.health = Some(status);
1035            true
1036        });
1037    }
1038
1039    pub(crate) fn publish(&self) {
1040        self.with_entry(|_| true);
1041    }
1042
1043    // Record the instance's outcome as its task or a cleanup retry completes.
1044    pub(crate) fn complete(&self, outcome: &Outcome) {
1045        self.with_entry(|entry| {
1046            entry.running.settlement.record(outcome);
1047            entry.snapshot.failure = outcome.as_ref().err().cloned();
1048            if let Some(health) = &mut entry.snapshot.health {
1049                if outcome.is_err() && !matches!(health, ServiceStatus::Inactive { .. }) {
1050                    *health = ServiceStatus::Inactive { cause: crate::services::ServiceExit::Panicked, history: Vec::new() };
1051                } else if outcome.is_ok() {
1052                    *health = ServiceStatus::Stopped;
1053                }
1054            }
1055            true
1056        });
1057    }
1058}
1059
1060impl Lease {
1061    fn new(running: Running, demand: u64, generation: OperationGeneration) -> Self {
1062        Self { running, demand, generation, released: false, joins_release: false }
1063    }
1064
1065    pub(crate) fn id(&self) -> OperationInstanceId {
1066        self.running.report.id
1067    }
1068
1069    pub(crate) fn generation(&self) -> OperationGeneration {
1070        self.generation
1071    }
1072
1073    pub(crate) fn lifecycle(&self) -> Arc<Instance> {
1074        Arc::clone(self.running.lifecycle())
1075    }
1076
1077    // Settle this instance; holding the lease keeps it running.
1078    pub(crate) fn settlement(&self) -> impl Future<Output = Result<()>> + Send + 'static + use<> {
1079        let running = self.running.clone();
1080        async move { settle(vec![running]).await }
1081    }
1082
1083    #[cfg(feature = "plugin-sidecar")]
1084    pub(crate) fn transition(&self, phase: Phase) {
1085        self.running.report.transition(phase);
1086    }
1087
1088    // Stop the instance for every lease holder.
1089    pub(crate) fn stop(&self) {
1090        let report = &self.running.report;
1091        let stopped = report.owner.upgrade().is_some_and(|owner| owner.stop_instance(&report.key, report.id));
1092        if !stopped {
1093            self.running.stop();
1094        }
1095    }
1096
1097    // Hold the lease until its scope retires or the runtime shuts down.
1098    pub(crate) fn detach(self) {
1099        let report = &self.running.report;
1100        let Some(owner) = report.owner.upgrade() else {
1101            return;
1102        };
1103        let scope = owner.state().scopes.get(&report.key.scope).cloned();
1104        match scope {
1105            Some(scope) => scope.detached.lock().expect("resident scope is healthy").push(self),
1106            None => drop(self),
1107        }
1108    }
1109
1110    // Release this lease and, when it was an infrastructure definition's last,
1111    // forget the definition once its instance settled.
1112    pub(crate) fn retire(&mut self) -> impl Future<Output = Result<()>> + Send + 'static + use<> {
1113        let completion = self.begin_release();
1114        let report = self.running.report.clone();
1115        let lifecycle = Arc::clone(self.running.lifecycle());
1116        async move {
1117            let result = completion.await;
1118            if !lifecycle.is_settled() {
1119                return result;
1120            }
1121            if let Some(owner) = report.owner.upgrade() {
1122                let mut state = owner.state();
1123                let owns = state.instances.get(&report.key).is_some_and(|entry| entry.snapshot.instance == report.id && entry.internal);
1124                let retired = owns.then(|| (state.instances.remove(&report.key), state.definitions.remove(&report.key.definition)));
1125                owner.publish(&state);
1126                drop(state);
1127                drop(retired);
1128            }
1129            result
1130        }
1131    }
1132
1133    pub(crate) async fn release(mut self) -> Result<()> {
1134        self.begin_release().await
1135    }
1136
1137    fn begin_release(&mut self) -> impl Future<Output = Result<()>> + Send + 'static + use<> {
1138        self.request();
1139        let running = self.joins_release.then(|| self.running.clone());
1140        async move { settle(running.into_iter().collect()).await }
1141    }
1142
1143    fn request(&mut self) -> bool {
1144        if self.released {
1145            return self.joins_release;
1146        }
1147        self.released = true;
1148        let report = &self.running.report;
1149        self.joins_release = match report.owner.upgrade() {
1150            Some(owner) => owner.release(&report.key, report.id, self.demand),
1151            None => {
1152                self.running.stop();
1153                true
1154            }
1155        };
1156        self.joins_release
1157    }
1158}
1159
1160impl Drop for Lease {
1161    fn drop(&mut self) {
1162        self.request();
1163    }
1164}
1165
1166impl Staged {
1167    pub fn commit(self) -> Result<()> {
1168        let owner = self.owner.upgrade().ok_or(Error::Closing { domain: "resident" })?;
1169        let state = owner.state();
1170        state.admit()?;
1171        if self.scope.closed.load(Ordering::Acquire) {
1172            return Err(Error::Closing { domain: "resident scope" });
1173        }
1174        for entry in &self.entries {
1175            #[cfg(any(feature = "plugin", feature = "plugin-guest", feature = "plugin-sidecar"))]
1176            if entry.plugin.as_ref().is_some_and(|scope| scope.is_closed()) {
1177                return Err(Error::Closing { domain: "plugin scope" });
1178            }
1179            if entry.closing.load(Ordering::Acquire)
1180                || !state.definitions.get(entry.definition.id()).is_some_and(|current| Arc::ptr_eq(current, entry))
1181            {
1182                return Err(Error::NotRegistered { domain: "resident reservation", name: entry.definition.id().to_owned() });
1183            }
1184        }
1185        self.published.store(true, Ordering::Release);
1186        Ok(())
1187    }
1188}
1189
1190impl Drop for Staged {
1191    fn drop(&mut self) {
1192        if self.published.load(Ordering::Acquire) {
1193            return;
1194        }
1195        let Some(owner) = self.owner.upgrade() else {
1196            return;
1197        };
1198        let mut state = owner.state();
1199        for entry in &self.entries {
1200            if state.definitions.get(entry.definition.id()).is_some_and(|current| Arc::ptr_eq(current, entry)) {
1201                state.definitions.remove(entry.definition.id());
1202            }
1203        }
1204        // self.entries retains every runner until this lock has been released.
1205    }
1206}
1207
1208impl Retirement {
1209    pub(crate) fn into_outcome(self) -> Outcome {
1210        self.outcome
1211    }
1212}
1213
1214impl Drop for Shared {
1215    fn drop(&mut self) {
1216        let state = self.state.get_mut().expect("resident registry is healthy");
1217        state.stop(|_, _, _| true);
1218    }
1219}
1220
1221/* === Public Functions === */
1222
1223/* === Private Functions === */
1224
1225pub(crate) async fn settle(running: Vec<Running>) -> Result<()> {
1226    let pending = running.into_iter().map(|running| async move {
1227        let domain = running.report.kind.domain();
1228        match running.settle().await {
1229            Ok(outcome) => outcome.as_ref().clone().map_err(|source| Error::TaskFailed { domain, source }),
1230            Err(source) => Err(Error::TaskFailed {
1231                domain: match running.report.kind {
1232                    Kind::Observer => "observer supervisor",
1233                    Kind::Service => "service supervisor",
1234                },
1235                source,
1236            }),
1237        }
1238    });
1239    supervision::settle_all(pending).await
1240}
1241
1242fn address(resources: &Resources) -> usize {
1243    Arc::as_ptr(resources) as *const () as usize
1244}
1245
1246fn validate(definition: &Definition) -> Result<()> {
1247    match definition {
1248        Definition::Observer(definition) => crate::observers::validate(definition),
1249        Definition::Service(definition) => crate::services::validate(definition),
1250    }
1251}
1252
1253/* === Tests === */