Mercurial > lbo > hg > memoize
changeset 38:c266d26acf7f
Merge pull request #2 from inferrna/ttl_as_duration
Ttl as duration
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Thu, 10 Dec 2020 19:17:23 +0100 |
parents | 9bcf7bab10a4 (current diff) 39b49b493180 (diff) |
children | 390b3b7095cc |
files | |
diffstat | 3 files changed, 90 insertions(+), 17 deletions(-) [+] |
line wrap: on
line diff
--- a/README.md Fri Oct 16 10:32:22 2020 +0200 +++ b/README.md Thu Dec 10 19:17:23 2020 +0100 @@ -77,6 +77,18 @@ of parsing attribute parameters. Currently, compiling will fail if you use a parameter such as `Capacity` without the feature `full` being enabled. + +Another parameter is TimeToLive - it's targeting to refresh outdated values. +Example: +```rust +#[memoize(Capacity: 123, TimeToLive: Duration::from_secs(2))] +``` +chrono::Duration is also possible, but have to be converted into std::time::Duration +```rust +#[memoize(TimeToLive: chrono::Duration::hours(3).to_std().unwrap())] +``` +cached value will be actual no longer than duration provided and refreshed with next request. + ## Contributions ...are always welcome! This being my first procedural-macros crate, I am
--- a/examples/test1.rs Fri Oct 16 10:32:22 2020 +0200 +++ b/examples/test1.rs Thu Dec 10 19:17:23 2020 +0100 @@ -1,25 +1,34 @@ use memoize::memoize; +use std::time::{Instant, Duration}; +use std::thread; #[derive(Debug, Clone)] struct ComplexStruct { s: String, b: bool, - i: i32, + i: Instant, } -#[memoize(Capacity: 123)] +#[memoize(TimeToLive: Duration::from_secs(2), Capacity: 123)] fn hello(key: String) -> ComplexStruct { println!("hello: {}", key); ComplexStruct { s: key, b: false, - i: 332, + i: Instant::now(), } } + + fn main() { println!("result: {:?}", hello("ABC".to_string())); println!("result: {:?}", hello("DEF".to_string())); - println!("result: {:?}", hello("ABC".to_string())); + println!("result: {:?}", hello("ABC".to_string())); //Same as first + thread::sleep(core::time::Duration::from_millis(2100)); + println!("result: {:?}", hello("EFG".to_string())); + println!("result: {:?}", hello("ABC".to_string())); //Refreshed + println!("result: {:?}", hello("EFG".to_string())); //Same as first + println!("result: {:?}", hello("ABC".to_string())); //Same as refreshed println!("result: {:?}", memoized_original_hello("ABC".to_string())); }
--- a/src/lib.rs Fri Oct 16 10:32:22 2020 +0200 +++ b/src/lib.rs Thu Dec 10 19:17:23 2020 +0100 @@ -4,7 +4,8 @@ use syn::{parse_macro_input, spanned::Spanned, ItemFn}; use proc_macro::TokenStream; -use quote::{self, ToTokens}; +use quote::{self}; +use syn::export::ToTokens; // This implementation of the storage backend does not depend on any more crates. #[cfg(not(feature = "full"))] @@ -46,19 +47,24 @@ #[cfg(feature = "full")] mod store { use proc_macro::TokenStream; - use syn::parse as p; + use syn::{parse as p, Expr}; + use syn::export::ToTokens; + - #[derive(Default, Debug, Clone)] - struct CacheOptions { + #[derive(Default, Clone)] + pub(crate) struct CacheOptions { lru_max_entries: Option<usize>, + pub(crate) time_to_live: Option<Expr>, } - #[derive(Debug, Clone)] + #[derive(Clone)] enum CacheOption { LRUMaxEntries(usize), + TimeToLive(Expr), } syn::custom_keyword!(Capacity); + syn::custom_keyword!(TimeToLive); syn::custom_punctuation!(Colon, :); // To extend option parsing, add functionality here. @@ -72,6 +78,13 @@ return Ok(CacheOption::LRUMaxEntries(cap.base10_parse()?)); } + if la.peek(TimeToLive) { + let _: TimeToLive = input.parse().unwrap(); + let _: Colon = input.parse().unwrap(); + let cap: syn::Expr = input.parse().unwrap(); + + return Ok(CacheOption::TimeToLive(cap)); + } Err(la.error()) } } @@ -85,6 +98,7 @@ for opt in f { match opt { CacheOption::LRUMaxEntries(cap) => opts.lru_max_entries = Some(cap), + CacheOption::TimeToLive(sec) => opts.time_to_live = Some(sec), } } Ok(opts) @@ -103,6 +117,10 @@ ) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { let options: CacheOptions = syn::parse(attr.clone()).unwrap(); + let value_type = match options.time_to_live { + None => quote::quote! {#value_type}, + Some(_) => quote::quote! {(std::time::Instant, #value_type)}, + }; // This is the unbounded default. match options.lru_max_entries { None => ( @@ -167,6 +185,9 @@ * The `memoize` attribute can take further parameters in order to use an LRU cache: * `#[memoize(Capacity: 1234)]`. In that case, instead of a `HashMap` we use an `lru::LruCache` * with the given capacity. + * `#[memoize(TimeToLive: Duration::from_secs(2))]`. In that case, cached value will be actual + * no longer than duration provided and refreshed with next request. If you prefer chrono::Duration, + * it can be also used: `#[memoize(TimeToLive: chrono::Duration::hours(9).to_std().unwrap()]` * * This mechanism can, in principle, be extended (in the source code) to any other cache mechanism. * @@ -219,17 +240,48 @@ let syntax_names_tuple = quote::quote! { (#(#input_names),*) }; let syntax_names_tuple_cloned = quote::quote! { (#(#input_names.clone()),*) }; let (insert_fn, get_fn) = store::cache_access_methods(&attr); - let memoizer = quote::quote! { - #sig { - let mut hm = &mut #store_ident.lock().unwrap(); - if let Some(r) = hm.#get_fn(&#syntax_names_tuple_cloned) { - return r.clone(); + #[cfg(feature = "full")] + let memoizer = { + let options: store::CacheOptions = syn::parse(attr.clone().into()).unwrap(); + match options.time_to_live { + None => quote::quote! { + #sig { + let mut hm = &mut #store_ident.lock().unwrap(); + if let Some(r) = hm.#get_fn(&#syntax_names_tuple_cloned) { + return r.clone(); + } + let r = #memoized_id(#(#input_names.clone()),*); + hm.#insert_fn(#syntax_names_tuple, r.clone()); + r + } + }, + Some(ttl) => quote::quote! { + #sig { + let mut hm = &mut #store_ident.lock().unwrap(); + if let Some((last_updated, r)) = hm.#get_fn(&#syntax_names_tuple_cloned) { + if last_updated.elapsed() < #ttl { + return r.clone(); + } + } + let r = #memoized_id(#(#input_names.clone()),*); + hm.#insert_fn(#syntax_names_tuple, (std::time::Instant::now(), r.clone())); + r + } } - let r = #memoized_id(#(#input_names.clone()),*); - hm.#insert_fn(#syntax_names_tuple, r.clone()); - r } }; + #[cfg(not(feature = "full"))] + let memoizer = quote::quote! { + #sig { + let mut hm = &mut #store_ident.lock().unwrap(); + if let Some(r) = hm.#get_fn(&#syntax_names_tuple_cloned) { + return r.clone(); + } + let r = #memoized_id(#(#input_names.clone()),*); + hm.#insert_fn(#syntax_names_tuple, r.clone()); + r + } + }; (quote::quote! { #store