From e03a8752232619143662dd225de801e80e648dad Mon Sep 17 00:00:00 2001 From: Ville Hakulinen Date: Wed, 6 Jun 2018 20:37:04 +0300 Subject: [PATCH 1/6] Implement request-state local cache --- core/lib/src/request/request.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/core/lib/src/request/request.rs b/core/lib/src/request/request.rs index 7c0a1986d7..c1dc4c44c4 100644 --- a/core/lib/src/request/request.rs +++ b/core/lib/src/request/request.rs @@ -1,3 +1,4 @@ +use std::rc::Rc; use std::cell::{Cell, RefCell}; use std::net::{IpAddr, SocketAddr}; use std::fmt; @@ -26,6 +27,7 @@ struct RequestState<'r> { cookies: RefCell, accept: Storage>, content_type: Storage>, + cache: Rc, } /// The type of an incoming web request. @@ -41,7 +43,7 @@ pub struct Request<'r> { uri: Uri<'r>, headers: HeaderMap<'r>, remote: Option, - state: RequestState<'r> + state: RequestState<'r>, } impl<'r> Request<'r> { @@ -67,6 +69,7 @@ impl<'r> Request<'r> { cookies: RefCell::new(CookieJar::new()), accept: Storage::new(), content_type: Storage::new(), + cache: Rc::new(Container::new()), } } } @@ -78,6 +81,23 @@ impl<'r> Request<'r> { f(&mut request); } + /// Retrieves the cached value for type `T` from the request-local cached + /// state of `self`. If no such value has previously been cached for + /// this request, `f` is called to produce the value which is subsequently + /// returned. + pub fn local_cache(&self, f: F) -> &T + where T: Send + Sync + 'static, + F: FnOnce(&Request) -> T { + + match self.state.cache.try_get() { + Some(cached) => cached, + None => { + self.state.cache.set(f(self)); + self.state.cache.get() + } + } + } + /// Retrieve the method from `self`. /// /// # Example From 8fa1cb92f0267127f000d576b09617f1077df26c Mon Sep 17 00:00:00 2001 From: Ville Hakulinen Date: Sat, 9 Jun 2018 17:02:54 +0300 Subject: [PATCH 2/6] Examples and tests for request state local cache --- Cargo.toml | 1 + core/lib/src/request/request.rs | 16 ++++ examples/request_state_local_cache/Cargo.toml | 9 +++ .../request_state_local_cache/src/main.rs | 73 +++++++++++++++++++ .../request_state_local_cache/src/tests.rs | 33 +++++++++ 5 files changed, 132 insertions(+) create mode 100644 examples/request_state_local_cache/Cargo.toml create mode 100644 examples/request_state_local_cache/src/main.rs create mode 100644 examples/request_state_local_cache/src/tests.rs diff --git a/Cargo.toml b/Cargo.toml index 4341d55931..0c31fd1a1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ members = [ "examples/content_types", "examples/ranking", "examples/testing", + "examples/request_state_local_cache", "examples/request_guard", "examples/stream", "examples/json", diff --git a/core/lib/src/request/request.rs b/core/lib/src/request/request.rs index c1dc4c44c4..a8b05437c3 100644 --- a/core/lib/src/request/request.rs +++ b/core/lib/src/request/request.rs @@ -85,6 +85,22 @@ impl<'r> Request<'r> { /// state of `self`. If no such value has previously been cached for /// this request, `f` is called to produce the value which is subsequently /// returned. + /// + /// # Example + /// + /// ```rust + /// # use rocket::http::Method; + /// # use rocket::Request; + /// # struct User(); + /// fn current_user(_: &Request) -> User { + /// // Load user... + /// # User() + /// } + /// + /// # Request::example(Method::Get, "/uri", |request| { + /// let user = request.local_cache(current_user); + /// # }); + /// ``` pub fn local_cache(&self, f: F) -> &T where T: Send + Sync + 'static, F: FnOnce(&Request) -> T { diff --git a/examples/request_state_local_cache/Cargo.toml b/examples/request_state_local_cache/Cargo.toml new file mode 100644 index 0000000000..0c720023b9 --- /dev/null +++ b/examples/request_state_local_cache/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "request_state_local_cache" +version = "0.0.0" +workspace = "../../" +publish = false + +[dependencies] +rocket = { path = "../../core/lib" } +rocket_codegen = { path = "../../core/codegen" } diff --git a/examples/request_state_local_cache/src/main.rs b/examples/request_state_local_cache/src/main.rs new file mode 100644 index 0000000000..a3dc01cd11 --- /dev/null +++ b/examples/request_state_local_cache/src/main.rs @@ -0,0 +1,73 @@ +#![feature(plugin, decl_macro)] +#![plugin(rocket_codegen)] +extern crate rocket; + +use std::thread; +use std::time::Duration; + +use rocket::request::{self, Request, FromRequest}; +use rocket::outcome::Outcome::*; + +#[cfg(test)] mod tests; + +#[derive(Clone, Copy)] +struct Sleep(); +struct SleepCached(Sleep); +struct ForwardGuard(); + +impl<'a, 'r> FromRequest<'a, 'r> for ForwardGuard { + type Error = (); + + fn from_request(_: &'a Request<'r>) -> request::Outcome { + Forward(()) + } +} + +impl Sleep { + fn get(_: &Request) -> Sleep { + thread::sleep(Duration::from_secs(3)); + Sleep() + } +} + +impl<'a, 'r> FromRequest<'a, 'r> for Sleep { + type Error = (); + + fn from_request(request: &'a Request<'r>) -> request::Outcome { + Success(Sleep::get(request)) + } +} + +impl<'a, 'r> FromRequest<'a, 'r> for SleepCached { + type Error = (); + + fn from_request(request: &'a Request<'r>) -> request::Outcome { + let s = request.local_cache(Sleep::get); + Success(SleepCached(*s)) + } +} + +#[get("/")] +fn index(_s: Sleep, _f: ForwardGuard) { +} + +#[get("/", rank = 2)] +fn index2(_s: Sleep) { +} + +#[get("/cached")] +fn index_cached(_s: SleepCached, _f: ForwardGuard) { +} + +#[get("/cached", rank = 2)] +fn index_cached2(_s: SleepCached) { +} + +fn rocket() -> rocket::Rocket { + rocket::ignite() + .mount("/", routes!(index, index2, index_cached, index_cached2)) +} + +fn main() { + rocket().launch(); +} diff --git a/examples/request_state_local_cache/src/tests.rs b/examples/request_state_local_cache/src/tests.rs new file mode 100644 index 0000000000..bb4c1e1bab --- /dev/null +++ b/examples/request_state_local_cache/src/tests.rs @@ -0,0 +1,33 @@ +use std::time::{Instant, Duration}; + +use super::rocket; +use rocket::local::Client; + +fn normal_time() -> Duration { + let client = Client::new(rocket()).unwrap(); + + let start = Instant::now(); + client.get("/").dispatch(); + let end = Instant::now(); + + end - start +} + +fn cached_time() -> Duration { + let client = Client::new(rocket()).unwrap(); + + let start = Instant::now(); + client.get("/cached").dispatch(); + let end = Instant::now(); + + end - start +} + +#[test] +fn cache_is_faster() { + let normal = normal_time(); + let cached = cached_time(); + + assert!(normal > cached, "Cached is faster"); + assert!((normal - cached).as_secs() >= 3, "Cached is at least 3 secs faster"); +} From a5d55cd1f8f0dfb7cd99ed94b542166e13a00e62 Mon Sep 17 00:00:00 2001 From: Ville Hakulinen Date: Thu, 28 Jun 2018 20:58:34 +0300 Subject: [PATCH 3/6] Improve request state local cache example tests --- .../request_state_local_cache/src/main.rs | 71 ++++++++----------- .../request_state_local_cache/src/tests.rs | 31 ++------ 2 files changed, 36 insertions(+), 66 deletions(-) diff --git a/examples/request_state_local_cache/src/main.rs b/examples/request_state_local_cache/src/main.rs index a3dc01cd11..57dc6523ec 100644 --- a/examples/request_state_local_cache/src/main.rs +++ b/examples/request_state_local_cache/src/main.rs @@ -2,70 +2,59 @@ #![plugin(rocket_codegen)] extern crate rocket; -use std::thread; -use std::time::Duration; +use std::sync::atomic::{AtomicUsize, Ordering}; -use rocket::request::{self, Request, FromRequest}; +use rocket::request::{self, Request, FromRequest, State}; use rocket::outcome::Outcome::*; #[cfg(test)] mod tests; -#[derive(Clone, Copy)] -struct Sleep(); -struct SleepCached(Sleep); -struct ForwardGuard(); - -impl<'a, 'r> FromRequest<'a, 'r> for ForwardGuard { - type Error = (); - - fn from_request(_: &'a Request<'r>) -> request::Outcome { - Forward(()) - } +struct Atomics { + first: AtomicUsize, + second: AtomicUsize, } -impl Sleep { - fn get(_: &Request) -> Sleep { - thread::sleep(Duration::from_secs(3)); - Sleep() - } -} +struct Guard1(); +struct Guard2(); -impl<'a, 'r> FromRequest<'a, 'r> for Sleep { +impl<'a, 'r> FromRequest<'a, 'r> for Guard1 { type Error = (); - fn from_request(request: &'a Request<'r>) -> request::Outcome { - Success(Sleep::get(request)) + fn from_request(req: &'a Request<'r>) -> request::Outcome { + req.guard::>()?.first.fetch_add(1, Ordering::Relaxed); + req.local_cache(|req| { + req.guard::>().unwrap().second.fetch_add(1, Ordering::Relaxed); + }); + + Success(Guard1()) } } -impl<'a, 'r> FromRequest<'a, 'r> for SleepCached { +impl<'a, 'r> FromRequest<'a, 'r> for Guard2 { type Error = (); - fn from_request(request: &'a Request<'r>) -> request::Outcome { - let s = request.local_cache(Sleep::get); - Success(SleepCached(*s)) - } -} - -#[get("/")] -fn index(_s: Sleep, _f: ForwardGuard) { -} + fn from_request(req: &'a Request<'r>) -> request::Outcome { + req.guard::>()?.first.fetch_add(1, Ordering::Relaxed); + req.local_cache(|req| { + req.guard::>().unwrap().second.fetch_add(1, Ordering::Relaxed); + }); -#[get("/", rank = 2)] -fn index2(_s: Sleep) { + Success(Guard2()) + } } -#[get("/cached")] -fn index_cached(_s: SleepCached, _f: ForwardGuard) { -} -#[get("/cached", rank = 2)] -fn index_cached2(_s: SleepCached) { +#[get("/")] +fn index(_g1: Guard1, _g2: Guard2) { } fn rocket() -> rocket::Rocket { rocket::ignite() - .mount("/", routes!(index, index2, index_cached, index_cached2)) + .manage(Atomics{ + first: AtomicUsize::new(0), + second: AtomicUsize::new(0), + }) + .mount("/", routes!(index)) } fn main() { diff --git a/examples/request_state_local_cache/src/tests.rs b/examples/request_state_local_cache/src/tests.rs index bb4c1e1bab..3fced68227 100644 --- a/examples/request_state_local_cache/src/tests.rs +++ b/examples/request_state_local_cache/src/tests.rs @@ -1,33 +1,14 @@ -use std::time::{Instant, Duration}; +use std::sync::atomic::{Ordering}; +use ::Atomics; use super::rocket; use rocket::local::Client; -fn normal_time() -> Duration { +#[test] +fn test() { let client = Client::new(rocket()).unwrap(); - - let start = Instant::now(); client.get("/").dispatch(); - let end = Instant::now(); - - end - start -} - -fn cached_time() -> Duration { - let client = Client::new(rocket()).unwrap(); - - let start = Instant::now(); - client.get("/cached").dispatch(); - let end = Instant::now(); - - end - start -} - -#[test] -fn cache_is_faster() { - let normal = normal_time(); - let cached = cached_time(); - assert!(normal > cached, "Cached is faster"); - assert!((normal - cached).as_secs() >= 3, "Cached is at least 3 secs faster"); + assert!(client.rocket().state::().unwrap().first.load(Ordering::Relaxed) == 2, "First is two"); + assert!(client.rocket().state::().unwrap().second.load(Ordering::Relaxed) == 1, "Secon is one"); } From 63674f9d20e18d0da3b36114f876dd27b387e549 Mon Sep 17 00:00:00 2001 From: Ville Hakulinen Date: Fri, 29 Jun 2018 20:05:26 +0300 Subject: [PATCH 4/6] Improve request local cache test based on feedback --- .../request_state_local_cache/src/main.rs | 33 +++++++++---------- .../request_state_local_cache/src/tests.rs | 5 +-- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/examples/request_state_local_cache/src/main.rs b/examples/request_state_local_cache/src/main.rs index 57dc6523ec..96b3a8a725 100644 --- a/examples/request_state_local_cache/src/main.rs +++ b/examples/request_state_local_cache/src/main.rs @@ -9,24 +9,27 @@ use rocket::outcome::Outcome::*; #[cfg(test)] mod tests; +#[derive(Default)] struct Atomics { - first: AtomicUsize, - second: AtomicUsize, + uncached: AtomicUsize, + cached: AtomicUsize, } -struct Guard1(); -struct Guard2(); +struct Guard1; +struct Guard2; impl<'a, 'r> FromRequest<'a, 'r> for Guard1 { type Error = (); fn from_request(req: &'a Request<'r>) -> request::Outcome { - req.guard::>()?.first.fetch_add(1, Ordering::Relaxed); - req.local_cache(|req| { - req.guard::>().unwrap().second.fetch_add(1, Ordering::Relaxed); + req.local_cache(|| { + let atomics = req.guard::>().unwrap(); + atomics.cached.fetch_add(1, Ordering::Relaxed); }); + let atomics = req.guard::>()?; + atomics.uncached.fetch_add(1, Ordering::Relaxed); - Success(Guard1()) + Success(Guard1) } } @@ -34,26 +37,20 @@ impl<'a, 'r> FromRequest<'a, 'r> for Guard2 { type Error = (); fn from_request(req: &'a Request<'r>) -> request::Outcome { - req.guard::>()?.first.fetch_add(1, Ordering::Relaxed); - req.local_cache(|req| { - req.guard::>().unwrap().second.fetch_add(1, Ordering::Relaxed); - }); - - Success(Guard2()) + req.guard::()?; + Success(Guard2) } } #[get("/")] fn index(_g1: Guard1, _g2: Guard2) { + // This exists only to run the request guards. } fn rocket() -> rocket::Rocket { rocket::ignite() - .manage(Atomics{ - first: AtomicUsize::new(0), - second: AtomicUsize::new(0), - }) + .manage(Atomics::default()) .mount("/", routes!(index)) } diff --git a/examples/request_state_local_cache/src/tests.rs b/examples/request_state_local_cache/src/tests.rs index 3fced68227..1f105edb63 100644 --- a/examples/request_state_local_cache/src/tests.rs +++ b/examples/request_state_local_cache/src/tests.rs @@ -9,6 +9,7 @@ fn test() { let client = Client::new(rocket()).unwrap(); client.get("/").dispatch(); - assert!(client.rocket().state::().unwrap().first.load(Ordering::Relaxed) == 2, "First is two"); - assert!(client.rocket().state::().unwrap().second.load(Ordering::Relaxed) == 1, "Secon is one"); + let atomics = &client.rocket().state::().unwrap(); + assert!(atomics.uncached.load(Ordering::Relaxed) == 2, "First is two"); + assert!(atomics.cached.load(Ordering::Relaxed) == 1, "Second is one"); } From 26dc36bef181f51bb86e03d672cb8a9aaa017af1 Mon Sep 17 00:00:00 2001 From: Ville Hakulinen Date: Fri, 29 Jun 2018 20:43:18 +0300 Subject: [PATCH 5/6] Don't pass &Request to local_cache --- core/lib/src/request/request.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/lib/src/request/request.rs b/core/lib/src/request/request.rs index a8b05437c3..f6eed6ab86 100644 --- a/core/lib/src/request/request.rs +++ b/core/lib/src/request/request.rs @@ -91,10 +91,10 @@ impl<'r> Request<'r> { /// ```rust /// # use rocket::http::Method; /// # use rocket::Request; - /// # struct User(); - /// fn current_user(_: &Request) -> User { + /// # struct User; + /// fn current_user() -> User { /// // Load user... - /// # User() + /// # User /// } /// /// # Request::example(Method::Get, "/uri", |request| { @@ -103,12 +103,12 @@ impl<'r> Request<'r> { /// ``` pub fn local_cache(&self, f: F) -> &T where T: Send + Sync + 'static, - F: FnOnce(&Request) -> T { + F: FnOnce() -> T { match self.state.cache.try_get() { Some(cached) => cached, None => { - self.state.cache.set(f(self)); + self.state.cache.set(f()); self.state.cache.get() } } From 1f7ce3b157166ae833f7d95117c5701e2a5aad62 Mon Sep 17 00:00:00 2001 From: Ville Hakulinen Date: Sat, 30 Jun 2018 12:53:47 +0300 Subject: [PATCH 6/6] Minor tweaks to request state local cache tests --- examples/request_state_local_cache/src/main.rs | 6 +----- examples/request_state_local_cache/src/tests.rs | 6 +++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/examples/request_state_local_cache/src/main.rs b/examples/request_state_local_cache/src/main.rs index 96b3a8a725..c51cefa1c6 100644 --- a/examples/request_state_local_cache/src/main.rs +++ b/examples/request_state_local_cache/src/main.rs @@ -22,12 +22,9 @@ impl<'a, 'r> FromRequest<'a, 'r> for Guard1 { type Error = (); fn from_request(req: &'a Request<'r>) -> request::Outcome { - req.local_cache(|| { - let atomics = req.guard::>().unwrap(); - atomics.cached.fetch_add(1, Ordering::Relaxed); - }); let atomics = req.guard::>()?; atomics.uncached.fetch_add(1, Ordering::Relaxed); + req.local_cache(|| atomics.cached.fetch_add(1, Ordering::Relaxed)); Success(Guard1) } @@ -42,7 +39,6 @@ impl<'a, 'r> FromRequest<'a, 'r> for Guard2 { } } - #[get("/")] fn index(_g1: Guard1, _g2: Guard2) { // This exists only to run the request guards. diff --git a/examples/request_state_local_cache/src/tests.rs b/examples/request_state_local_cache/src/tests.rs index 1f105edb63..1e16f7d858 100644 --- a/examples/request_state_local_cache/src/tests.rs +++ b/examples/request_state_local_cache/src/tests.rs @@ -9,7 +9,7 @@ fn test() { let client = Client::new(rocket()).unwrap(); client.get("/").dispatch(); - let atomics = &client.rocket().state::().unwrap(); - assert!(atomics.uncached.load(Ordering::Relaxed) == 2, "First is two"); - assert!(atomics.cached.load(Ordering::Relaxed) == 1, "Second is one"); + let atomics = client.rocket().state::().unwrap(); + assert_eq!(atomics.uncached.load(Ordering::Relaxed), 2); + assert_eq!(atomics.cached.load(Ordering::Relaxed), 1); }