Skip to main content

somatize_fabric_broker/
reaching.rs

1//! One host, reached through a broker, standing in the engine's `Transport`
2//! hole.
3//!
4//! The seam: above it the engine says *carry this slice to `w1`* and knows
5//! nothing else; below it a broker was asked where `w1` is and a wire was opened
6//! to whatever it said. Neither half learns about the other.
7//!
8//! Thin on purpose. Everything true across hosts — where each one turned out to
9//! be, which of them are the same place — belongs to the
10//! [`Session`](crate::Session), the only thing that sees more than one at a
11//! time. What is left here is one name, what is staged for it, and the wire once
12//! there is one.
13//!
14//! **The connection waits until somebody sends work.** A graph names hosts a run
15//! may never reach, so this opens nothing when it is built; the rendezvous may
16//! already have happened, but a rendezvous is tens of bytes and a connection is
17//! a socket or a process. The visible consequence: **an unreachable host now
18//! fails when it is needed rather than when it is named.**
19//!
20//! Packing an artifact is expensive and happens up front, because a worker has
21//! **one** catalog and half of one is a different catalog. Those two look like
22//! they conflict and do not: [`Reaching::offering`] *stages* the artifact, and
23//! the bytes only move inside the wire's own greeting, on the first dispatch —
24//! so the same artifact twice does nothing, and changing one out from under an
25//! open session fails rather than swapping a catalog with live state in it.
26//!
27//! A [`Reply::Met`](crate::Reply::Met) can carry a `good_for` and **nothing here
28//! enforces it**: no broker issues one today, so enforcing it would be a
29//! mechanism with no tenant. The day one does, the enforcement is this type's —
30//! it is the only thing that knows when the rendezvous was granted.
31
32use crate::{Host, Session};
33use somatize_core::{Cargo, Outcome, Plan, Transport, TransportError, Watcher};
34use somatize_fabric_wire::{Artifact, Worker};
35use std::sync::{Arc, Mutex, MutexGuard};
36
37/// One host, reached through a broker.
38pub struct Reaching {
39    /// The conversation this host is reached through. Shared: one session
40    /// serves every host of a run, greets once for all of them, and is what
41    /// notices that two of them are the same place.
42    session: Arc<Session>,
43    /// The name the graph gave it. Every message about this rendezvous names
44    /// it, including the one sent when this is dropped.
45    host: Host,
46    /// What to provision the far side with, staged until there is a wire to
47    /// stage it on. `None` is a worker that brings its own catalog.
48    carries: Mutex<Option<(Artifact, String)>>,
49    /// The wire, once there is one. Behind a lock because [`Transport`] is
50    /// `Sync` and two branches of a wave arrive here at the same time — the
51    /// first through opens it and the second finds it open.
52    open: Mutex<Option<Arc<Worker>>>,
53}
54
55impl Reaching {
56    /// A host that will be connected to through this session when somebody
57    /// needs it.
58    pub fn new(session: Arc<Session>, host: Host) -> Self {
59        Self {
60            session,
61            host,
62            carries: Mutex::new(None),
63            open: Mutex::new(None),
64        }
65    }
66
67    /// The name this reaches.
68    pub fn host(&self) -> &Host {
69        &self.host
70    }
71
72    /// Tells it what to provision the far side with, before the first job.
73    ///
74    /// Staged if the wire is not open yet, handed straight over if it is — and
75    /// either way the far side only receives it if it asks, because the wire
76    /// announces an artifact's **name** and sends the bytes on request.
77    pub fn offering(
78        &self,
79        artifact: Artifact,
80        runtime: impl Into<String>,
81    ) -> Result<(), TransportError> {
82        let open = locked(&self.open);
83        if let Some(worker) = open.as_ref() {
84            // Open session: the wire owns this rule and enforces it, including
85            // the refusal to swap a catalog with live state behind it.
86            return worker.offering(artifact, runtime);
87        }
88        drop(open);
89
90        let mut carries = locked(&self.carries);
91        // Nothing has greeted anybody yet, so replacing is free — and being
92        // handed the same one twice is still nothing, which is what a graph run
93        // in pieces does.
94        match carries.as_ref() {
95            Some((already, _)) if already.id == artifact.id => Ok(()),
96            _ => {
97                *carries = Some((artifact, runtime.into()));
98                Ok(())
99            }
100        }
101    }
102
103    /// The wire to this host, opening it if this is the first work for it.
104    fn wire(&self) -> Result<Arc<Worker>, TransportError> {
105        let mut open = locked(&self.open);
106        if let Some(worker) = open.as_ref() {
107            return Ok(Arc::clone(worker));
108        }
109        let worker = self
110            .session
111            .wire(&self.host, locked(&self.carries).clone())?;
112        *open = Some(Arc::clone(&worker));
113        Ok(worker)
114    }
115}
116
117impl Transport for Reaching {
118    /// The connection on the first call, and after that this is the wire's
119    /// dispatch with one `Arc` clone in front of it.
120    fn dispatch(
121        &self,
122        plan: &Plan,
123        cargo: &Cargo<'_>,
124        seen: Option<&dyn Watcher>,
125    ) -> Result<Outcome, TransportError> {
126        self.wire()?.dispatch(plan, cargo, seen)
127    }
128}
129
130impl Drop for Reaching {
131    /// Lets the rendezvous go, so that no client has to remember to.
132    ///
133    /// Only one that was taken: a handle nobody sent work to never held
134    /// anything. Nothing fails if this does not arrive, which is why the failure
135    /// is swallowed — a run that finished is not a run to report an error from.
136    fn drop(&mut self) {
137        if locked(&self.open).is_some() {
138            self.session.done(&self.host);
139        }
140    }
141}
142
143fn locked<T>(what: &Mutex<T>) -> MutexGuard<'_, T> {
144    match what.lock() {
145        Ok(one) => one,
146        Err(poisoned) => poisoned.into_inner(),
147    }
148}