-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement request-state local cache #655
Conversation
Please add some doc-tests, unit tests, and integration tests. |
Sorry, didn't mean to close this PR. |
Depending on "wall time" for the test is error prone. Instead, lets use something like two request guards each which access the same pair of In pseudocode, this is: #[get("/")]
fn handler(Guard1, Guard2) { .. }
impl FromRequest for {Guard1, Guard2} {
req.guard::<State<Atomics>>()?.first.increment();
req.local_cache(|req| req.guard::<State<Atomics>>()?.second.increment());
}
fn main() {
rocket::ignite().manage(Atomics);
}
#[test]
fn test() {
client.get("/").dispatch();
assert_eq!(client.rocket().state::<Atomics>().unwrap().first.value(), 2);
assert_eq!(client.rocket().state::<Atomics>().unwrap().second.value(), 1);
} |
type Error = (); | ||
|
||
fn from_request(req: &'a Request<'r>) -> request::Outcome<Self, ()> { | ||
req.guard::<State<Atomics>>()?.first.fetch_add(1, Ordering::Relaxed); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just call the Guard1
request guard here.
second: AtomicUsize, | ||
} | ||
|
||
struct Guard1(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for ()
here or when it's constructed.
let client = Client::new(rocket()).unwrap(); | ||
client.get("/").dispatch(); | ||
|
||
assert!(client.rocket().state::<Atomics>().unwrap().first.load(Ordering::Relaxed) == 2, "First is two"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Store the reference to Atomics
in a variable to clean these assert!
s up:
let atomics = client.rocket().state::<Atomics>().unwrap();
assert_eq!(atomics.uncached.load(Ordering::Relaxed), 2);
assert_eq!(atomics.cached.load(Ordering::Relaxed), 2);
#[cfg(test)] mod tests; | ||
|
||
struct Atomics { | ||
first: AtomicUsize, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's rename these cached
and uncached
.
|
||
fn rocket() -> rocket::Rocket { | ||
rocket::ignite() | ||
.manage(Atomics{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead, derive Default
for Atomics
and call Atomics::default()
here.
core/lib/src/request/request.rs
Outdated
/// ``` | ||
pub fn local_cache<T, F>(&self, f: F) -> &T | ||
where T: Send + Sync + 'static, | ||
F: FnOnce(&Request) -> T { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought: why are we passing self
to F
? The caller already has a handle to request
, so they can use it if they need to. I think this should probably be F: FnOnce() -> T
.
|
||
|
||
#[get("/")] | ||
fn index(_g1: Guard1, _g2: Guard2) { |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
type Error = (); | ||
|
||
fn from_request(req: &'a Request<'r>) -> request::Outcome<Self, ()> { | ||
req.guard::<State<Atomics>>()?.first.fetch_add(1, Ordering::Relaxed); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull out the atomics
state loading:
let atomics = req.guard::<State<Atomics>>()?;
atomics.uncached.fetch_add(1, Ordering::Relaxed);
req.local_cache(|| atomics.cached.fetch_add(1, Ordering::Relaxed));
Guard1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change wasn't made.
|
||
fn from_request(req: &'a Request<'r>) -> request::Outcome<Self, ()> { | ||
req.guard::<State<Atomics>>()?.first.fetch_add(1, Ordering::Relaxed); | ||
req.local_cache(|req| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is saving a value of type ()
in the cache. I suppose that's fine, but let's at least leave a comment indicating this.
Updated the PR based on feedback. I've been wondering whether or not the use of trait would be better for |
client.get("/").dispatch(); | ||
|
||
let atomics = &client.rocket().state::<Atomics>().unwrap(); | ||
assert!(atomics.uncached.load(Ordering::Relaxed) == 2, "First is two"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for the string comment. Also, use assert_eq
. Same for the one below.
let client = Client::new(rocket()).unwrap(); | ||
client.get("/").dispatch(); | ||
|
||
let atomics = &client.rocket().state::<Atomics>().unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you need the &
there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmh, when I tried without &
, I had to de-reference ((*atomics).cached...
) the value when I wanted to access it, for some reason. Seems to work without it now.
Success(Guard2) | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kill this extra empty line.
type Error = (); | ||
|
||
fn from_request(req: &'a Request<'r>) -> request::Outcome<Self, ()> { | ||
req.local_cache(|| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make the change in the previous comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see I commited my experiments.
Merged in 97c6b3a. Hoorah! Thanks so much for this! |
Opening this PR for feedback. Based completely on @SergioBenitez's design.
I guess the next step is to extend the
state
crate to allow more relaxed bounds forT
in thelocal_cache
?The
cache
needs to be wrapped inRc
so it getsClone
, but it was mentioned that in futureRequest
might be send to another thread to be processed, so should we already useArc
instead?Simple example usage https://gist.github.com/vhakulinen/fb84746fa102761c0683f694523a47cd.
Closes #654.