Overview

Understand what Selium is and how it works.

Selium is a software-defined hypervisor for WebAssembly. Like other hypervisors, Selium creates secure and isolated environments for running applications. However unlike the others, Selium is built specifically for the needs and preferences of software developers:

  • Our API is library-based and highly composable
  • "Virtual machines" in Selium behave like processes and don't ship with (or use) an operating system
  • Selium processes feature robust isolation with opt-in capability-based security
  • Our I/O is built around an ergonomic and strong-typed messaging layer, while still integrating trivially with external network protocols

Quick note: Selium is in alpha, so expect a bit of churn as we perfect our architecture.

Example App

/// Selium uses Flatbuffers to enforce strong typing across process boundaries.
/// This is the request payload to be transmitted on the wire.
#[schema(
    path = "schemas/echo.fbs",
    ty = "examples.echo.EchoMsg",
    binding = "crate::fbs::examples::echo::EchoMsg"
)]
pub struct EchoMsg {
    /// Your message to echo
    pub msg: String,
}
 
/// `entrypoint`s are like "main functions" for your application, except you can have
/// lots of them in the same crate.
#[entrypoint]
async fn echo_client(ctx: Context) -> Result<()> {
    // Singletons give users access to global context, like Switchboard and Atlas handles.
    // You can define your own too!
    let switchboard = ctx.require::<Switchboard>().await;
    let atlas = ctx.require::<Atlas>().await;
 
    // Create a client to query the server on its URI: "/moo/cow"
    let mut client = Client::create(&switchboard).await?;
    SubscriberAtlasExt::connect(&client, &atlas, &switchboard, "sel://moo/cow").await?;
 
    // Send the request, expecting the server to return an `EchoMsg` response
    let response: EchoMsg = client
        .request(EchoMsg::new("Hello, world!".to_string()))
        .await
        .context("send request")?;
    
    // Logs are integrated with messaging, so you can subscribe to logs from other
    // processes as well as the CLI.
    info!(reply = response.msg, "received echo");
 
    Ok(())
}
 
#[entrypoint]
async fn echo_server(ctx: Context) -> Result<()> {
    let switchboard = ctx.require::<Switchboard>().await;
    let atlas = ctx.require::<Atlas>().await;
 
    let server: Server<EchoMsg, EchoMsg> = Server::create(&switchboard).await?;
    
    // Tell Atlas that we're listening on /moo/cow so that the client can discover us
    atlas.insert(Uri::parse("sel://moo/cow").unwrap(), server.endpoint_id() as u64).await?;
    
    // Handle requests from clients
    server
        .try_for_each(async |req| {
            let (msg, responder) = req.into_parts();
            info!(msg = msg.msg, "received request");
            // Replies are automatically routed to their respective clients
            responder.send(msg).await
        })
        .await?;
 
    Ok(())
}
  • Start with Getting started to get a local runtime running and launch your first service.
  • Then read Applications to understand entrypoints, arguments, and capabilities.
  • Keep Runtime nearby when you want to tweak flags or module specs.
  • When you are ready to think about throughput and isolation, go deeper on Channels.