Created
June 28, 2020 18:36
-
-
Save o0Ignition0o/3b5dd124d9cfb6b3189e942548db79ad to your computer and use it in GitHub Desktop.
Bastion floating on tide part 2: Building a bastion
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use async_std::task; | |
use bastion::{blocking, context::BastionContext, msg, prelude::ChildrenRef, Bastion}; | |
mod prime_number { | |
use std::{ | |
iter, | |
time::{Duration, Instant}, | |
}; | |
#[derive(Debug)] | |
pub struct Response { | |
prime_number: u128, | |
num_digits: usize, | |
compute_time: Duration, | |
} | |
impl std::fmt::Display for Response { | |
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
write!( | |
f, | |
"{} is a prime number that has {} digits.\nIt was found in {}s and {}ms", | |
self.prime_number, | |
self.num_digits, | |
self.compute_time.as_secs(), | |
self.compute_time.as_millis() % 1000 | |
) | |
} | |
} | |
// our prime number getter now returns a Response | |
pub fn get(num_digits: usize) -> Response { | |
let start = Instant::now(); | |
// Get a prime number | |
let prime_number = get_prime(num_digits); | |
// Stop the stopwatch | |
let elapsed = Instant::now().duration_since(start); | |
Response { | |
prime_number, | |
num_digits, | |
compute_time: elapsed, | |
} | |
} | |
fn get_prime(num_digits: usize) -> u128 { | |
let min_bound = get_min_bound(num_digits); | |
// with num_digits = 4, max_bound == 10000 | |
let max_bound = get_max_bound(num_digits); | |
// maybe_prime is a number in range [1000, 10000) | |
// the closing parenthesiss means it won't reach the number. | |
// the maximum allowed value for maybe_prime is 9999. | |
use rand::Rng; | |
let mut maybe_prime = rand::thread_rng().gen_range(min_bound, max_bound); | |
loop { | |
if is_prime(maybe_prime) { | |
return number_or_panic(maybe_prime); | |
} | |
// for any integer n > 3, | |
// there always exists at least one prime number p | |
// with n < p < 2n - 2 | |
maybe_prime += 1; | |
// We don't want to return a number | |
// that doesn't have the right number of digits | |
if maybe_prime == max_bound { | |
maybe_prime = min_bound; | |
} | |
} | |
} | |
// in order to determine if n is prime | |
// we will use a primality test. | |
// https://en.wikipedia.org/wiki/Primality_test#Pseudocode | |
fn is_prime(n: u128) -> bool { | |
if n <= 3 { | |
n > 1 | |
} else if n % 2 == 0 || n % 3 == 0 { | |
false | |
} else { | |
for i in (5..=(n as f64).sqrt() as u128).step_by(6) { | |
if n % i == 0 || n % (i + 2) == 0 { | |
return false; | |
} | |
} | |
true | |
} | |
} | |
// given a sequence of digits, return the corresponding number | |
// eg: assert_eq!(1234, digits_to_number(vec![1,2,3,4])) | |
fn digits_to_number(iter: impl Iterator<Item = usize>) -> u128 { | |
iter.fold(0, |acc, b| acc * 10 + b as u128) | |
} | |
fn get_min_bound(num_digits: usize) -> u128 { | |
let lower_bound_iter = | |
iter::once(1usize).chain(iter::repeat(0usize).take(num_digits - 1 as usize)); | |
digits_to_number(lower_bound_iter) | |
} | |
fn get_max_bound(num_digits: usize) -> u128 { | |
let lower_bound_iter = iter::once(1usize).chain(iter::repeat(0usize).take(num_digits)); | |
digits_to_number(lower_bound_iter) | |
} | |
fn number_or_panic(number_to_return: u128) -> u128 { | |
// Let's roll a dice | |
if rand::random::<u8>() % 6 == 0 { | |
panic!(format!( | |
"I was about to return {} but I chose to panic instead!", | |
number_to_return | |
)) | |
} | |
number_to_return | |
} | |
} | |
// `serve_prime_numbers` is the bastion child's behavior. | |
async fn serve_prime_numbers(ctx: BastionContext) -> Result<(), ()> { | |
// let's put the context in an arc, so we can pass it to other threads | |
let arc_ctx = std::sync::Arc::new(ctx); | |
// a child will keep processing messages until it crashes | |
// (or until it gets told to shutdown) | |
loop { | |
// msg! is our message receiver helper. | |
// we will only use one variant here | |
// =!> means messages that can be replied to | |
// usize means it will only match against messages that are a usize | |
msg! { arc_ctx.clone().recv().await?, | |
nb_digits: usize =!> { | |
// clone the context to send it to a thread | |
let ctx2 = arc_ctx.clone(); | |
// answer! takes a context and will automagically figure out whom to reply to | |
blocking!(answer!(ctx2, prime_number::get(nb_digits)).expect("couldn't reply :(")); | |
}; | |
// this is a catch all for any other message we might receive | |
unknown:_ => { | |
println!("uh oh, I received a message I didn't understand\n {:?}", unknown); | |
}; | |
} | |
} | |
} | |
struct PrimeClient { | |
children_handle: ChildrenRef, | |
} | |
impl PrimeClient { | |
pub fn new(children_handle: ChildrenRef) -> Self { | |
Self { children_handle } | |
} | |
pub async fn request_prime(&self, nb_digits: usize) -> Result<prime_number::Response, ()> { | |
// Ask our child for a prime number | |
let reply = self | |
.children_handle | |
.elems() | |
.first() | |
.expect("no child?") | |
.ask_anonymously(nb_digits) | |
.expect("couldn't ask for a prime number"); | |
// wait for the reply | |
msg! { reply.await?, | |
response: prime_number::Response => { | |
Ok(response) | |
}; | |
// this is a catch all for any other message we might receive | |
unknown:_ => { | |
println!("uh oh, I received a message I didn't understand\n {:?}", unknown); | |
Err(()) | |
}; | |
} | |
} | |
} | |
async fn prime_number(req: tide::Request<PrimeClient>) -> Result<String, tide::Error> { | |
let d: usize = req.param("digits").unwrap_or(1); | |
// Use the PrimeClient to ask for a prime number | |
// PrimeClient is now part of our state | |
let response = req.state().request_prime(d).await.map_err(|_| { | |
tide::Error::from_str( | |
tide::StatusCode::InternalServerError, | |
"I'm sorry, I couldn't get a prime number", | |
) | |
})?; | |
Ok(format!("{}\n", response)) | |
} | |
fn main() -> Result<(), std::io::Error> { | |
// We need a bastion in order to run everything | |
Bastion::init(); | |
Bastion::start(); | |
// Spawn 1 child that will serve prime numbers | |
let children_handle = | |
Bastion::children(|children| children.with_redundancy(1).with_exec(serve_prime_numbers)) | |
.expect("couldn't spawn children, aborting program."); | |
// Create a prime client... | |
let prime_client = PrimeClient::new(children_handle); | |
task::block_on(async move { | |
// ...That will populate our tide state | |
let mut app = tide::with_state(prime_client); | |
app.at("/prime/:digits").get(prime_number); | |
app.listen("127.0.0.1:8080").await | |
})?; | |
Bastion::stop(); | |
Bastion::block_until_stopped(); | |
Ok(()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Cargo.toml dependencies:
Output: