Skip to main content

macula_rust/pool/
serve.rs

1//! Serving through the pool: a procedure served on every link the pool
2//! holds and every link it dials later, each advertising it naming its own
3//! station, until stopped. An org procedure is served only in a realm the
4//! pool pins a key for; one in the node's own namespace needs none.
5
6use std::collections::HashMap;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::{Arc, Mutex, MutexGuard, Weak};
9use std::time::Duration;
10
11use crate::frame::StreamMode;
12use crate::station_link::{
13    self, Handler, Link, LinkError, StreamHandler, StreamOffer, DEFAULT_CALL_TIMEOUT,
14};
15
16use super::{Pool, PoolError, PoolInner};
17
18static NEXT_SERVED: AtomicU64 = AtomicU64::new(1);
19
20/// A procedure the node serves: its realm, which the pool must pin a key for
21/// unless the procedure is in the node's own namespace, its name, and
22/// exactly one of a unary handler and a stream offer.
23#[derive(Clone)]
24pub struct Offer {
25    pub realm: [u8; 32],
26    pub procedure: String,
27    pub handler: Option<Handler>,
28    pub stream: Option<StreamOffer>,
29}
30
31impl Offer {
32    /// A unary procedure.
33    pub fn unary(realm: [u8; 32], procedure: &str, handler: Handler) -> Offer {
34        Offer {
35            realm,
36            procedure: procedure.to_string(),
37            handler: Some(handler),
38            stream: None,
39        }
40    }
41
42    /// A streaming procedure of `mode`.
43    pub fn stream(
44        realm: [u8; 32],
45        procedure: &str,
46        mode: StreamMode,
47        handler: StreamHandler,
48    ) -> Offer {
49        Offer {
50            realm,
51            procedure: procedure.to_string(),
52            handler: None,
53            stream: Some(StreamOffer { mode, handler }),
54        }
55    }
56}
57
58/// A procedure the node serves on every link the pool holds, and on every
59/// link it dials later, until [`Served::stop`].
60pub struct Served {
61    inner: Arc<ServedInner>,
62}
63
64pub(super) struct ServedInner {
65    id: u64,
66    pool: Weak<PoolInner>,
67    offer: station_link::Offer,
68    held: Mutex<Held>,
69}
70
71struct Held {
72    stopped: bool,
73    on_links: HashMap<u64, station_link::Served>,
74}
75
76impl Pool {
77    /// Serves `o` on every link that is up, and on every link that comes up
78    /// after. It succeeds when one link serves it; each link advertises it
79    /// naming its own station, and renews it and puts it in the DHT as
80    /// [`Link::serve`] does.
81    pub async fn serve(&self, o: Offer) -> Result<Served, PoolError> {
82        let realm_key = self.inner.realm_key_for(&o.realm, &o.procedure)?;
83        let served = Arc::new(ServedInner {
84            id: NEXT_SERVED.fetch_add(1, Ordering::Relaxed),
85            pool: Arc::downgrade(&self.inner),
86            offer: station_link::Offer {
87                realm: o.realm,
88                procedure: o.procedure,
89                handler: o.handler,
90                stream: o.stream,
91                realm_key,
92            },
93            held: Mutex::new(Held {
94                stopped: false,
95                on_links: HashMap::new(),
96            }),
97        });
98        let links = self.inner.links();
99        if links.is_empty() {
100            return Err(PoolError::NoLink(Vec::new()));
101        }
102        let mut errors = Vec::new();
103        for link in &links {
104            if let Err(e) = served.serve_on(link).await {
105                errors.push(e);
106            }
107        }
108        if errors.len() == links.len() {
109            return Err(PoolError::NotServed(errors));
110        }
111        {
112            let mut state = self.inner.lock();
113            if state.closed {
114                return Err(PoolError::Closed);
115            }
116            state.served.insert(served.id, served.clone());
117        }
118        for link in self.inner.links() {
119            served.attach(link);
120        }
121        Ok(Served { inner: served })
122    }
123}
124
125impl PoolInner {
126    /// Gives a new link the node's subscriptions, then its served
127    /// procedures, as macula's pool replays them on a respawned link.
128    pub(super) async fn replay(&self, link: &Link) {
129        let (subs, served) = {
130            let state = self.lock();
131            (
132                state.subs.values().cloned().collect::<Vec<_>>(),
133                state.served.values().cloned().collect::<Vec<_>>(),
134            )
135        };
136        for sub in subs {
137            sub.attach(link).await;
138        }
139        for s in served {
140            s.attach(link.clone());
141        }
142    }
143}
144
145impl Served {
146    /// Withdraws the procedure on every link.
147    pub async fn stop(&self) -> Result<(), LinkError> {
148        if let Some(pool) = self.inner.pool.upgrade() {
149            pool.lock().served.remove(&self.inner.id);
150        }
151        let on_links = {
152            let mut held = self.inner.lock();
153            if held.stopped {
154                return Ok(());
155            }
156            held.stopped = true;
157            std::mem::take(&mut held.on_links)
158        };
159        let mut result = Ok(());
160        for on_link in on_links.into_values() {
161            if let Err(e) = on_link.stop().await {
162                result = Err(e);
163            }
164        }
165        result
166    }
167}
168
169impl ServedInner {
170    fn lock(&self) -> MutexGuard<'_, Held> {
171        self.held.lock().unwrap_or_else(|p| p.into_inner())
172    }
173
174    fn respawn_delay(&self) -> Option<Duration> {
175        self.pool.upgrade().map(|p| p.opts.respawn_delay)
176    }
177
178    /// Serves the offer on `link`, once, and watches it there.
179    async fn serve_on(self: &Arc<Self>, link: &Link) -> Result<(), LinkError> {
180        {
181            let held = self.lock();
182            if held.stopped || held.on_links.contains_key(&link.serial()) {
183                return Ok(());
184            }
185        }
186        let on_link = match link.serve(self.offer.clone()).await {
187            Err(LinkError::AlreadyServed) => return Ok(()),
188            other => other?,
189        };
190        {
191            let mut held = self.lock();
192            if !held.stopped {
193                held.on_links.insert(link.serial(), on_link.clone());
194                drop(held);
195                tokio::spawn(watch(self.clone(), link.clone(), on_link));
196                return Ok(());
197            }
198        }
199        on_link.stop().await
200    }
201
202    /// Serves the offer on a link the pool dialed, trying again every
203    /// respawn delay while the link lives and the offer is not served there.
204    pub(super) fn attach(self: &Arc<Self>, link: Link) {
205        let served = self.clone();
206        tokio::spawn(async move {
207            loop {
208                let outcome =
209                    tokio::time::timeout(DEFAULT_CALL_TIMEOUT, served.serve_on(&link)).await;
210                if matches!(outcome, Ok(Ok(()))) {
211                    return;
212                }
213                let Some(delay) = served.respawn_delay() else {
214                    return;
215                };
216                tokio::select! {
217                    _ = link.done() => return,
218                    _ = tokio::time::sleep(delay) => {}
219                }
220            }
221        });
222    }
223}
224
225/// Forgets a link's serving when it ends, and serves the offer there again
226/// when it lapsed while the link lives.
227async fn watch(served: Arc<ServedInner>, link: Link, on_link: station_link::Served) {
228    let why = on_link.done().await;
229    let stopped = {
230        let mut held = served.lock();
231        held.on_links.remove(&link.serial());
232        held.stopped
233    };
234    if stopped || why == LinkError::Stopped || link.error().is_some() {
235        return;
236    }
237    let Some(delay) = served.respawn_delay() else {
238        return;
239    };
240    tokio::select! {
241        _ = link.done() => {}
242        _ = tokio::time::sleep(delay) => served.attach(link.clone()),
243    }
244}