1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
// TODO: All of the examples here are also inside of the ./examples folder because rustdoc doesn't
//       work with the target wasm32-wasi (https://github.com/rust-lang/cargo/issues/7040) at the
//       moment, but we still want to make sure this code compiles. This duplication is hard to
//       keep in sync and as soon as this issue is closed we should remove the duplicates and rely
//       just on the docs for examples.

#![allow(clippy::needless_doctest_main)]
/*!
Helper library for building Rust applications that run on [lunatic][1].

# Main concepts

The main abstraction in [lunatic][1] is a [`Process`](crate::process::Process). Contrary to
operating system processes, lunatic processes are lightweight and fast to spawn. They are
designed for **massive** concurrency.

Processes can be spawned from just a function:
```
use lunatic::{process, Mailbox};

#[lunatic::main]
fn main(m: Mailbox<()>) {
    // Get reference to itself.
    let this = process::this(&m);
    // Pass the reference to the child process.
    process::spawn_with(this, |parent, _: Mailbox<()>| {
        println!("Hi! I'm a process.");
        // Notify parent that we are done.
        parent.send(());
    })
    .unwrap();
    // Wait for child to finish. If this line was missing the main process could shut down
    // before the child prints anything. If the main process finishes, all others are killed.
    m.receive();
}
```

One important characteristic of processes is that they are sandboxed. Each of them gets a separate
memory and they can't access any memory from the parent, not even through raw pointer access. If we
need to pass any information to the newly spawned process we can do it through a `context`:
```
use lunatic::{process, Mailbox};

let proc = "Process";
process::spawn_with(proc.to_string(), |proc, _: Mailbox<()>| {
    // This closure gets a new heap and stack to execute on,
    // and can't access the memory of the parent process.
    println!("Hello {}!", proc);
})
.unwrap();
```

## Messaging

Processes can exchange information with each other through messages:

```
use lunatic::process;

let proc = process::spawn(|mailbox| {
    let message = mailbox.receive();
    println!("Hello {}", message);
})
.unwrap();

proc.send("World!".to_string());
```
Everything that implements the **[`Serialize`](serde::Serialize)** and
**[`Deserialize`](serde::Deserialize)** traits can be sent as a message to another process.

Each process gets a [`Mailbox`] as an argument to the entry function. Mailboxes can be used to
[`receive`](Mailbox::receive()) messages. If there are no messages in the mailbox the process
will block on [`receive`](Mailbox::receive()) until a message arrives.

## Request/Reply architecture

It's common in lunatic to have processes that act as servers, they receive requests and send back
replys. Such an architecture can be achieved if the client process sends a reference to itself as
part of the message. The server then will be able to send the response back to the correct client.
Because this is such a common construct, this library provides a helper type [`Request`] that
automatically captures a reference to the sender

```
use lunatic::{process, Mailbox, Request};

#[lunatic::main]
fn main(_: Mailbox<()>) {
    // Spawn a process that gets two numbers as a request and can reply to the sender with
    // the sum of the numbers.
    let add_server = process::spawn(|mailbox: Mailbox<Request<(i32, i32), i32>>| loop {
        let request = mailbox.receive().unwrap();
        let (a, b) = *request.data();
        request.reply(a + b);
    })
    .unwrap();
    // Make specific requests to the `add_server` & ignore all messages in the mailbox that
    // are not responses to the request.
    assert_eq!(add_server.request((1, 1)).unwrap(), 2);
    assert_eq!(add_server.request((1, 2)).unwrap(), 3);
}
```

It's important to notice here that the response can be a different type (`i32`) from the mailbox
type (`()`). This is safe, because the call to the `request` function will block until we get back
a response and handle it right away, so that the different type never ends up in the mailbox.

## Linking

Processes can be linked together. This means that if one of them fails, all the ones linked to
it will get notified. A linked process can be spawned with the [`process::spawn_link`] function.
The function will take the current [`Mailbox`] and return a [`LinkMailbox`], that will also
receive notifications about linked processes. If we would like to automatically fail as soon as
one of the linked processes fails, we can turn the [`LinkMailbox`] back to a regular one with
the `panic_if_link_panics()` function.

```
use lunatic::{process, Mailbox};

#[lunatic::main]
fn main(mailbox: Mailbox<()>) {
    let (_child, _tag, link_mailbox) = process::spawn_link(mailbox, child).unwrap();
    // Wait on message
    assert!(link_mailbox.receive().is_err());
}

fn child(_: Mailbox<()>) {
    panic!("Error");
}

```

## Sandboxing

A [`Environment`] can define characteristics that processes spawned into it have. The environment
can limit:

* Memory usage
* Compute usage
* WebAssembly host function (syscalls) access

An `Environment` is configured through a [`Config`].

```
use lunatic::{Config, Environment, Mailbox};

#[lunatic::main]
fn main(m: Mailbox<()>) {
    // Create a new environment where processes can use maximum 17 Wasm pages of
    // memory (17 * 64KB) & 1 compute unit of instructions (~=100k CPU cycles).
    let mut config = Config::new(1_200_000, Some(1));
    // Allow only syscalls under the "wasi_snapshot_preview1::environ*" namespace
    config.allow_namespace("wasi_snapshot_preview1::environ");
    let mut env = Environment::new(config).unwrap();
    let module = env.add_this_module().unwrap();

    // This process will fail because it can't uses syscalls for std i/o
    let (_, _, m) = module
        .spawn_link(m, |_: Mailbox<()>| println!("Hi from different env"))
        .unwrap();
    assert!(m.receive().is_signal());

    // This process will fail because it uses too much memory
    let (_, _, m) = module
        .spawn_link(m, |_: Mailbox<()>| {
            vec![0; 150_000];
        })
        .unwrap();
    assert!(m.receive().is_signal());

    // This process will fail because it uses too much compute
    let (_, _, m) = module.spawn_link(m, |_: Mailbox<()>| loop {}).unwrap();
    assert!(m.receive().is_signal());
}
```

## Loading other WebAssembly modules

Lunatic allows for dynamic loading of other WebAssembly modules during runtime.
[`Environment::add_module`] can be used to add WebAssembly modules to an environment.

# Setting up Cargo for lunatic

To simplify developing, testing and running lunatic applications with cargo, you can add a
`.cargo/config.toml` file to your project with the following content:

```toml
[build]
target = "wasm32-wasi"

[target.wasm32-wasi]
runner = "lunatic"
```

Now you can just use the commands you were already familiar with, such as `cargo run`, `cargo test`
and cargo is going to automatically build your project as a WebAssembly module and run it inside of
lunatic.

# Debugging

If a process dies, either because an unsupported syscall was called or a `Kill` signal was received
there is not going to be any output in the terminal. To get more insight set the `RUST_LOG`
environment variable to `lunatic=debug`. E.g. `RUST_LOG=lunatic=debug cargo run`.

[1]: https://github.com/lunatic-solutions/lunatic
*/

mod environment;
mod error;
mod host_api;
mod mailbox;
pub mod net;
pub mod process;
mod request;
mod tag;

pub use environment::{lookup, Config, Environment, Module, Param, ThisModule};
pub use error::LunaticError;
pub use mailbox::{LinkMailbox, Mailbox, Message, ReceiveError, Signal, TransformMailbox};
pub use request::Request;
pub use tag::Tag;

pub use lunatic_macros::main;
pub use lunatic_macros::test;