narju/
world.rs

1use std::collections::HashMap;
2
3use tokio::sync::mpsc::{channel, Receiver, Sender, WeakSender};
4
5use crate::floor::machine::Fault;
6use crate::floor::{list, list_elements, pair, rc_val, Val};
7use crate::link::{self, Frame};
8use crate::sched::{self, addr, Addr, Heap, Id};
9use crate::wire;
10
11fn sym(s: &str) -> Val {
12    Val::Sym(s.into())
13}
14
15/// Local: the same peer is a different number seen from the other end.
16pub type ConnId = u64;
17
18/// One queue carries everything arriving from outside the heap, so a quiescent
19/// node waits on a single receiver rather than selecting over a set that
20/// changes as peers come and go.
21pub enum Event {
22    Post(Val, Val),
23    /// A connection established while the loop runs, which is every one a node
24    /// accepts rather than makes; the counterpart before it starts is
25    /// [`World::link`].
26    Link(ConnId, Sender<Frame>),
27    /// The only liveness edge a node has about a far side.
28    Down(ConnId),
29    /// A frame addressed to the node rather than a task in it
30    /// ([`crate::link::NODE`]):
31    ///
32    /// ```text
33    /// ('watch id)         watch a task of yours for me
34    /// ('down  id result)  it ended, and this is what with
35    /// ```
36    ///
37    /// Not a service reached through the greeter, because a table held by a
38    /// task is lost when the task restarts with no event to say so.
39    Node(ConnId, Val),
40    /// A host peer's adapter has dropped the receiving end. Raised by a waiter
41    /// the loop spawns, so a peer ending is an edge rather than a question.
42    Gone(String),
43}
44
45/// `('host name)`, deliberately the same shape as a task's: a call site cannot
46/// tell a peer written in Rust from one in the object language.
47pub fn host(name: &str) -> Val {
48    list(&[sym("host"), sym(name)])
49}
50
51/// `('req from id body)` is what the prelude's `call` sends. An adapter answers
52/// it exactly as a task would, which is why `call` works against Rust.
53pub fn request(msg: &Val) -> Option<(Val, Val, Val)> {
54    match list_elements(msg)?.as_slice() {
55        [tag, from, id, body] if *tag == sym("req") => {
56            Some((from.clone(), id.clone(), body.clone()))
57        }
58        _ => None,
59    }
60}
61
62/// `('reply id v)`, the answer to a request.
63pub fn reply(id: Val, v: Val) -> Val {
64    list(&[sym("reply"), id, v])
65}
66
67/// `noconnection` rather than an exit value, because no exit was seen: the link
68/// going is this node losing the ability to hear about the far task, not the
69/// far task ending. Distinct from `noproc`, a task known to have ended.
70fn lost(conn: ConnId, id: Id) -> Val {
71    let why = pair(sym("throw"), sym("noconnection"));
72    sched::down(Addr::Remote(conn, id).val(), why)
73}
74
75/// `noproc` and no value, the same answer a monitor of an already-ended task
76/// gets. Nothing re-registers a peer, so an adapter that has returned is gone
77/// for good and the distinction `noconnection` draws does not arise.
78fn gone(target: Val) -> Val {
79    sched::down(target, pair(sym("throw"), sym("noproc")))
80}
81
82/// The id needs no translating: it was written in the far node's numbering by
83/// whoever handed the address out.
84fn watch_of(id: Id) -> Val {
85    list(&[sym("watch"), Val::Num(id as i64)])
86}
87
88fn down_of(id: Id, result: Val) -> Val {
89    list(&[sym("down"), Val::Num(id as i64), result])
90}
91
92/// One heap and the peers it can reach.
93pub struct World {
94    heap: Heap,
95    hosts: HashMap<String, Sender<Val>>,
96    links: HashMap<ConnId, Sender<Frame>>,
97    /// `conn -> [(watcher, far id)]`. Kept here because the death this node can
98    /// observe is the link's, and the links are here.
99    watches: HashMap<ConnId, Vec<(Id, Id)>>,
100    /// One waiter is spawned per name watched, not per watcher.
101    peer_watches: HashMap<String, Vec<Id>>,
102    /// Tasks here that links asked to be told about, the other way round from
103    /// `watches`.
104    remote_watches: HashMap<Id, Vec<ConnId>>,
105    /// Template for [`World::port`], dropped when the loop starts so the inbox
106    /// is open exactly while some adapter or link still holds a clone.
107    port: Option<Sender<Event>>,
108    /// The same way in, held weakly so the loop does not keep itself alive.
109    /// Failing to upgrade means nothing is left that could answer this world.
110    back: WeakSender<Event>,
111    inbox: Receiver<Event>,
112}
113
114impl World {
115    pub fn new(budget: usize) -> World {
116        let (port, inbox) = channel(64);
117        let back = port.downgrade();
118        World {
119            heap: Heap::new(budget),
120            hosts: HashMap::new(),
121            links: HashMap::new(),
122            watches: HashMap::new(),
123            peer_watches: HashMap::new(),
124            remote_watches: HashMap::new(),
125            port: Some(port),
126            back,
127            inbox,
128        }
129    }
130
131    pub fn heap(&mut self) -> &mut Heap {
132        &mut self.heap
133    }
134
135    /// Register a peer under `name`, reached from the object language as
136    /// `('host name)`. This end knows only where to put a message.
137    pub fn serve(&mut self, name: &str, tx: Sender<Val>) {
138        self.hosts.insert(name.to_string(), tx);
139    }
140
141    /// A link established before the loop starts; the counterpart for one
142    /// established while it runs is [`Event::Link`].
143    pub fn link(&mut self, conn: ConnId, tx: Sender<Frame>) {
144        self.links.insert(conn, tx);
145    }
146
147    /// Every live clone holds the world open, so an adapter that has finished
148    /// must drop its own.
149    pub fn port(&self) -> Sender<Event> {
150        self.port.clone().expect("the world has not started")
151    }
152
153    /// Run until nothing can wake the heap again, answering with the
154    /// tasks that ended and what they ended with.
155    pub async fn run(mut self) -> Result<Vec<(Id, Val)>, Fault> {
156        self.port = None;
157        let mut ended = Vec::new();
158        loop {
159            self.heap.run()?;
160            let exits = self.heap.take_exits();
161            // Before the await, or a task waiting on a reply to the message it
162            // just sent would wait for it to be sent. Watches first, since a
163            // watch is itself a frame and must not be overtaken by the message
164            // it was set for - watching early cannot lose a death, late can.
165            let mut woke = false;
166            for (watcher, target) in self.heap.take_watches() {
167                woke |= self.watch(watcher, target).await;
168            }
169            for (to, msg) in self.heap.take_away() {
170                woke |= self.forward(to, msg).await;
171            }
172            // After those and never before, the order the heap uses for its own
173            // watchers: a death that overtook an answer would report
174            // `callee-down` for a call that was in fact answered.
175            for (id, result) in &exits {
176                self.ended(*id, result).await;
177            }
178            ended.extend(exits);
179            if self.heap.idle() {
180                return Ok(ended);
181            }
182            // The world answers some addresses itself, so a task can be
183            // runnable again with nothing having arrived from outside.
184            if woke {
185                continue;
186            }
187            match self.inbox.recv().await {
188                Some(Event::Post(to, msg)) => self.heap.post(&to, msg),
189                Some(Event::Link(conn, tx)) => {
190                    self.links.insert(conn, tx);
191                }
192                Some(Event::Down(conn)) => {
193                    self.links.remove(&conn);
194                    for (watcher, id) in self.watches.remove(&conn).unwrap_or_default() {
195                        self.heap.post(&addr(watcher), lost(conn, id));
196                    }
197                    self.remote_watches.retain(|_, conns| {
198                        conns.retain(|c| *c != conn);
199                        !conns.is_empty()
200                    });
201                }
202                Some(Event::Node(conn, body)) => self.node(conn, body).await,
203                Some(Event::Gone(name)) => {
204                    self.hosts.remove(&name);
205                    for watcher in self.peer_watches.remove(&name).unwrap_or_default() {
206                        self.heap.post(&addr(watcher), gone(host(&name)));
207                    }
208                }
209                None => return Ok(ended),
210            }
211        }
212    }
213
214    /// Record a watch on something outside the heap, answering `true` if it was
215    /// answered at once - which is what makes a watch race-free to set. Same
216    /// dispatch as [`World::forward`]: the two must agree on what an address
217    /// names.
218    async fn watch(&mut self, watcher: Id, target: Val) -> bool {
219        if let Some(Addr::Remote(conn, id)) = Addr::of(&target) {
220            let (conn, id) = (*conn, *id);
221            if !self.links.contains_key(&conn) {
222                self.heap.post(&addr(watcher), lost(conn, id));
223                return true;
224            }
225            // Both: the far node reports the task ending, the link dying is
226            // what is left when there is no far node to ask. An answered watch
227            // leaves the table, so the two cannot both fire.
228            let waiting = self.watches.entry(conn).or_default();
229            let first = !waiting.iter().any(|(_, far)| *far == id);
230            waiting.push((watcher, id));
231            if first {
232                self.forward_to_link(conn, link::NODE, watch_of(id)).await;
233            }
234            return false;
235        }
236        match list_elements(&target).as_deref() {
237            Some([tag, Val::Sym(name)]) if *tag == sym("host") => {
238                let name = name.to_string();
239                self.watch_host(watcher, name)
240            }
241            // An address this node cannot route names nothing that could die,
242            // so there is nothing further to learn.
243            _ => {
244                self.heap.post(&addr(watcher), gone(target));
245                true
246            }
247        }
248    }
249
250    /// The edge is the adapter dropping the receiving end, which `Sender::closed`
251    /// waits on, so one waiter per name serves any number of watchers and
252    /// nothing is ever polled.
253    ///
254    /// The waiter holds a port deliberately: it lives exactly as long as the
255    /// adapter it watches, and lets go when the loop's inbox closes, dropping
256    /// its clone of the peer's sender. An adapter parked on `recv` would
257    /// otherwise never see its own inbox close and would outlive the world.
258    fn watch_host(&mut self, watcher: Id, name: String) -> bool {
259        let peer = self.hosts.get(&name).cloned();
260        let (Some(peer), Some(back)) = (peer, self.back.upgrade()) else {
261            self.heap.post(&addr(watcher), gone(host(&name)));
262            return true;
263        };
264        let watchers = self.peer_watches.entry(name.clone()).or_default();
265        let first = watchers.is_empty();
266        watchers.push(watcher);
267        if first {
268            tokio::spawn(async move {
269                tokio::select! {
270                    () = peer.closed() => {
271                        let _ = back.send(Event::Gone(name)).await;
272                    }
273                    () = back.closed() => {}
274                }
275            });
276        }
277        false
278    }
279
280    /// Nothing here reports whether the heap woke: the loop reaches this only
281    /// after deciding to wait, so a mailbox is drained on the next round.
282    async fn node(&mut self, conn: ConnId, body: Val) {
283        match list_elements(&body).as_deref() {
284            Some([verb, Val::Num(n)]) if *verb == sym("watch") => {
285                let id = *n as Id;
286                if self.heap.alive(id) {
287                    self.remote_watches.entry(id).or_default().push(conn);
288                } else {
289                    // The round trip is the window, so a watch arriving late
290                    // would otherwise be one that is never answered.
291                    let why = pair(sym("throw"), sym("noproc"));
292                    self.forward_to_link(conn, link::NODE, down_of(id, why))
293                        .await;
294                }
295            }
296            Some([verb, Val::Num(n), result]) if *verb == sym("down") => {
297                let id = *n as Id;
298                let target = Addr::Remote(conn, id).val();
299                let mut waiting = self.watches.remove(&conn).unwrap_or_default();
300                waiting.retain(|(watcher, far)| {
301                    if *far != id {
302                        return true;
303                    }
304                    let told = sched::down(target.clone(), result.clone());
305                    self.heap.post(&addr(*watcher), told);
306                    false
307                });
308                if !waiting.is_empty() {
309                    self.watches.insert(conn, waiting);
310                }
311            }
312            // Dropped rather than refused: the frame is from a node, not a
313            // caller, so there is nothing to refuse it to.
314            _ => {}
315        }
316    }
317
318    /// The exit value crosses as an ordinary payload, so a watcher over a link
319    /// learns what a local one would. When it cannot encode, the death is
320    /// still reported: a watcher not told waits for ever, which is worse than
321    /// being told the result could not come with it.
322    async fn ended(&mut self, id: Id, result: &Val) {
323        let Some(conns) = self.remote_watches.remove(&id) else {
324            return;
325        };
326        for conn in conns {
327            let carried = match link::departs(result, conn) {
328                true => result.clone(),
329                false => pair(sym("throw"), sym("third-party-address")),
330            };
331            self.forward_to_link(conn, link::NODE, down_of(id, carried))
332                .await;
333        }
334    }
335
336    /// Hand a message to the peer its address names, answering `true` if the
337    /// answer went back into the heap rather than out of it. An address naming
338    /// nothing is answered rather than dropped when it carries a request: a
339    /// caller that can never be replied to should learn so at once.
340    async fn forward(&mut self, to: Val, msg: Val) -> bool {
341        if let Some(Addr::Remote(conn, id)) = Addr::of(&to) {
342            return self.forward_to_link(*conn, *id as u64, msg).await;
343        }
344        match list_elements(&to).as_deref() {
345            Some([tag, Val::Sym(name)]) if *tag == sym("host") => {
346                let name = name.to_string();
347                self.forward_to_host(&name, msg).await
348            }
349            _ => self.undeliverable(&msg, sym("not-local")),
350        }
351    }
352
353    async fn forward_to_host(&mut self, name: &str, msg: Val) -> bool {
354        let Some(tx) = self.hosts.get(name) else {
355            return self.undeliverable(&msg, pair(sym("no-host"), sym(name)));
356        };
357        if tx.send(msg.clone()).await.is_err() {
358            self.hosts.remove(name);
359            return self.undeliverable(&msg, pair(sym("no-host"), sym(name)));
360        }
361        false
362    }
363
364    /// `send` refuses a non-data payload at the primitive, so a failure to
365    /// encode here is a bug and is reported as one. An address naming a third
366    /// node is a program's error and is answered.
367    async fn forward_to_link(&mut self, conn: ConnId, to: u64, msg: Val) -> bool {
368        let Some(tx) = self.links.get(&conn) else {
369            return self.undeliverable(&msg, pair(sym("no-link"), Val::Num(conn as i64)));
370        };
371        if !link::departs(&msg, conn) {
372            return self.undeliverable(&msg, sym("third-party-address"));
373        }
374        let body = wire::encode(&msg).expect("a payload is data");
375        if tx.send(Frame { to, body }).await.is_err() {
376            self.links.remove(&conn);
377            return self.undeliverable(&msg, pair(sym("no-link"), Val::Num(conn as i64)));
378        }
379        false
380    }
381
382    fn undeliverable(&mut self, msg: &Val, why: Val) -> bool {
383        let Some((from, id, _)) = request(msg) else {
384            return false;
385        };
386        let thrown = Val::Pair(rc_val(sym("throw")), rc_val(why));
387        self.heap.post(&from, reply(id, thrown));
388        true
389    }
390}
391
392#[cfg(test)]
393mod tests {
394    use super::*;
395    use crate::floor::host::Str;
396    use crate::floor::machine::{Machine, NoMail, Store};
397    use crate::floor::Env;
398
399    fn val(src: &str) -> Val {
400        let Val::Code(e) = crate::surface::compile(src).expect("compiles") else {
401            panic!("not code")
402        };
403        Machine::new(Env::default(), e)
404            .run(&mut Store::<NoMail>::default())
405            .expect("no fault")
406    }
407
408    fn ok(v: Val) -> Val {
409        pair(sym("ok"), v)
410    }
411
412    #[tokio::test]
413    async fn a_world_runs_a_heap_that_needs_nothing() {
414        let mut w = World::new(1000);
415        let id = w.heap().spawn(val("(lambda t (_) (+ 1 2))"));
416        assert_eq!(w.run().await.expect("no bug"), vec![(id, ok(Val::Num(3)))]);
417    }
418
419    #[tokio::test]
420    async fn a_world_gives_up_when_nothing_could_wake_it() {
421        let mut w = World::new(1000);
422        w.heap().spawn(val("(lambda t (_) (receive 0))"));
423        assert_eq!(w.run().await.expect("no bug"), vec![]);
424    }
425
426    #[tokio::test]
427    async fn a_message_crosses_a_link() {
428        let (near, far) = tokio::io::duplex(64);
429
430        let mut b = World::new(1000);
431        let there = b.heap().spawn(val("(lambda t (_) (receive))"));
432        b.link(0, crate::link::bridge(0, far, b.port()));
433
434        let mut a = World::new(1000);
435        // The second `receive` is never answered: it keeps this side up
436        // until the far side is done, so the frame is written rather than
437        // dropped with the world that queued it.
438        let here = a
439            .heap()
440            .spawn(val("(lambda t (_) (begin (send (receive) 'hi) (receive)))"));
441        a.heap().post(&addr(here), Addr::Remote(0, there).val());
442        a.link(0, crate::link::bridge(0, near, a.port()));
443
444        let (ran_a, ran_b) = tokio::join!(a.run(), b.run());
445        assert_eq!(ran_a.expect("no bug"), vec![]);
446        assert_eq!(ran_b.expect("no bug"), vec![(there, ok(sym("hi")))]);
447    }
448
449    #[tokio::test]
450    async fn a_reply_finds_its_way_back_across_a_link() {
451        let (near, far) = tokio::io::duplex(64);
452
453        let mut b = World::new(1000);
454        let there = b
455            .heap()
456            .spawn(val("(lambda t (_) (send (car (receive)) 'pong))"));
457        b.link(0, crate::link::bridge(0, far, b.port()));
458
459        let mut a = World::new(1000);
460        let here = a
461            .heap()
462            .spawn(val("(lambda t (me) (begin (send (receive) (cons me 'ping))
463                                              (receive)))"));
464        a.heap().post(&addr(here), Addr::Remote(0, there).val());
465        a.link(0, crate::link::bridge(0, near, a.port()));
466
467        let (ran_a, ran_b) = tokio::join!(a.run(), b.run());
468        assert_eq!(ran_a.expect("no bug"), vec![(here, ok(sym("pong")))]);
469        assert_eq!(ran_b.expect("no bug"), vec![(there, ok(Val::Nil))]);
470    }
471
472    #[tokio::test]
473    async fn an_address_for_a_third_node_does_not_cross() {
474        let (near, _far) = tokio::io::duplex(64);
475        let mut w = World::new(1000);
476        let id = w.heap().spawn(val("(lambda t (me)
477               (let ((m (receive)))
478                 (begin (send (car m) (list 'req me 7 (cdr m))) (receive))))"));
479        w.heap().post(
480            &addr(id),
481            pair(Addr::Remote(0, 1).val(), Addr::Remote(1, 2).val()),
482        );
483        w.link(0, crate::link::bridge(0, near, w.port()));
484        assert_eq!(
485            w.run().await.expect("no bug"),
486            vec![(
487                id,
488                ok(list(&[
489                    sym("reply"),
490                    Val::Num(7),
491                    pair(sym("throw"), sym("third-party-address"))
492                ]))
493            )]
494        );
495    }
496
497    #[tokio::test]
498    async fn a_call_across_a_link_that_is_not_there_is_answered() {
499        let mut w = World::new(1000);
500        let id = w.heap().spawn(val("(lambda t (me)
501               (begin (send (receive) (list 'req me 7 'anything))
502                      (receive)))"));
503        w.heap().post(&addr(id), Addr::Remote(7, 1).val());
504        assert_eq!(
505            w.run().await.expect("no bug"),
506            vec![(
507                id,
508                ok(list(&[
509                    sym("reply"),
510                    Val::Num(7),
511                    pair(sym("throw"), pair(sym("no-link"), Val::Num(7)))
512                ]))
513            )]
514        );
515    }
516
517    #[tokio::test]
518    async fn a_watch_across_a_link_fires_when_the_link_dies() {
519        let (near, far) = tokio::io::duplex(64);
520        let mut w = World::new(1000);
521        let id = w
522            .heap()
523            .spawn(val("(lambda t (_) (begin (monitor (receive)) (receive)))"));
524        w.heap().post(&addr(id), Addr::Remote(0, 1).val());
525        w.link(0, crate::link::bridge(0, near, w.port()));
526        drop(far);
527        assert_eq!(
528            w.run().await.expect("no bug"),
529            vec![(
530                id,
531                ok(sched::down(
532                    Addr::Remote(0, 1).val(),
533                    pair(sym("throw"), sym("noconnection"))
534                ))
535            )]
536        );
537    }
538
539    #[tokio::test]
540    async fn a_watch_across_a_link_fires_when_the_far_task_ends() {
541        let (near, far) = tokio::io::duplex(64);
542
543        let mut b = World::new(1000);
544        let there = b.heap().spawn(val("(lambda t (_) (receive))"));
545        b.link(0, crate::link::bridge(0, far, b.port()));
546
547        let mut a = World::new(1000);
548        let here = a.heap().spawn(val("(lambda t (_) (let ((to (receive)))
549                             (begin (monitor to) (send to 'bye) (receive))))"));
550        a.heap().post(&addr(here), Addr::Remote(0, there).val());
551        a.link(0, crate::link::bridge(0, near, a.port()));
552
553        let (ran_a, ran_b) = tokio::join!(a.run(), b.run());
554        assert_eq!(
555            ran_a.expect("no bug"),
556            vec![(
557                here,
558                ok(sched::down(Addr::Remote(0, there).val(), ok(sym("bye"))))
559            )]
560        );
561        assert_eq!(ran_b.expect("no bug"), vec![(there, ok(sym("bye")))]);
562    }
563
564    #[tokio::test]
565    async fn a_watch_on_a_far_task_that_has_ended_is_answered() {
566        let (near, far) = tokio::io::duplex(64);
567
568        let mut b = World::new(1000);
569        // Ends before anything is said to it, so the watch cannot be in
570        // time however the two loops interleave.
571        let there = b.heap().spawn(val("(lambda t (_) 'done)"));
572        // And something to keep that node up long enough to be asked. A
573        // node whose last task ended is finished, and would answer by
574        // dropping the link instead - which is `noconnection`, and true,
575        // but not what is being tested.
576        b.heap().spawn(val("(lambda t (_) (receive))"));
577        b.link(0, crate::link::bridge(0, far, b.port()));
578
579        let mut a = World::new(1000);
580        let here = a
581            .heap()
582            .spawn(val("(lambda t (_) (begin (monitor (receive)) (receive)))"));
583        a.heap().post(&addr(here), Addr::Remote(0, there).val());
584        a.link(0, crate::link::bridge(0, near, a.port()));
585
586        let (ran_a, ran_b) = tokio::join!(a.run(), b.run());
587        assert_eq!(ran_b.expect("no bug"), vec![(there, ok(sym("done")))]);
588        assert_eq!(
589            ran_a.expect("no bug"),
590            vec![(
591                here,
592                ok(sched::down(
593                    Addr::Remote(0, there).val(),
594                    pair(sym("throw"), sym("noproc"))
595                ))
596            )]
597        );
598    }
599
600    #[tokio::test]
601    async fn a_watch_across_a_link_that_is_not_there_is_answered() {
602        let mut w = World::new(1000);
603        let id = w
604            .heap()
605            .spawn(val("(lambda t (_) (begin (monitor (receive)) (receive)))"));
606        w.heap().post(&addr(id), Addr::Remote(7, 1).val());
607        assert_eq!(
608            w.run().await.expect("no bug"),
609            vec![(
610                id,
611                ok(sched::down(
612                    Addr::Remote(7, 1).val(),
613                    pair(sym("throw"), sym("noconnection"))
614                ))
615            )]
616        );
617    }
618
619    #[tokio::test]
620    async fn a_syscall_is_a_send_and_a_receive() {
621        let mut w = World::new(1000);
622        let id = w.heap().spawn(val("(lambda t (me)
623               (begin (send '(host upper) (cons me 'ping)) (receive)))"));
624
625        let (tx, mut rx) = channel(8);
626        w.serve("upper", tx);
627        let back = w.port();
628        tokio::spawn(async move {
629            let msg = rx.recv().await.expect("a request");
630            let Val::Pair(reply_to, payload) = msg else {
631                panic!("not a request")
632            };
633            let answer = pair(sym("pong"), Val::clone(&payload));
634            back.send(Event::Post(Val::clone(&reply_to), answer))
635                .await
636                .expect("open");
637        });
638
639        assert_eq!(
640            w.run().await.expect("no bug"),
641            vec![(id, ok(pair(sym("pong"), sym("ping"))))]
642        );
643    }
644
645    #[tokio::test]
646    async fn a_call_to_an_adapter_that_ends_raises_at_the_call_site() {
647        let mut t = crate::tower::Tower::load().expect("loads");
648        let forms = crate::floor::read::read(
649            "(task-loop self
650                        (lambda h (st msg)
651                          (cons 'stop (attempt (lambda () (call '(host quiet) 'hi)))))
652                        0)",
653        )
654        .expect("reads");
655        let entry = t
656            .task(&crate::surface::desugar_body(&forms).expect("desugars"))
657            .expect("no fault");
658
659        let mut w = World::new(1_000_000);
660        let id = w.heap().spawn(entry);
661        w.heap().post(&addr(id), sym("go"));
662
663        let (tx, mut rx) = channel(8);
664        w.serve("quiet", tx);
665        tokio::spawn(async move {
666            rx.recv().await.expect("a request");
667        });
668
669        let why = pair(
670            sym("callee-down"),
671            pair(host("quiet"), pair(sym("throw"), sym("noproc"))),
672        );
673        assert_eq!(
674            w.run().await.expect("no bug"),
675            vec![(id, ok(pair(sym("throw"), why)))]
676        );
677    }
678
679    #[tokio::test]
680    async fn an_object_task_calls_a_host_adapter() {
681        let mut t = crate::tower::Tower::load().expect("loads");
682        let forms = crate::floor::read::read(
683            "(task-loop self
684                        (lambda h (st msg) (cons 'stop (call '(host echo) 'hi)))
685                        0)",
686        )
687        .expect("reads");
688        let entry = t
689            .task(&crate::surface::desugar_body(&forms).expect("desugars"))
690            .expect("no fault");
691
692        let mut w = World::new(100_000);
693        let id = w.heap().spawn(entry);
694        w.heap().post(&addr(id), sym("go"));
695
696        let (tx, mut rx) = channel(8);
697        w.serve("echo", tx);
698        let back = w.port();
699        tokio::spawn(async move {
700            let msg = rx.recv().await.expect("a request");
701            let (from, rid, body) = request(&msg).expect("a request");
702            back.send(Event::Post(from, reply(rid, pair(sym("heard"), body))))
703                .await
704                .expect("open");
705        });
706
707        assert_eq!(
708            w.run().await.expect("no bug"),
709            vec![(id, ok(pair(sym("heard"), sym("hi"))))]
710        );
711    }
712
713    #[tokio::test]
714    async fn a_protected_region_can_still_suspend() {
715        let mut t = crate::tower::Tower::load().expect("loads");
716        let forms = crate::floor::read::read(
717            "(task-loop self
718                        (lambda h (st msg)
719                          (cons 'stop (attempt (lambda ()
720                                        (call '(host echo) 'hi)))))
721                        0)",
722        )
723        .expect("reads");
724        let entry = t
725            .task(&crate::surface::desugar_body(&forms).expect("desugars"))
726            .expect("no fault");
727
728        let mut w = World::new(100_000);
729        let id = w.heap().spawn(entry);
730        w.heap().post(&addr(id), sym("go"));
731
732        let (tx, mut rx) = channel(8);
733        w.serve("echo", tx);
734        let back = w.port();
735        tokio::spawn(async move {
736            let msg = rx.recv().await.expect("a request");
737            let (from, rid, body) = request(&msg).expect("a request");
738            back.send(Event::Post(from, reply(rid, pair(sym("heard"), body))))
739                .await
740                .expect("open");
741        });
742
743        assert_eq!(
744            w.run().await.expect("no bug"),
745            vec![(id, ok(pair(sym("ok"), pair(sym("heard"), sym("hi")))))]
746        );
747    }
748
749    #[tokio::test]
750    async fn a_compiled_caller_calls_a_compiled_callee_rather_than_unfolding_it() {
751        let mut t = crate::tower::Tower::load().expect("loads");
752        // Both `clambda`s run, so `down` is a compiled function by the time
753        // `go` stages a call to it. Unfolding one is unfolding a recursion,
754        // and the turn limit is what says so if this regresses.
755        let forms = crate::floor::read::read(
756            "(limit-turns self 100)
757             (define down (clambda down (n) (if (< n 1) 0 (down (- n 1)))))
758             (define go (clambda go (n) (down n)))
759             (go 5)",
760        )
761        .expect("reads");
762        let entry = t.script(&forms).expect("no fault");
763
764        let mut w = World::new(10_000);
765        let id = w.heap().spawn(entry);
766        w.heap().post(&addr(id), sym("run"));
767
768        assert_eq!(w.run().await.expect("no bug"), vec![(id, ok(Val::Num(0)))]);
769    }
770
771    #[tokio::test]
772    async fn a_file_runs_as_a_task_and_can_still_call() {
773        let mut t = crate::tower::Tower::load().expect("loads");
774        let forms = crate::floor::read::read(
775            "(define (twice x) (* 2 x))
776             (call '(host echo) (twice 21))",
777        )
778        .expect("reads");
779        let entry = t.script(&forms).expect("no fault");
780
781        let mut w = World::new(100_000);
782        let id = w.heap().spawn(entry);
783        w.heap().post(&addr(id), sym("run"));
784
785        let (tx, mut rx) = channel(8);
786        w.serve("echo", tx);
787        let back = w.port();
788        tokio::spawn(async move {
789            let msg = rx.recv().await.expect("a request");
790            let (from, rid, body) = request(&msg).expect("a request");
791            back.send(Event::Post(from, reply(rid, body)))
792                .await
793                .expect("open");
794        });
795
796        assert_eq!(w.run().await.expect("no bug"), vec![(id, ok(Val::Num(42)))]);
797    }
798
799    #[tokio::test]
800    async fn a_file_body_is_not_re_entered_by_a_message_that_arrives_during_it() {
801        use std::sync::atomic::{AtomicUsize, Ordering};
802        use std::sync::Arc;
803
804        let mut t = crate::tower::Tower::load().expect("loads");
805        let forms = crate::floor::read::read(
806            "(define a (call '(host echo) 1))
807             (define b (call '(host echo) 2))
808             (+ a b)",
809        )
810        .expect("reads");
811        let entry = t.script(&forms).expect("no fault");
812
813        let mut w = World::new(100_000);
814        let id = w.heap().spawn(entry);
815        w.heap().post(&addr(id), sym("run"));
816        w.heap().post(&addr(id), sym("poke"));
817
818        let (tx, mut rx) = channel(8);
819        w.serve("echo", tx);
820        let back = w.port();
821        let seen = Arc::new(AtomicUsize::new(0));
822        let count = seen.clone();
823        tokio::spawn(async move {
824            while let Some(msg) = rx.recv().await {
825                count.fetch_add(1, Ordering::SeqCst);
826                let (from, rid, body) = request(&msg).expect("a request");
827                back.send(Event::Post(from, reply(rid, body)))
828                    .await
829                    .expect("open");
830            }
831        });
832
833        assert_eq!(w.run().await.expect("no bug"), vec![(id, ok(Val::Num(3)))]);
834        assert_eq!(seen.load(Ordering::SeqCst), 2);
835    }
836
837    #[tokio::test]
838    async fn a_module_is_loaded_by_name_and_imported() {
839        assert_eq!(
840            loading(
841                "(import (load \"arith.naj\"))
842                 (down (up 5))",
843                &[(
844                    "arith.naj",
845                    "(define base 10)
846                     (list (cons 'up (lambda u (n) (+ n base)))
847                           (cons 'down (lambda d (n) (- n base))))",
848                )],
849            )
850            .await,
851            ok(Val::Num(5))
852        );
853    }
854
855    #[tokio::test]
856    async fn a_module_may_load_another() {
857        assert_eq!(
858            loading(
859                "(import (load \"outer.naj\"))
860                 (twice 21)",
861                &[
862                    (
863                        "outer.naj",
864                        "(import (load \"inner.naj\"))
865                         (list (cons 'twice (lambda t (n) (add n n))))",
866                    ),
867                    ("inner.naj", "(list (cons 'add (lambda a (x y) (+ x y))))"),
868                ],
869            )
870            .await,
871            ok(Val::Num(42))
872        );
873    }
874
875    #[tokio::test]
876    async fn a_module_cannot_see_the_scope_it_is_imported_into() {
877        let v = loading(
878            "(define secret 7)
879             (import (load \"peek.naj\"))
880             (get)",
881            &[("peek.naj", "(list (cons 'get (lambda g () secret)))")],
882        )
883        .await;
884        assert_eq!(v, pair(sym("throw"), pair(sym("unbound"), sym("secret"))));
885    }
886
887    #[tokio::test]
888    async fn a_call_answered_with_a_throw_raises_at_the_call_site() {
889        assert_eq!(
890            loading("(load \"missing.naj\")", &[]).await,
891            pair(sym("throw"), pair(sym("io"), Str::val("no such file")))
892        );
893    }
894
895    #[tokio::test]
896    async fn a_failed_call_is_catchable() {
897        assert_eq!(
898            loading(
899                "(attempt (lambda () (load \"missing.naj\")))",
900                &[("other.naj", "'()")],
901            )
902            .await,
903            ok(pair(
904                sym("throw"),
905                pair(sym("io"), Str::val("no such file"))
906            ))
907        );
908    }
909
910    const ROOT: &str = "/store";
911
912    #[tokio::test]
913    async fn a_module_is_resolved_through_the_lock_file() {
914        assert_eq!(
915            loading(
916                "(import (need 'arith))
917                 (down (up 5))",
918                &[
919                    (
920                        "naj.lock",
921                        "((narju-lock 1)
922                          (modules
923                           (arith (url \"https://forge/thorn/naj-arith\")
924                                  (rev \"a1b2c3\")
925                                  (path \"forge/thorn/naj-arith\")
926                                  (file \"arith.naj\"))))",
927                    ),
928                    (
929                        "/store/forge/thorn/naj-arith/a1b2c3/arith.naj",
930                        "(define base 10)
931                         (list (cons 'up (lambda u (n) (+ n base)))
932                               (cons 'down (lambda d (n) (- n base))))",
933                    ),
934                ],
935            )
936            .await,
937            ok(Val::Num(5))
938        );
939    }
940
941    #[tokio::test]
942    async fn a_module_the_lock_does_not_carry_is_refused() {
943        assert_eq!(
944            loading(
945                "(attempt (lambda () (need 'missing)))",
946                &[("naj.lock", "((narju-lock 1) (modules))")],
947            )
948            .await,
949            ok(pair(
950                sym("throw"),
951                list(&[sym("no-such-module"), sym("missing")])
952            ))
953        );
954    }
955
956    #[tokio::test]
957    async fn a_locked_module_needs_another_through_its_own_lock() {
958        assert_eq!(
959            loading(
960                "(import (need 'outer))
961                 (twice 21)",
962                &[
963                    (
964                        "naj.lock",
965                        "((narju-lock 1)
966                          (modules
967                           (outer (url \"https://f/t/outer\") (rev \"r1\")
968                                  (path \"f/t/outer\") (file \"o.naj\"))))",
969                    ),
970                    (
971                        "/store/f/t/outer/r1/naj.lock",
972                        "((narju-lock 1)
973                          (modules
974                           (inner (url \"https://f/t/inner\") (rev \"r2\")
975                                  (path \"f/t/inner\") (file \"i.naj\"))))",
976                    ),
977                    (
978                        "/store/f/t/outer/r1/o.naj",
979                        "(import (need 'inner))
980                         (list (cons 'twice (lambda t (n) (add n n))))",
981                    ),
982                    (
983                        "/store/f/t/inner/r2/i.naj",
984                        "(list (cons 'add (lambda a (x y) (+ x y))))",
985                    ),
986                ],
987            )
988            .await,
989            ok(Val::Num(42))
990        );
991    }
992
993    #[tokio::test]
994    async fn two_modules_may_mean_different_things_by_one_name() {
995        assert_eq!(
996            loading(
997                "(import (need 'left))
998                 (import (need 'right))
999                 (list (l) (r))",
1000                &[
1001                    (
1002                        "naj.lock",
1003                        "((narju-lock 1)
1004                          (modules
1005                           (left (url \"https://f/left\") (rev \"r1\")
1006                                 (path \"f/left\") (file \"m.naj\"))
1007                           (right (url \"https://f/right\") (rev \"r1\")
1008                                  (path \"f/right\") (file \"m.naj\"))))",
1009                    ),
1010                    (
1011                        "/store/f/left/r1/naj.lock",
1012                        "((narju-lock 1)
1013                          (modules
1014                           (dep (url \"https://f/one\") (rev \"a\")
1015                                (path \"f/one\") (file \"d.naj\"))))",
1016                    ),
1017                    (
1018                        "/store/f/right/r1/naj.lock",
1019                        "((narju-lock 1)
1020                          (modules
1021                           (dep (url \"https://f/two\") (rev \"b\")
1022                                (path \"f/two\") (file \"d.naj\"))))",
1023                    ),
1024                    (
1025                        "/store/f/left/r1/m.naj",
1026                        "(import (need 'dep))
1027                         (list (cons 'l (lambda l () (it))))",
1028                    ),
1029                    (
1030                        "/store/f/right/r1/m.naj",
1031                        "(import (need 'dep))
1032                         (list (cons 'r (lambda r () (it))))",
1033                    ),
1034                    ("/store/f/one/a/d.naj", "(list (cons 'it (lambda i () 1)))",),
1035                    ("/store/f/two/b/d.naj", "(list (cons 'it (lambda i () 2)))",),
1036                ],
1037            )
1038            .await,
1039            ok(list(&[Val::Num(1), Val::Num(2)]))
1040        );
1041    }
1042
1043    #[tokio::test]
1044    async fn a_module_may_be_more_than_one_file() {
1045        assert_eq!(
1046            loading(
1047                "(import (need 'lib))
1048                 (top)",
1049                &[
1050                    (
1051                        "naj.lock",
1052                        "((narju-lock 1)
1053                          (modules
1054                           (lib (url \"https://f/lib\") (rev \"r1\")
1055                                (path \"f/lib\") (file \"src/m.naj\"))))",
1056                    ),
1057                    (
1058                        "/store/f/lib/r1/naj.lock",
1059                        "((narju-lock 1)
1060                          (modules
1061                           (dep (url \"https://f/dep\") (rev \"a\")
1062                                (path \"f/dep\") (file \"d.naj\"))))",
1063                    ),
1064                    (
1065                        "/store/f/lib/r1/src/m.naj",
1066                        "(import (load \"src/helper.naj\"))
1067                         (list (cons 'top (lambda t () (helped))))",
1068                    ),
1069                    (
1070                        "/store/f/lib/r1/src/helper.naj",
1071                        "(import (need 'dep))
1072                         (list (cons 'helped (lambda h () (it))))",
1073                    ),
1074                    ("/store/f/dep/a/d.naj", "(list (cons 'it (lambda i () 7)))"),
1075                ],
1076            )
1077            .await,
1078            ok(Val::Num(7))
1079        );
1080    }
1081
1082    #[tokio::test]
1083    async fn a_module_with_no_lock_of_its_own_cannot_need() {
1084        assert_eq!(
1085            loading(
1086                "(attempt (lambda () (need 'solo)))",
1087                &[
1088                    (
1089                        "naj.lock",
1090                        "((narju-lock 1)
1091                          (modules
1092                           (solo (url \"https://f/solo\") (rev \"r1\")
1093                                 (path \"f/solo\") (file \"s.naj\"))))",
1094                    ),
1095                    (
1096                        "/store/f/solo/r1/s.naj",
1097                        "(import (need 'anything))
1098                         (list)",
1099                    ),
1100                ],
1101            )
1102            .await,
1103            ok(pair(
1104                sym("throw"),
1105                pair(sym("io"), Str::val("no such file"))
1106            ))
1107        );
1108    }
1109
1110    #[tokio::test]
1111    async fn a_lock_from_a_later_format_is_refused() {
1112        assert_eq!(
1113            loading(
1114                "(attempt (lambda () (need 'x)))",
1115                &[("naj.lock", "((narju-lock 2) (modules))")],
1116            )
1117            .await,
1118            ok(pair(
1119                sym("throw"),
1120                list(&[sym("lock-version"), Val::Num(2)])
1121            ))
1122        );
1123    }
1124
1125    async fn loading(src: &str, files: &[(&str, &str)]) -> Val {
1126        let mut t = crate::tower::Tower::load().expect("loads");
1127        let forms = crate::floor::read::read(src).expect("reads");
1128        let entry = t.script(&forms).expect("no fault");
1129
1130        let mut w = World::new(10_000_000);
1131        let id = w.heap().spawn(entry);
1132        w.heap().post(&addr(id), sym("run"));
1133
1134        let (tx, mut rx) = channel(8);
1135        w.serve("files", tx);
1136        let back = w.port();
1137        let table: Vec<(String, String)> = files
1138            .iter()
1139            .map(|(p, s)| ((*p).to_string(), (*s).to_string()))
1140            .collect();
1141        tokio::spawn(async move {
1142            while let Some(msg) = rx.recv().await {
1143                let (from, rid, body) = request(&msg).expect("a request");
1144                let asked = list_elements(&body).expect("a list");
1145                let v = match asked.as_slice() {
1146                    [verb] if *verb == sym("root") => Str::val(ROOT),
1147                    [verb, path] if *verb == sym("read") => {
1148                        let want = Str::of(path).expect("a path");
1149                        match table.iter().find(|(p, _)| p == want) {
1150                            Some((_, text)) => Str::val(text.clone()),
1151                            None => pair(sym("throw"), pair(sym("io"), Str::val("no such file"))),
1152                        }
1153                    }
1154                    _ => panic!("unexpected {body}"),
1155                };
1156                if back.send(Event::Post(from, reply(rid, v))).await.is_err() {
1157                    return;
1158                }
1159            }
1160        });
1161
1162        let ended = w.run().await.expect("no bug");
1163        assert_eq!(ended.len(), 1);
1164        ended[0].1.clone()
1165    }
1166
1167    #[tokio::test]
1168    async fn the_prompt_carries_its_environment_from_line_to_line() {
1169        let said = console(
1170            &[
1171                "(define (double x) (* 2 x))",
1172                "(double 21)",
1173                "",
1174                "(car 3)",
1175                "\"hi\"",
1176            ],
1177            100_000,
1178        )
1179        .await;
1180        assert_eq!(
1181            said,
1182            ["'double", "42", "('error 'wrong-type . 'car)", "\"hi\""]
1183        );
1184    }
1185
1186    #[tokio::test]
1187    async fn the_prompt_takes_an_import_a_line_at_a_time() {
1188        let said = console(
1189            &["(import (list (cons 'z 9) (cons 'w 4)))", "(+ z w)"],
1190            100_000,
1191        )
1192        .await;
1193        assert_eq!(said, ["('z 'w)", "13"]);
1194    }
1195
1196    async fn console(lines: &[&str], budget: usize) -> Vec<String> {
1197        use std::sync::{Arc, Mutex};
1198
1199        let mut t = crate::tower::Tower::load().expect("loads");
1200        let entry = t.repl().expect("no fault");
1201
1202        let mut w = World::new(budget);
1203        let id = w.heap().spawn(entry);
1204        w.heap().post(&addr(id), sym("go"));
1205
1206        let (tx, mut rx) = channel(8);
1207        w.serve("stdin", tx.clone());
1208        w.serve("stdout", tx);
1209        let back = w.port();
1210        let said = Arc::new(Mutex::new(Vec::new()));
1211        let out = Arc::clone(&said);
1212        let mut script = lines.iter().map(|s| s.to_string()).collect::<Vec<_>>();
1213        script.reverse();
1214
1215        tokio::spawn(async move {
1216            while let Some(msg) = rx.recv().await {
1217                let (from, rid, body) = request(&msg).expect("a request");
1218                let v = match list_elements(&body).as_deref() {
1219                    Some([verb, _]) if *verb == sym("read") => match script.pop() {
1220                        Some(line) => Str::val(line),
1221                        None => sym("eof"),
1222                    },
1223                    Some([verb, arg]) if *verb == sym("line") => {
1224                        out.lock()
1225                            .expect("no panic")
1226                            .push(Str::of(arg).map_or_else(|| arg.to_string(), str::to_string));
1227                        sym("ok")
1228                    }
1229                    _ => panic!("unexpected {body}"),
1230                };
1231                if back.send(Event::Post(from, reply(rid, v))).await.is_err() {
1232                    return;
1233                }
1234            }
1235        });
1236
1237        assert_eq!(w.run().await.expect("no bug"), vec![(id, ok(sym("bye")))]);
1238        let held = said.lock().expect("no panic");
1239        held.clone()
1240    }
1241
1242    #[tokio::test]
1243    async fn a_call_to_a_peer_that_is_not_there_is_answered() {
1244        let mut w = World::new(1000);
1245        let id = w.heap().spawn(val("(lambda t (me)
1246               (begin (send '(host nowhere) (list 'req me 7 'anything))
1247                      (receive)))"));
1248        assert_eq!(
1249            w.run().await.expect("no bug"),
1250            vec![(
1251                id,
1252                ok(list(&[
1253                    sym("reply"),
1254                    Val::Num(7),
1255                    pair(sym("throw"), pair(sym("no-host"), sym("nowhere")))
1256                ]))
1257            )]
1258        );
1259    }
1260}