Released 2023-05-03.
- Wasmtime updated to 8.0
- Process monitoring support added (@tqwewe)
- SQLite support added (@SquattingSocrates)
- Support for intermediate CA certificates added (@teskeras)
- Show name of registered processes when they fail (@tqwewe)
- Environment spawn limit (@kosticmarin)
peer_addr
host API added (@MarkintoshZ)- Metrics API added (@Roger)
process::exists
API added (@jtenner)- Cargo lunatic wrapper added (@Gentle)
- Improved CLI default arguments (@pinkforest)
- Improved CI workflow (@shamilsan)
- Additional tests added (@sid-707)
Released 2022-11-15.
- Compiled modules can now be sent between processes (@tqwewe)
- TLS support added (@SquattingSocrates)
- Metrics added to the VM (@HurricanKai)
- Improvements to distributed lunatic (@kosticmarin)
- Distributed metadata added (@kosticmarin)
- Improved error reporting (@alecthomas)
- TCP read/write timeouts added back (@SquattingSocrates)
- Time API moved from async-std to tokio.rs (@MarkintoshZ)
- FIX: Sender can be dropped during execution (@HurricanKai)
- FIX: Dependency issues (@pinkforest)
Released 2022-07-04.
- Distributed lunatic based on QUIC (@withtypes & @kosticmarin)
- Switched from async_std to tokio (@kosticmarin)
kill
host function added (@zhamlin)send_after
&cancel_timer
host functions added (@zhamlin)- timeout type in host functions switched from
u32
tou64
- timeout parameters are removed for networking read/write calls
Released 2022-04-25.
- UDP support (contributed by @jtenner)
- Added support for
cargo test
whenlunatic
is used as runner. - Temporarily removed support for distributed lunatic while a better design is being developed.
Released 2022-01-20.
- The CI now builds universal macOS binaries (M1 & Intel support).
- Host functions for TCP read/writes indicate a timeout with a return value now, instead of a generic error. (contributed by @teymour-aldridge)
Released 2022-01-15.
- Adds
local_addr
host function for TCP listeners. - Adds
version
host function. (contributed by @teymour-aldridge) - Adds check if processes are spawned before the Wasm module was initialized. (contributed by @jtenner)
- Process traps are now logged by default to stdout.
Released 2021-12-01.
This is the first release that supports connecting multiple lunatic instances together 🎉. From the perspective of developers that are targeting lunatic there should be no difference between locally running processes or remote ones. Spawning and sending messages to them uses the same APIs.
To turn your local lunatic instance into a distributed node you will need to provide a unique name and socket to bind to. Both of them can be set through the cli.
To start a distributed node you can run:
lunatic --node 0.0.0.0:8333 --node-name foo --no-entry
This starts a lunatic node with the name foo
listening the specified port. The --no-entry
flag
means that this node doesn't have a start function, it will just block forever.
If you want to connect to a node you can pass in the --peer
flag:
lunatic --node localhost:8334 --node-name bar --peer 0.0.0.0:8333 file.wasm
Once you connect to one node all others known ones will be dynamically discovered.
A great thing about lunatic is that much of the functionality provided by the runtime is directly exposed to the code running inside of it. This allows you to dynamically load WebAssembly code from already running WebAssembly code, or to create sandboxed environments to execute some code on the fly.
The abstraction of a Environment
, that we used previously to sandbox and limit process
resources, fits perfectly into the world of distributed lunatic. Every time you create a new
Environment
you need to explicitly add Wasm Modules
to it, because we may need to JIT
re-compile the module with the new limitations that have been set. Spawning a process from the same
function in different Environments
may use different machine generated code to be more efficient
in regard to the provided sandbox.
Now that a Module
may be sent over the network to a computer running a different operating system
or even using a different CPU architecture, no changes need to be done to this already existing
pattern inside of lunatic.
Here is an example of using the new API from Rust guest code:
use lunatic::{Config, Environment, Mailbox};
#[lunatic::main]
fn main(_: Mailbox<()>) {
// Give full access to the remote environment.
let mut config = Config::new(0xA00000000, None);
config.allow_namespace("");
// Create a new environment on the remote node with the name "foo"
let mut env = Environment::new_remote("foo", config).unwrap();
// Add the currently running module to the environment.
// This allows us to spawn a process from a closure, because the remote module will have the same
// bytecode available.
let module = env.add_this_module().unwrap();
// Spawn a process on a remote machine as you would do it locally.
let _ = module.spawn(|_: Mailbox<()>| println!("Hello world"));
}
This will print out Hello world
on the node labeled foo
. Adding this to the rust library
required only a few lines of code changes. The whole implementation complexity stays inside the
VM. From the developer's perspective it's trivial to just send a closure to be executed on a
completely different machine that may use a different operating system or CPU architecture.
- At the moment nodes send plain text messages between each other and each node connects to each other over TCP.
- If a node disappears from the network linked processes will not be notified that the links broke.
Released 2021-08-31.
This release contains mostly internal changes that should improve the developer experience of people working on the VM, but also adds some cool new features.
-
Processes now have a more general abstraction, the
Process
trait. It allows us to treat anything that can receive a "message" as a process. At the moment this can only be a WebAssembly process or native Rust closures, but it could be extended in the future with other resources that act as processes. -
Tags were added to messages, allowing for selective receives. A common use case for them is to make a request to a process and ignore all other messages until the response arrives. This can be now done by giving the request message a specific tag (
i64
value) and waiting for a response on that tag withlunatic::message::receive
. Thereceive
function will first search the existing mailbox for the first message matching the tag or block until a message with the specified tag arrives. If we know that such a tag can't yet exist in the mailbox, we can use the atomic send and receive operation (send_receive_skip_search
) that will not look through the mailbox. -
Messages are now just a special kind of signals that a process can receive. Other signals are
Kill
,Link
,Unlink
, ... -
A test was added for catching signature changes of host functions.
-
The messaging API was extended, including functions
write_data
andread_data
that allow for streaming zero-copy message de/serialization. -
The
Environment
was extended with a concept of aregistry
and 3 host functions:register
,unregister
andlookup
. Processes can now be registered inside theEnvironment
under a well known name and version number. When looking up processes inside theEnvironment
with a query, the lookup will follow semantic versioning rules for the version. If we have a process under the name "test" and version "1.2.3", a lookup query with the name "test" and version "^1.2" will match it. -
Fixed an issue around async Rust cancellation safety and receives with timeouts.
-
Improved handling of command line arguments and environment variables.
-
The
Message
trait was removed, and we now solely rely on serde'sSerialize
&Deserialize
traits to define what can be a message. Originally I was thinking that this is going to be an issue once we get support for Rust's nativeTcpStream
, and we can't define serde's traits for it, but this can be solved with remote derives in the future. This removes a really big and complex macro from the library and allows us to use the newwrite_data
andread_data
host functions for zero-copy de/serialization. -
MessagePack is now used as the default message serialization format.
-
A request/reply API was added, that was built on the new selective receive functionality.
-
The
Environment
struct was extended with the newregistry
functionality. -
New
lunatic::main
&lunatic::test
macros were added to improve developer experiences. -
lunatic::process::this_env
was added to get the environment that the process was spawned in.
Released 2021-07-29.
Lunatic was completely re-written from scratch. Now it's built on top of
Wasmtime's async
support and doesn't contain any
unsafe code.
The architecture of the runtime was changed to closer mirror Erlang's features. Processes now only contain one mailbox and the channels API was removed. Processes can also be linked together to propagate failure, so that supervisor like tools can be built on top of them.
Environments allow you to specify some characteristics of the execution, like how much memory or CPU processes can use. They can also define host function namespaces that are allowed to be called. Processes that are spawned into an environment inherit these characteristics, allowing you to dynamically create execution contexts for new processes.
WebAssembly modules can be loaded from other WebAssembly modules during runtime. Combined
with Environments
this can be used to load untrusted code and run it inside a sandbox.
The Rust library was also completely re-written to support the new abstractions. Check out the new docs!
The AssemblyScript library is still WIP and doesn't support the new features yet.
Released 2021-02-22.
Miscellaneous bug fixes and stability improvements.
Released 2021-01-26.
1. Created uptown_funk (this link leads to a YouTube music video)
This is by far the biggest change in this release and one that took up most of the development time. uptown_funk is a crate that lets you elegantly define Wasm host functions that are compatible with both Wasmtime and Wasmer runtimes.
It consists of a macro and a few structs/traits that let you translate from Wasm primitive types to higher level Rust types, and back.
Lets look at an example of a host function definition using uptown_funk
:
#[host_functions(namespace = "wasi_snapshot_preview1")]
impl WasiState {
async fn fd_pread(&mut self, fd: u32, iovs: &mut [IoSliceMut<'_>], offset: Filesize) -> (Status, u32) {
// ...
}
// .. Other functions depending on the WasiState struct.
}
The host_function
macro lets us grab any host side struct and use it as state for the Wasm instances.
Instead of dealing with low level pointers + lengths passed from the WebAssembly guests, we can pretend
to receive higher level Rust type (e.g. &mut [IoSliceMut<'_>]
) and the macro is going to create appropriate
wrappers for us. And of course, it correctly works with async
functions on Lunatic.
This was an important step forward to make Lunatic runtime agnostic. Currently, we support bot Wasmer and Wasmtime,
but if we wanted, we could add support for another runtime in the future by just adding support to uptown_funk
.
Sadly, uptown_funk
doesn't have any documentation yet and is not that useful to other projects. But I intend to
invest more time into this in the future.
This issue needs a bit of context. All Lunatic processes are executed on a separate stack and if they are waiting
for some I/O they will be moved off the execution thread. Now, you can decide while you are waiting on something
just to cancel this process. Until now this would free the memory region belonging to the stack/heap and finish.
However, it can happen that the separate stack contains pointers to resources held by the runtime (channels, other
processes, etc.). Their drop()
methods would have never been called in this case and the resources would have
been leaked.
This required a fix in the async-wormhole crate. Now, every time when a generator is dropped and the closure didn't yet finish running, a stack unwind will be triggered on the separate stack. This will clean up all the resources left behind.
The Lunatic Rust library allows you to write Rust applications that can take complete advantage of Lunatic's features, not to embed the Lunatic runtime in your Rust application (coming soon). The Rust library has seen almost a complete rewrite and takes much better advantage of Rust's ownership model now. Especially when sending host resources between processes.
This is still a WIP area, but the basic functionality is there for opening files, reading directories, etc.
A few APIs are still missing, but we have enough to create a TCP listener/client.
There are too many other small fixes and additions to mention here, but Lunatic is much more stable now than just 2 months ago, and I have removed the experimental warning in the Readme :)