From 083e5ec83ad34f3630d857921096db7bb79b052c Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Sun, 13 Jun 2021 22:17:38 -0700 Subject: [PATCH] spawn-blocking: Add example from Chapter 20, Asynchronous Programming. --- spawn-blocking/.gitignore | 2 ++ spawn-blocking/Cargo.toml | 12 ++++++++ spawn-blocking/src/lib.rs | 60 +++++++++++++++++++++++++++++++++++++ spawn-blocking/src/tests.rs | 55 ++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 spawn-blocking/.gitignore create mode 100644 spawn-blocking/Cargo.toml create mode 100644 spawn-blocking/src/lib.rs create mode 100644 spawn-blocking/src/tests.rs diff --git a/spawn-blocking/.gitignore b/spawn-blocking/.gitignore new file mode 100644 index 0000000..ca98cd9 --- /dev/null +++ b/spawn-blocking/.gitignore @@ -0,0 +1,2 @@ +/target/ +Cargo.lock diff --git a/spawn-blocking/Cargo.toml b/spawn-blocking/Cargo.toml new file mode 100644 index 0000000..f2d81b7 --- /dev/null +++ b/spawn-blocking/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "spawn-blocking" +version = "0.1.0" +authors = ["You "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dev-dependencies] +argonautica = "0.2" +async-std = "1.7" + diff --git a/spawn-blocking/src/lib.rs b/spawn-blocking/src/lib.rs new file mode 100644 index 0000000..c78cc5d --- /dev/null +++ b/spawn-blocking/src/lib.rs @@ -0,0 +1,60 @@ +#[cfg(test)] +mod tests; + +use std::sync::{Arc, Mutex}; +use std::task::Waker; + +pub struct SpawnBlocking(Arc>>); + +struct Shared { + value: Option, + waker: Option, +} + +pub fn spawn_blocking(closure: F) -> SpawnBlocking +where F: FnOnce() -> T, + F: Send + 'static, + T: Send + 'static, +{ + let inner = Arc::new(Mutex::new(Shared { + value: None, + waker: None, + })); + + std::thread::spawn({ + let inner = inner.clone(); + move || { + let value = closure(); + + let maybe_waker = { + let mut guard = inner.lock().unwrap(); + guard.value = Some(value); + guard.waker.take() + }; + + if let Some(waker) = maybe_waker { + waker.wake(); + } + } + }); + + SpawnBlocking(inner) +} + +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + +impl Future for SpawnBlocking { + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut guard = self.0.lock().unwrap(); + if let Some(value) = guard.value.take() { + return Poll::Ready(value); + } + + guard.waker = Some(cx.waker().clone()); + Poll::Pending + } +} diff --git a/spawn-blocking/src/tests.rs b/spawn-blocking/src/tests.rs new file mode 100644 index 0000000..fe01e12 --- /dev/null +++ b/spawn-blocking/src/tests.rs @@ -0,0 +1,55 @@ +use super::*; + +async fn verify_password(password: &str, hash: &str, key: &str) + -> Result +{ + let password = password.to_string(); + let hash = hash.to_string(); + let key = key.to_string(); + + spawn_blocking(move || { + argonautica::Verifier::default() + .with_hash(hash) + .with_password(password) + .with_secret_key(key) + .verify() + }).await +} + +static PASSWORD: &str = "P@ssw0rd"; +static HASH: &str = "$argon2id$v=19$m=4096,t=192,p=4$\ + o2y5PU86Vt+sr93N7YUGgC7AMpTKpTQCk4tNGUPZMY4$\ + yzP/ukZRPIbZg6PvgnUUobUMbApfF9RH6NagL9L4Xr4\ + "; +static SECRET_KEY: &str = "secret key that you should really store in a .env file \ + instead of in code, but this is just an example\ + "; + +#[test] +fn argonautica() { + async_std::task::block_on(async { + assert!(verify_password(PASSWORD, HASH, SECRET_KEY).await.unwrap()); + }); +} + +#[test] +fn many_serial() { + async_std::task::block_on(async { + for i in 0..1000 { + assert_eq!(spawn_blocking(move || i).await, i); + } + }); +} + +#[test] +fn many_parallel() { + async_std::task::block_on(async { + let futures: Vec<_> = (0..100) + .map(|i| (i, spawn_blocking(move || i))) + .collect(); + + for (i, f) in futures { + assert_eq!(f.await, i); + } + }); +}