narju/
gate.rs

1use tokio::net::{TcpListener, TcpStream};
2use tokio::sync::mpsc::{channel, Receiver, Sender};
3
4use crate::floor::host::Str;
5use crate::floor::{list, list_elements, pair, Val};
6use crate::link;
7use crate::sched::Addr;
8use crate::world::{reply, request, ConnId, Event};
9
10/// The task a node is reached at before it has said anything. A convention,
11/// because nothing else could be: an address is only learned by being told, so
12/// a node just dialled is unreachable unless one address on it is known already.
13pub const GREETER: usize = 0;
14
15fn sym(s: &str) -> Val {
16    Val::Sym(s.into())
17}
18
19fn throw(why: Val) -> Val {
20    pair(sym("throw"), why)
21}
22
23fn io(e: &std::io::Error) -> Val {
24    throw(pair(sym("io"), Str::val(e.to_string())))
25}
26
27/// `('tcp "host:port")`, or nothing this gate fronts.
28fn where_to(spec: &Val) -> Option<String> {
29    match list_elements(spec).as_deref() {
30        Some([kind, at]) if *kind == sym("tcp") => Some(Str::of(at)?.to_string()),
31        _ => None,
32    }
33}
34
35/// `('host gate)`. The only thing that opens a link, so the only thing that
36/// names one: the counter needs neither sharing nor an atomic, which is why a
37/// listener's accepts come back here instead of becoming links where they
38/// arrive. Numbering starts past the ids a launcher may have registered before
39/// the loop began ([`crate::world::World::link`]).
40pub async fn gate(mut inbox: Receiver<Val>, back: Sender<Event>) {
41    let (accepts, mut accepted) = channel::<(TcpStream, Val)>(8);
42    let mut next: ConnId = 16;
43    loop {
44        tokio::select! {
45            msg = inbox.recv() => {
46                let Some(msg) = msg else { return };
47                let Some((from, id, ask)) = request(&msg) else { continue };
48                let v = match list_elements(&ask).as_deref() {
49                    Some([verb, spec]) if *verb == sym("dial") => {
50                        match where_to(spec) {
51                            Some(at) => dial(&at, &mut next, &back).await,
52                            None => throw(pair(sym("no-transport"), spec.clone())),
53                        }
54                    }
55                    Some([verb, spec]) if *verb == sym("listen") => {
56                        match where_to(spec) {
57                            Some(at) => listen(&at, &accepts, from.clone()).await,
58                            None => throw(pair(sym("no-transport"), spec.clone())),
59                        }
60                    }
61                    _ => throw(pair(sym("bad-request"), ask)),
62                };
63                if back.send(Event::Post(from, reply(id, v))).await.is_err() {
64                    return;
65                }
66            }
67            // The gate holds a sender, so this never closes.
68            Some((sock, to)) = accepted.recv() => {
69                let peer = attach(sock, &mut next, &back).await;
70                let told = back.send(Event::Post(to, list(&[sym("peer"), peer]))).await;
71                if told.is_err() {
72                    return;
73                }
74            }
75        }
76    }
77}
78
79async fn dial(at: &str, next: &mut ConnId, back: &Sender<Event>) -> Val {
80    match TcpStream::connect(at).await {
81        Ok(sock) => attach(sock, next, back).await,
82        Err(e) => io(&e),
83    }
84}
85
86/// Bring a socket up as a link and answer the address of the greeter on it. The
87/// registration and the reply go through the one queue so the first cannot be
88/// overtaken by the second, which would answer `('no-link conn)` for a link that
89/// exists. Nothing waits for the registration to be *drained*: that would be
90/// waiting on the node this reply is for.
91async fn attach(sock: TcpStream, next: &mut ConnId, back: &Sender<Event>) -> Val {
92    let conn = *next;
93    *next += 1;
94    let out = link::bridge(conn, sock, back.clone());
95    if back.send(Event::Link(conn, out)).await.is_err() {
96        return throw(sym("no-world"));
97    }
98    Addr::Remote(conn, GREETER).val()
99}
100
101/// Answers the address bound rather than the one asked for: a port of 0 is how
102/// a caller says it does not care which, and it has to be told the answer.
103async fn listen(at: &str, accepts: &Sender<(TcpStream, Val)>, to: Val) -> Val {
104    let bound = match TcpListener::bind(at).await {
105        Ok(l) => l,
106        Err(e) => return io(&e),
107    };
108    let here = match bound.local_addr() {
109        Ok(a) => a.to_string(),
110        Err(e) => return io(&e),
111    };
112    tokio::spawn(accepting(bound, accepts.clone(), to));
113    Str::val(here)
114}
115
116/// The second arm is why this is not a bare loop: a listener otherwise outlives
117/// the world it was opened for, parked on an `accept` for a node with nothing
118/// left to hand the connection to.
119async fn accepting(bound: TcpListener, accepts: Sender<(TcpStream, Val)>, to: Val) {
120    loop {
121        tokio::select! {
122            got = bound.accept() => match got {
123                Ok((sock, _)) => {
124                    if accepts.send((sock, to.clone())).await.is_err() {
125                        return;
126                    }
127                }
128                // The listening socket itself failed, which no message can
129                // repair - a per-connection error would have been an `Ok` here.
130                Err(_) => return,
131            },
132            () = accepts.closed() => return,
133        }
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use crate::floor::machine::{Machine, NoMail, Store};
141    use crate::floor::Env;
142    use crate::sched::addr;
143    use crate::world::World;
144
145    fn val(src: &str) -> Val {
146        let Val::Code(e) = crate::surface::compile(src).expect("compiles") else {
147            panic!("not code")
148        };
149        Machine::new(Env::default(), e)
150            .run(&mut Store::<NoMail>::default())
151            .expect("no fault")
152    }
153
154    fn ok(v: Val) -> Val {
155        pair(sym("ok"), v)
156    }
157
158    #[tokio::test]
159    async fn a_node_dials_itself_and_is_answered() {
160        let mut w = World::new(100_000);
161        w.heap()
162            .spawn(val("(lambda g (_) (send (car (receive)) 'pong))"));
163        w.heap().spawn(val("(lambda t (me)
164               (begin
165                 (send '(host gate) (list 'req me 1 '(listen (tcp \"127.0.0.1:0\"))))
166                 (let ((at (car (cdr (cdr (receive))))))
167                   (begin
168                     (spawn (lambda d (dme)
169                              (begin
170                                (send '(host gate)
171                                      (list 'req dme 2 (list 'dial (list 'tcp at))))
172                                (let ((there (car (cdr (cdr (receive))))))
173                                  (begin (send there (cons dme 'hi)) (receive))))))
174                     (receive)))))"));
175
176        let (tx, rx) = channel(8);
177        w.serve("gate", tx);
178        tokio::spawn(gate(rx, w.port()));
179
180        let mut ended = w.run().await.expect("no bug");
181        ended.sort_by_key(|(id, _)| *id);
182        assert_eq!(
183            ended,
184            vec![
185                (0, ok(Val::Nil)),
186                (1, ok(list(&[sym("peer"), Addr::Remote(17, GREETER).val()]))),
187                (2, ok(sym("pong"))),
188            ]
189        );
190    }
191
192    #[tokio::test]
193    async fn a_name_is_looked_up_across_a_link_and_used() {
194        let mut t = crate::tower::Tower::load().expect("loads");
195        let mut entry = |src: &str| {
196            let forms = crate::floor::read::read(src).expect("reads");
197            t.task(&crate::surface::desugar_body(&forms).expect("desugars"))
198                .expect("no fault")
199        };
200
201        let greeter = entry(
202            "(greet self
203               (list (cons 'echo
204                           (spawn (lambda (me)
205                                    (task-loop me
206                                               (lambda h (st msg)
207                                                 (begin (reply msg 'pong)
208                                                        (cons 'stop 'ok)))
209                                               '()))))))",
210        );
211        let driver = entry(
212            "(task-loop self
213               (lambda h (st msg)
214                 (if (nil? st)
215                     (let ((at (call '(host gate) '(listen (tcp \"127.0.0.1:0\")))))
216                       (let ((d (spawn (lambda (me)
217                                         (task-loop me
218                                           (lambda h2 (st2 msg2)
219                                             (let ((there (call '(host gate)
220                                                                (list 'dial (list 'tcp at)))))
221                                               (let ((echo (call there '(lookup echo))))
222                                                 (let ((got (call echo 'ping)))
223                                                   (begin (call there '(stop))
224                                                          (cons 'stop got))))))
225                                           '())))))
226                         (begin (send d 'go) (cons 'listening at))))
227                     (cons 'stop msg)))
228               '())",
229        );
230
231        let mut w = World::new(50_000_000);
232        w.heap().spawn(greeter);
233        let drive = w.heap().spawn(driver);
234        w.heap().post(&addr(drive), sym("go"));
235
236        let (tx, rx) = channel(8);
237        w.serve("gate", tx);
238        tokio::spawn(gate(rx, w.port()));
239
240        let mut ended = w.run().await.expect("no bug");
241        ended.sort_by_key(|(id, _)| *id);
242        assert_eq!(
243            ended,
244            vec![
245                (0, ok(sym("ok"))),
246                (1, ok(list(&[sym("peer"), Addr::Remote(17, GREETER).val()]))),
247                (2, ok(sym("ok"))),
248                (3, ok(sym("pong"))),
249            ]
250        );
251    }
252
253    #[tokio::test]
254    async fn a_transport_that_is_not_fronted_is_refused() {
255        let mut w = World::new(1000);
256        let id = w.heap().spawn(val("(lambda t (me)
257               (begin (send '(host gate) (list 'req me 1 '(dial (carrier \"pigeon\"))))
258                      (receive)))"));
259
260        let (tx, rx) = channel(8);
261        w.serve("gate", tx);
262        tokio::spawn(gate(rx, w.port()));
263
264        assert_eq!(
265            w.run().await.expect("no bug"),
266            vec![(
267                id,
268                ok(list(&[
269                    sym("reply"),
270                    Val::Num(1),
271                    throw(pair(
272                        sym("no-transport"),
273                        list(&[sym("carrier"), Str::val("pigeon")])
274                    ))
275                ]))
276            )]
277        );
278    }
279}