Bastion

This book will introduce you to Bastion, a highly-available, fault-tolerant runtime system with dynamic, dispatch-oriented, lightweight process model.

It supplies actor-model-like concurrency with a lightweight process implementation and utilizes all of the system resources efficiently guaranteeing at-most-once message delivery.

Discussion and Development

We use Discord for development discussions. Also please don't hesitate to open issues on GitHub, ask for features, report bugs, comment on design and more! More interaction and more ideas are better!

Contributing to Bastion

All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome.

A detailed overview on how to contribute can be found in the CONTRIBUTING guide on GitHub.

Like bastion, this book is open-source. If you find something wrong or a typo, send us a pull request!

License

Licensed under either of:

  • Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
  • MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)

at your option.

Getting started

Let's start our journey with Bastion. During it we will see the main features, we have a lot to discover. For that we will follow a theme, we will build a small CRUD application.

In this first chapter, we will talk about:

  • Installing Bastion on your project
  • The Classic and timeless: hello, world!

Installation

Bastion is a library which need to be added as a dependency to your project. For that, you just have to add it as follow to your Cargo.toml:

bastion = "0.3.5-alpha"

You can also use cargo-edit and run cargo add bastion in a shell.

Hello, world!

The most classic of all examples and especially the essential hello, world! Only for you, in Bastion.

use bastion::prelude::*;

async fn once_hello_world(ctx: BastionContext) -> Result<(), ()> {
    MessageHandler::new(ctx.recv().await?)
        .on_question(|question: &str, sender| {
            if question == "hi!" {
                sender
                    .reply("hello, world!")
                    .expect("Failed to reply to sender");
            } else {
                panic!("Expected string `hi!`, found `{}`", question);
            }
        })
        .on_fallback(|v, addr| panic!("Wrong message from {:?}: got {:?}", addr, v));
    Ok(())
}

fn main() {
    Bastion::init();
    Bastion::start();

    Bastion::children(|children| {
        children
            .with_distributor(Distributor::named("say_hi"))
            .with_exec(once_hello_world)
    })
    .expect("Couldn't create the children group.");

    let say_hi = Distributor::named("say_hi");

    run!(async {
        let answer: Result<&str, SendError> =
            say_hi.request("hi!").await.expect("Couldn't send request");

        println!("{}", answer.expect("Couldn't receive answer"))
    });

    Bastion::stop();
}

Don't forget, if you have any question, you can find us on Discord!

How to

In this section we will talk about the principal features of bastion, with a functional approach.

Initializing Bastion

Initialize

Bastion needs to be initialized. You can initialize it with the default configuration or with a custom one. A custom configuration will allow you to override the Backtraces* system

Enabled by default, a custom configuration will allow you to override it and enable or disable the Backtraces.

  • Default configuration
use bastion::prelude::*;

fn main() {
    Bastion::init();

    // Some stuff have to be written later... but not yet!
}
  • Custom configuration, we are hidding the backtraces here, you can still use show_backtraces(); to enable it.
use bastion::prelude::*;

fn main() {
    let config = Config::new().hide_backtraces();
    Bastion::init_with(config);

    // Some stuff have to be written later... but not yet!
}

Start

After you initialize bastion you will have to start it by using Bastion::start();.

use bastion::prelude::*;

fn main() {
    Bastion::init();
    Bastion::start();

    // Some stuff have to be written later... but not yet!
}

Block

As with any application, there will be a point where you want it to stop. But this time might not occur in your main() function. This can happen anywhere in your code.

Bastion provide Bastion::block_until_stopped(). It will block the current thread until the system is stopped (either by calling Bastion::stop() or Bastion::kill()).

use bastion::prelude::*;

fn main() {
    Bastion::init();
    Bastion::start();

    // Some stuff have to be written later... but not yet!

    Bastion::block_until_stopped();
}

Stop

At this point you might want to stop the bastion. You can use Bastion::stop().

It will send a message to the whole system to tell it to gracefully stop each running children groups and supervisors.

use bastion::prelude::*;

fn main() {
    Bastion::init();
    Bastion::start();

    // Some stuff have to be written later... but not yet!

    Bastion::stop();
}

As we said in the Block section of this chapter you may want to stop the application anywhere in your code. Here we will stop our bastion after the execution of stop_me_now().

use bastion::prelude::*;

async fn stop_me_now(_ctx: BastionContext) -> Result<(), ()> {
    Bastion::stop();
    Ok(())
}

fn main() {
    Bastion::init();
    Bastion::start();

    Bastion::children(|children| children.with_exec(stop_me_now))
        .expect("Couldn't create the children group.");
}

As you may have noticed, we don't block_until_stopped(); because the children's with_exec task will complete immediately. There's nothing to wait for.

Kill

In some case, in unrecoverable cases, you may want to just kill the system and drop all processes in execution. Bastion::kill(); is made for it.

use bastion::prelude::*;

fn main() {
    Bastion::init();
    Bastion::start();

    // Some stuff have to be written later... but not yet!

    Bastion::kill();
}

*Backtraces: Appendix - Definition

Appendix

The following sections contain reference material you may find useful in Bastion.

Definition