Skip to main content

liminal_server/tap/subscription/
publish.rs

1//! The existing channel publish hook and its measured fan-out.
2
3use super::{
4    Admitted, Arc, Hop, Instant, Ordering, PublishFact, Reached, Recipient, Tap, TapError,
5    TapIdent, TapMessage, TapSource, elapsed_ms, gather, lock,
6};
7
8impl Tap {
9    /// The hook: one publish return. With no subscription it is one atomic
10    /// load and nothing else. Otherwise each matching subscription decides
11    /// under its own lock whether to keep the message before anything is
12    /// allocated for it; a kept message allocates its subject and its hops
13    /// (and its reply-to, and a payload copy under a grant), and the drainer
14    /// is woken once, after every tap lock is released. Answers whether any
15    /// subscription kept it.
16    ///
17    /// # Errors
18    /// Returns [`TapError::Poisoned`] when a tap lock is poisoned; any
19    /// subscription that kept the message before it still wakes the drainer.
20    /// Returns [`TapError::StationsDisagree`] when its station list was
21    /// refused: the message is kept, its receivers not recorded. Returns
22    /// [`TapError::HopsOutOfOrder`] when a hop was measured before the one
23    /// ahead of it: the message is kept with its hops as measured. Returns
24    /// [`TapError::HopBeforeFirstSight`] or [`TapError::HopOverflow`] when a
25    /// hop could not be carried: the message is kept without it. More than
26    /// one of these is [`TapError::Several`], each named.
27    pub fn observe(&self, fact: &PublishFact<'_>) -> Result<bool, TapError> {
28        self.observe_from(fact, None)
29    }
30
31    pub(crate) fn observe_embedded(
32        &self,
33        fact: &PublishFact<'_>,
34        host: &str,
35    ) -> Result<bool, TapError> {
36        self.observe_from(fact, Some(host))
37    }
38
39    fn observe_from(&self, fact: &PublishFact<'_>, host: Option<&str>) -> Result<bool, TapError> {
40        if self.shared.active.load(Ordering::Acquire) == 0 {
41            return Ok(false);
42        }
43        let observation = self.shared.observed.fetch_add(1, Ordering::Relaxed);
44        #[cfg(test)]
45        if fact.source == TapSource::Server && host.is_none() {
46            self.shared.observed_server.fetch_add(1, Ordering::Relaxed);
47        }
48        let mut kept = false;
49        let result = self.keep(fact, observation, &mut kept, host);
50        if kept && let Some(waker) = self.shared.waker.get() {
51            waker();
52        }
53        result.map(|()| kept)
54    }
55
56    /// The station `source` publishes as, when it is a registered worker.
57    pub(super) fn station_of(&self, source: TapSource) -> Result<Option<Arc<str>>, TapError> {
58        Ok(match source {
59            TapSource::Connection(connection) => lock(&self.shared.stations, "tap stations")?
60                .get(&connection)
61                .map(Arc::clone),
62            TapSource::Server => None,
63        })
64    }
65
66    /// The stations `recipients` belong to: each registered worker once, in
67    /// order, with the instant its first recipient was queued, in one
68    /// allocation; the shared empty list when none is one, which
69    /// allocates nothing. A first pass counts them; the second fills a slice
70    /// of exactly that length, so the shared list is built in place.
71    fn stations_of(&self, recipients: &[Recipient]) -> Result<Arc<[Reached]>, TapError> {
72        if recipients.is_empty() {
73            return Ok(Arc::clone(&self.shared.no_stations));
74        }
75        let receivers = lock(&self.shared.receivers, "tap receivers")?;
76        let stations = lock(&self.shared.stations, "tap stations")?;
77        let station_at = |index: usize| {
78            recipients
79                .get(index)
80                .and_then(|(subscriber, _)| receivers.get(subscriber))
81                .and_then(|connection| stations.get(connection))
82        };
83        // The recipient at `index` names a station no earlier one named, and
84        // was queued at the instant it carries.
85        let first_at = |index: usize| {
86            station_at(index)
87                .filter(|station| !(0..index).any(|earlier| station_at(earlier) == Some(*station)))
88                .zip(recipients.get(index).map(|(_, queued_at)| *queued_at))
89        };
90        let named = (0..recipients.len())
91            .filter(|index| first_at(*index).is_some())
92            .count();
93        let list = gather(recipients.len(), named, first_at)?
94            .unwrap_or_else(|| Arc::clone(&self.shared.no_stations));
95        drop(stations);
96        drop(receivers);
97        Ok(list)
98    }
99
100    /// A kept message's hops, in one allocation of no more than their
101    /// number (lane brief rulings 3 and 8): its publishing station at 0;
102    /// this server's depot as a durable append completed, when it keeps a
103    /// persistent store; the exchange (this node) once the message was
104    /// appended, so an append to an in-memory store sits inside its time;
105    /// then each station it reached as its first inbox queued it. No exchange
106    /// hop: nothing measured the fan-out. A publish that appended nothing
107    /// took no exchange instant, which is normal and not a failure (Jules's
108    /// F1, Hermes 07:22), so its exchange is left out, never written at 0.
109    /// Each is measured from `seen_at` and kept as measured, never rewritten
110    /// (Hermes 04:09 and 04:27). Into `failures`, when given, go the first
111    /// hop measured before first sight and the first beyond u64
112    /// milliseconds, each left out of the hops, and the first hop measured
113    /// before the hop kept ahead of it, kept; each names its place in the
114    /// chain.
115    fn hops(
116        &self,
117        fact: &PublishFact<'_>,
118        seen_at: Instant,
119        station: Option<&Arc<str>>,
120        reached: Option<&[Reached]>,
121        failures: Option<&mut Vec<TapError>>,
122    ) -> Vec<Hop> {
123        let reached = reached.unwrap_or_default();
124        let appended_at = fact.recorded.and_then(|recorded| recorded.appended_at);
125        let depot = self.depot.as_ref().zip(appended_at);
126        let mut hops = Vec::with_capacity(
127            usize::from(station.is_some())
128                + usize::from(depot.is_some())
129                + usize::from(appended_at.is_some())
130                + reached.len(),
131        );
132        let mut early = None;
133        let mut overflow = None;
134        let mut out_of_order = None;
135        let mut place = 0;
136        let mut pass = |node: &Arc<str>, at: Option<Instant>| {
137            let hop = place;
138            place += 1;
139            let at_ms = match at {
140                None => 0,
141                Some(at) if at < seen_at => {
142                    early.get_or_insert_with(|| {
143                        elapsed_ms(seen_at.duration_since(at))
144                            .map_or(TapError::HopOverflow { hop }, |early_ms| {
145                                TapError::HopBeforeFirstSight { hop, early_ms }
146                            })
147                    });
148                    return;
149                }
150                Some(at) => {
151                    let Some(at_ms) = elapsed_ms(at.duration_since(seen_at)) else {
152                        overflow.get_or_insert(TapError::HopOverflow { hop });
153                        return;
154                    };
155                    at_ms
156                }
157            };
158            if let Some(before) = hops.last().map(|kept: &Hop| kept.at_ms)
159                && at_ms < before
160            {
161                out_of_order.get_or_insert(TapError::HopsOutOfOrder {
162                    hop,
163                    at_ms,
164                    before_ms: before,
165                });
166            }
167            hops.push(Hop {
168                node: Arc::clone(node),
169                at_ms,
170            });
171        };
172        if let Some(station) = station {
173            pass(station, None);
174        }
175        if let Some((depot, appended_at)) = depot {
176            pass(depot, Some(appended_at));
177        }
178        if let Some(appended_at) = appended_at {
179            pass(&self.node, Some(appended_at));
180        }
181        for reached in reached {
182            pass(&reached.station, Some(reached.queued_at));
183        }
184        if let Some(failures) = failures {
185            failures.extend([early, overflow, out_of_order].into_iter().flatten());
186        }
187        hops
188    }
189
190    fn keep(
191        &self,
192        fact: &PublishFact<'_>,
193        observation: u64,
194        kept: &mut bool,
195        host: Option<&str>,
196    ) -> Result<(), TapError> {
197        let mut seen_at = None;
198        // Looked up once, and only when a subscription keeps the message.
199        let mut station = None;
200        let mut to_stations = None;
201        // A station list refused, and hops that could not be kept as
202        // measured, are each reported once the message is kept (Buttercup's
203        // V3): empty, this allocates nothing.
204        let mut failures = Vec::new();
205        let mut hops_reported = false;
206        let subscribers = lock(&self.shared.subscribers, "tap subscribers")?;
207        for subscriber in subscribers.iter() {
208            if !subscriber.filter.matches(fact.subject) {
209                continue;
210            }
211            let mut state = lock(&subscriber.state, "tap subscriber")?;
212            if !state.admits() {
213                continue;
214            }
215            let one_in = state.one_in;
216            if station.is_none() {
217                station = Some(match host {
218                    Some(host) => Some(Arc::from(format!("liminal.{host}"))),
219                    None => self.station_of(fact.source)?,
220                });
221                to_stations = Some(
222                    match fact
223                        .recorded
224                        .map(|recorded| self.stations_of(recorded.recipients))
225                    {
226                        Some(Ok(list)) => Some(list),
227                        // Kept as not recorded, and the refusal reported.
228                        Some(Err(error @ TapError::StationsDisagree { .. })) => {
229                            failures.push(error);
230                            None
231                        }
232                        Some(Err(error)) => return Err(error),
233                        None => None,
234                    },
235                );
236            }
237            // First sight is receipt (lane brief ruling 6), or now when the
238            // publish recorded nothing.
239            let first_sight = *seen_at.get_or_insert_with(|| {
240                fact.recorded
241                    .map_or_else(Instant::now, |recorded| recorded.received_at)
242            });
243            // Every keeping subscription's hops are the same; their failures
244            // are taken once.
245            let hops = self.hops(
246                fact,
247                first_sight,
248                station.as_ref().and_then(Option::as_ref),
249                to_stations.as_ref().and_then(Option::as_deref),
250                (!hops_reported).then_some(&mut failures),
251            );
252            hops_reported = true;
253            let message = TapMessage {
254                ident: fact
255                    .message_id
256                    .map_or(TapIdent::Observation(observation), TapIdent::Message),
257                subject: fact.subject.to_owned(),
258                size: u64::try_from(fact.payload.len()).unwrap_or(u64::MAX),
259                outcome: fact.outcome,
260                seen_at: first_sight,
261                source: fact.source,
262                station: station.clone().flatten(),
263                to_stations: to_stations.clone().flatten(),
264                hops,
265                reply_to: fact.reply_to.map(str::to_owned),
266                held: false,
267                call: None,
268                payload: subscriber.grant.map(|_| fact.payload.to_vec()),
269            };
270            if state.ring.push(Admitted { one_in, message }) {
271                self.shared.dropped_total.fetch_add(1, Ordering::Relaxed);
272            }
273            state.after_push();
274            drop(state);
275            *kept = true;
276        }
277        drop(subscribers);
278        match failures.len() {
279            0 => Ok(()),
280            1 => failures.pop().map_or(Ok(()), Err),
281            _ => Err(TapError::Several(failures)),
282        }
283    }
284}