Skip to main content

vertep_runtime/
mailbox.rs

1//! Mailboxes: where messages wait for an actor, independently of its activations.
2//!
3//! A mailbox keeps messages in arrival order, which preserves the order of every single sender.
4//! [`Mailbox::receive`] hands out the oldest message that is not in flight and not deferred by
5//! the receiver's active states, and marks it in flight; [`Mailbox::ack`] removes it once its
6//! turn is durable; [`Mailbox::release`] puts it back when the turn failed, so it is delivered
7//! again. Deferred messages are skipped, never removed, so they keep their place.
8
9use std::collections::VecDeque;
10use std::sync::{Arc, Mutex, MutexGuard};
11
12use oxrdf::{Literal, Quad, Term};
13use serde::{Deserialize, Serialize};
14use vertep_core::datamodel::Event;
15use vertep_core::kernel::descriptor_matches;
16
17use crate::address::Address;
18use crate::storage::{Storage, select, text};
19use crate::vocab::{self as vt, node};
20
21/// A message on its way to an actor.
22#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
23pub struct Envelope {
24    /// The deduplication key: stable across redeliveries and retries.
25    pub message_id: String,
26    /// The receiver's address.
27    pub to: String,
28    /// The event, with origin and origin type set by the sender's transport.
29    pub event: Event,
30    /// A request's correlation id, or the correlation id a reply answers.
31    pub correlation: Option<String>,
32    /// The timer whose firing this message is.
33    pub timer: Option<String>,
34}
35
36impl Envelope {
37    /// A message `event` to `to` with message id `message_id`.
38    pub fn new(message_id: impl Into<String>, to: &Address, event: Event) -> Self {
39        Envelope {
40            message_id: message_id.into(),
41            to: to.as_str().to_owned(),
42            event,
43            correlation: None,
44            timer: None,
45        }
46    }
47}
48
49/// A mailbox failure.
50#[derive(Clone, Debug, PartialEq, Eq)]
51pub struct MailboxError(pub String);
52
53impl core::fmt::Display for MailboxError {
54    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
55        write!(f, "mailbox error: {}", self.0)
56    }
57}
58
59impl std::error::Error for MailboxError {}
60
61/// True when an event named `name` matches one of the `vt:defer` descriptors.
62pub fn is_deferred(deferred: &[String], name: &str) -> bool {
63    deferred.iter().any(|d| descriptor_matches(d, name))
64}
65
66/// The mailbox contract: enqueue, ordered receive with deferral, and acknowledge.
67// @tg: implements:: [[openspec:messaging#Pluggable Mailboxes]]
68// @tg: implements:: [[openspec:messaging#Mailbox Ordering]]
69pub trait Mailbox: Send + Sync {
70    /// Appends a message.
71    ///
72    /// # Errors
73    ///
74    /// [`MailboxError`] when the message cannot be stored.
75    fn enqueue(&self, env: Envelope) -> Result<(), MailboxError>;
76
77    /// The oldest message that is neither in flight nor deferred (its event name matches no
78    /// descriptor in `deferred`), now marked in flight.
79    fn receive(&self, deferred: &[String]) -> Option<Envelope>;
80
81    /// Removes the in-flight message `message_id` (its turn is durable).
82    ///
83    /// # Errors
84    ///
85    /// [`MailboxError`] when the removal cannot be stored.
86    fn ack(&self, message_id: &str) -> Result<(), MailboxError>;
87
88    /// Puts the in-flight message `message_id` back, to be received again.
89    fn release(&self, message_id: &str);
90
91    /// Puts every in-flight message back.
92    fn release_all(&self);
93
94    /// Every message not yet acknowledged, in arrival order.
95    fn pending(&self) -> Vec<Envelope>;
96
97    /// True when [`Mailbox::receive`] would return a message.
98    fn has_deliverable(&self, deferred: &[String]) -> bool {
99        self.pending_deliverable(deferred) > 0
100    }
101
102    /// The number of messages that are neither in flight nor deferred.
103    fn pending_deliverable(&self, deferred: &[String]) -> usize;
104
105    /// The number of messages not yet acknowledged.
106    fn len(&self) -> usize {
107        self.pending().len()
108    }
109
110    /// True when every message is acknowledged.
111    fn is_empty(&self) -> bool {
112        self.len() == 0
113    }
114}
115
116struct Row {
117    env: Envelope,
118    seq: u64,
119    in_flight: bool,
120}
121
122#[derive(Default)]
123struct Rows {
124    rows: VecDeque<Row>,
125    next: u64,
126}
127
128impl Rows {
129    fn receive(&mut self, deferred: &[String]) -> Option<Envelope> {
130        let r = self
131            .rows
132            .iter_mut()
133            .find(|r| !r.in_flight && !is_deferred(deferred, &r.env.event.name))?;
134        r.in_flight = true;
135        Some(r.env.clone())
136    }
137
138    fn remove(&mut self, id: &str) -> Option<Row> {
139        let i = self
140            .rows
141            .iter()
142            .position(|r| r.in_flight && r.env.message_id == id)
143            .or_else(|| self.rows.iter().position(|r| r.env.message_id == id))?;
144        self.rows.remove(i)
145    }
146
147    fn release(&mut self, id: &str) {
148        for r in &mut self.rows {
149            if r.env.message_id == id {
150                r.in_flight = false;
151            }
152        }
153    }
154
155    fn deliverable(&self, deferred: &[String]) -> usize {
156        self.rows
157            .iter()
158            .filter(|r| !r.in_flight && !is_deferred(deferred, &r.env.event.name))
159            .count()
160    }
161}
162
163fn lock(m: &Mutex<Rows>) -> MutexGuard<'_, Rows> {
164    m.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
165}
166
167/// An in-memory mailbox; its messages are lost with the process.
168#[derive(Default)]
169pub struct MemoryMailbox {
170    rows: Mutex<Rows>,
171}
172
173impl std::fmt::Debug for MemoryMailbox {
174    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175        f.debug_struct("MemoryMailbox")
176            .field("len", &self.len())
177            .finish()
178    }
179}
180
181impl Mailbox for MemoryMailbox {
182    fn enqueue(&self, env: Envelope) -> Result<(), MailboxError> {
183        let mut g = lock(&self.rows);
184        let seq = g.next;
185        g.next += 1;
186        g.rows.push_back(Row {
187            env,
188            seq,
189            in_flight: false,
190        });
191        Ok(())
192    }
193
194    fn receive(&self, deferred: &[String]) -> Option<Envelope> {
195        lock(&self.rows).receive(deferred)
196    }
197
198    fn ack(&self, message_id: &str) -> Result<(), MailboxError> {
199        lock(&self.rows).remove(message_id);
200        Ok(())
201    }
202
203    fn release(&self, message_id: &str) {
204        lock(&self.rows).release(message_id);
205    }
206
207    fn release_all(&self) {
208        for r in &mut lock(&self.rows).rows {
209            r.in_flight = false;
210        }
211    }
212
213    fn pending(&self) -> Vec<Envelope> {
214        lock(&self.rows)
215            .rows
216            .iter()
217            .map(|r| r.env.clone())
218            .collect()
219    }
220
221    fn pending_deliverable(&self, deferred: &[String]) -> usize {
222        lock(&self.rows).deliverable(deferred)
223    }
224}
225
226/// A mailbox kept in the actor's graph store, in graph `<address>/mailbox`: it survives
227/// restarts, and its messages are delivered again in their original order.
228// @tg: implements:: [[openspec:messaging#Mailboxes Outlive Activations]]
229pub struct StoreMailbox {
230    address: Address,
231    storage: Arc<dyn Storage>,
232    rows: Mutex<Rows>,
233}
234
235impl std::fmt::Debug for StoreMailbox {
236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237        f.debug_struct("StoreMailbox")
238            .field("address", &self.address)
239            .field("len", &self.len())
240            .finish()
241    }
242}
243
244impl StoreMailbox {
245    /// Opens the mailbox of `address`, loading the messages stored for it.
246    pub fn open(address: Address, storage: Arc<dyn Storage>) -> Self {
247        let mut rows = Rows::default();
248        let g = address.mailbox_graph();
249        let q = format!(
250            "SELECT ?seq ?env WHERE {{ GRAPH {g} {{ ?m <{seq}> ?seq ; <{env}> ?env }} }}",
251            seq = vt::SEQUENCE,
252            env = vt::ENVELOPE
253        );
254        if let Ok(sol) = select(storage.graph(address.node()), &q) {
255            let mut found: Vec<(u64, Envelope)> = Vec::new();
256            for i in 0..sol.len() {
257                let seq = sol
258                    .get(i, "seq")
259                    .and_then(text)
260                    .and_then(|s| s.parse::<u64>().ok());
261                let env = sol
262                    .get(i, "env")
263                    .and_then(text)
264                    .and_then(|s| serde_json::from_str::<Envelope>(&s).ok());
265                if let (Some(seq), Some(env)) = (seq, env) {
266                    found.push((seq, env));
267                }
268            }
269            // Rows written by other nodes sharing the store may repeat a sequence number; the
270            // message id orders those deterministically.
271            found.sort_by(|(s, a), (t, b)| s.cmp(t).then_with(|| a.message_id.cmp(&b.message_id)));
272            for (seq, env) in found {
273                rows.next = rows.next.max(seq + 1);
274                rows.rows.push_back(Row {
275                    env,
276                    seq,
277                    in_flight: false,
278                });
279            }
280        }
281        StoreMailbox {
282            address,
283            storage,
284            rows: Mutex::new(rows),
285        }
286    }
287
288    fn quads(&self, seq: u64, env: &Envelope) -> Vec<Quad> {
289        let g = self.address.mailbox_graph();
290        let json = serde_json::to_string(env).unwrap_or_default();
291        let m = self.address.mailbox_row_iri(seq, &json);
292        let q = |p: &str, o: Term| Quad::new(m.clone(), node(p), o, g.clone());
293        vec![
294            q(vt::RDF_TYPE, node(vt::MESSAGE).into()),
295            q(vt::RECIPIENT, self.address.node().clone().into()),
296            q(
297                vt::SEQUENCE,
298                Literal::new_typed_literal(seq.to_string(), oxrdf::vocab::xsd::INTEGER).into(),
299            ),
300            q(
301                vt::MESSAGE_ID,
302                Literal::new_simple_literal(&env.message_id).into(),
303            ),
304            q(vt::ENVELOPE, Literal::new_simple_literal(json).into()),
305        ]
306    }
307}
308
309impl Mailbox for StoreMailbox {
310    fn enqueue(&self, env: Envelope) -> Result<(), MailboxError> {
311        let mut g = lock(&self.rows);
312        let seq = g.next;
313        self.storage
314            .graph(self.address.node())
315            .insert_quads(&self.quads(seq, &env))
316            .map_err(|e| MailboxError(e.to_string()))?;
317        g.next += 1;
318        g.rows.push_back(Row {
319            env,
320            seq,
321            in_flight: false,
322        });
323        Ok(())
324    }
325
326    fn receive(&self, deferred: &[String]) -> Option<Envelope> {
327        lock(&self.rows).receive(deferred)
328    }
329
330    fn ack(&self, message_id: &str) -> Result<(), MailboxError> {
331        let mut g = lock(&self.rows);
332        if let Some(r) = g.remove(message_id) {
333            self.storage
334                .graph(self.address.node())
335                .remove_quads(&self.quads(r.seq, &r.env))
336                .map_err(|e| MailboxError(e.to_string()))?;
337        }
338        Ok(())
339    }
340
341    fn release(&self, message_id: &str) {
342        lock(&self.rows).release(message_id);
343    }
344
345    fn release_all(&self) {
346        for r in &mut lock(&self.rows).rows {
347            r.in_flight = false;
348        }
349    }
350
351    fn pending(&self) -> Vec<Envelope> {
352        lock(&self.rows)
353            .rows
354            .iter()
355            .map(|r| r.env.clone())
356            .collect()
357    }
358
359    fn pending_deliverable(&self, deferred: &[String]) -> usize {
360        lock(&self.rows).deliverable(deferred)
361    }
362}
363
364/// Creates the mailbox of an address.
365pub trait MailboxFactory: Send + Sync {
366    /// The mailbox of `address`.
367    fn create(&self, address: &Address) -> Arc<dyn Mailbox>;
368    /// The addresses that have stored messages, so that a restarted node delivers them.
369    fn recover(&self) -> Vec<Address> {
370        Vec::new()
371    }
372}
373
374/// In-memory mailboxes.
375#[derive(Clone, Copy, Debug, Default)]
376pub struct MemoryMailboxes;
377
378impl MailboxFactory for MemoryMailboxes {
379    fn create(&self, _address: &Address) -> Arc<dyn Mailbox> {
380        Arc::new(MemoryMailbox::default())
381    }
382}
383
384/// Store-backed mailboxes on a node's storage.
385#[derive(Clone)]
386pub struct StoreMailboxes {
387    storage: Arc<dyn Storage>,
388}
389
390impl std::fmt::Debug for StoreMailboxes {
391    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
392        f.debug_struct("StoreMailboxes").finish_non_exhaustive()
393    }
394}
395
396impl StoreMailboxes {
397    /// Mailboxes stored in `storage`.
398    pub fn new(storage: Arc<dyn Storage>) -> Self {
399        StoreMailboxes { storage }
400    }
401}
402
403impl MailboxFactory for StoreMailboxes {
404    fn create(&self, address: &Address) -> Arc<dyn Mailbox> {
405        Arc::new(StoreMailbox::open(address.clone(), self.storage.clone()))
406    }
407
408    fn recover(&self) -> Vec<Address> {
409        let q = format!(
410            "SELECT DISTINCT ?a WHERE {{ GRAPH ?g {{ ?m a <{msg}> ; <{rcpt}> ?a }} }}",
411            msg = vt::MESSAGE,
412            rcpt = vt::RECIPIENT
413        );
414        let mut out = Vec::new();
415        for g in self.storage.graphs() {
416            if let Ok(sol) = select(g, &q) {
417                for t in sol.values("a") {
418                    if let Some(a) = text(&t).and_then(|s| Address::parse(&s).ok()) {
419                        out.push(a);
420                    }
421                }
422            }
423        }
424        out.sort();
425        out.dedup();
426        out
427    }
428}
429
430#[cfg(test)]
431mod tests {
432    use super::*;
433    use vertep_core::datamodel::EventType;
434
435    fn env(id: &str, name: &str) -> Envelope {
436        Envelope::new(
437            id,
438            &Address::parse("urn:t:a").unwrap(),
439            Event::named(name, EventType::External),
440        )
441    }
442
443    #[test]
444    fn receive_skips_deferred_and_in_flight_and_keeps_order() {
445        let m = MemoryMailbox::default();
446        m.enqueue(env("1", "order.cancel")).unwrap();
447        m.enqueue(env("2", "ping")).unwrap();
448        m.enqueue(env("3", "pong")).unwrap();
449        let d = vec!["order.*".to_owned()];
450        assert_eq!(m.receive(&d).unwrap().message_id, "2");
451        assert_eq!(m.receive(&d).unwrap().message_id, "3");
452        assert!(m.receive(&d).is_none());
453        m.release("2");
454        assert_eq!(m.receive(&[]).unwrap().message_id, "1");
455        m.ack("1").unwrap();
456        assert_eq!(m.receive(&[]).unwrap().message_id, "2");
457        assert_eq!(m.len(), 2);
458    }
459}