A framework for writing fault tolerant, highly scalable applications with the Rust programming language. It is:
Processes
) that are isolated and exchange information via messages.GenServer
and Supervisor
to restart parts of your system when things to awry.Hydra runs on the Tokio runtime, known for powering reliable, asynchronous, and slim applications with the Rust programming language. At a high level, Hydra provides the following major components:
A basic GenServer
Stack application with Hydra.
Make sure you have added Hydra, Serde in your Cargo.toml:
[dependencies]
hydra = "0.1"
serde = { version="1.0", features = "derive" }
Make sure that you are not aborting on panics, Hydra catches and manages panics for all Processes
. Find and remove this line if it exists in your Cargo.toml:
panic = "abort"
Then, in your main.rs:
use hydra::Application;
use hydra::ExitReason;
use hydra::From;
use hydra::GenServer;
use hydra::GenServerOptions;
use hydra::Pid;
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Serialize, Deserialize)]
enum StackMessage {
Pop,
PopResult(String),
Push(String),
}
struct Stack {
stack: Vec<String>,
}
impl Stack {
pub fn with_entries(entries: Vec<&'static str>) -> Self {
Self {
stack: Vec::from_iter(entries.into_iter().map(Into::into)),
}
}
}
impl GenServer for Stack {
type Message = StackMessage;
async fn init(&mut self) -> Result<(), ExitReason> {
Ok(())
}
async fn handle_call(
&mut self,
message: Self::Message,
_from: From,
) -> Result<Option<Self::Message>, ExitReason> {
match message {
StackMessage::Pop => Ok(Some(StackMessage::PopResult(self.stack.remove(0)))),
_ => unreachable!(),
}
}
async fn handle_cast(&mut self, message: Self::Message) -> Result<(), ExitReason> {
match message {
StackMessage::Push(value) => self.stack.insert(0, value),
_ => unreachable!(),
}
Ok(())
}
}
struct StackApplication;
impl Application for StackApplication {
// Here, we must link a process for the application to monitor, usually, a Supervisor, but it can be any process.
async fn start(&self) -> Result<Pid, ExitReason> {
let pid = Stack::with_entries(vec!["hello", "world"])
.start_link(GenServerOptions::new())
.await
.expect("Failed to start stack!");
let result = Stack::call(pid, StackMessage::Pop, None)
.await
.expect("Stack call failed!");
tracing::info!("{:?}", result);
Stack::cast(pid, StackMessage::Push(String::from("rust")));
let result = Stack::call(pid, StackMessage::Pop, None)
.await
.expect("Stack call failed!");
tracing::info!("{:?}", result);
// Otherwise, the application will run forever waiting for Stack to terminate.
Stack::stop(pid, ExitReason::Normal, None).await?;
Ok(pid)
}
}
fn main() {
// This method will return once the linked Process in StackApplication::start has terminated.
Application::run(StackApplication)
}
Find more examples in the examples folder. You can run them with: cargo run --example=stack
.
The following projects are related to, or in use in Hydra such that it would be wise to learn them as well:
tokio
: A runtime for writing reliable, asynchronous, and slim applications with the Rust programming language.serde
: A framework for serializing and deserializing Rust data structures efficiently and generically.tracing
: A framework for application-level tracing and async-aware diagnostics.There is a message passing benchmark in the examples you can run using cargo run --example=benchmark --release
. I've gotten the following results:
This project is licensed under the MIT license
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Hydra by you, shall be licensed as MIT, without any additional terms or conditions.