Skip to main content

harmos_runtime/operations/
resident.rs

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