-
-
Notifications
You must be signed in to change notification settings - Fork 94
Closed
Description
The entry API was added to Moka v0.10.0. It has some methods such as entry_by_ref(&key).or_insert_with(|| ...)
to atomically insert a value when key does not exist. This issue will add another method and_compute
to the entry API for more complex tasks. It is similar to the compute
method in Java Caffeine cache. Moka user can use and_compute
to insert, update, or remove a value for a key with single closure or function.
A new entry API: and_compute
method
use moka::{Entry, ops::{Op, PerformedOp}};
// For moka::sync::Cache's entry API
// `K` and `V` are the key and value type of the cache.
pub fn and_compute<F>(self, f: F) -> (Option<Entry<K, V>>, PerformedOp)
where
F: FnOnce(Option<&V> -> Op<V>);
// For moka::future::Cache's entry API
pub async fn and_compute<'a, F, Fut>(self, f: F) -> (Option<Entry<K, V>>, PerformedOp)
where
F: FnOnce(Option<&'a V>) -> Fut,
Fut: Future<Output = Op<V>> + 'a;
moka::ops::Op
and moka::ops::PerformedOp
will be defined as:
/// Instructs the `and_compute` method how to modify the cache entry.
pub enum Op<V> {
/// No-op. Do not modify the cache entry.
Nop,
/// Insert or replace the value of the cache entry.
Put(V),
/// Invalidate the cache entry.
Invalidate,
}
/// Will be returned from `and_compute` method to indicate what kind of
/// operation was performed.
pub enum PerformedOp {
/// The entry did not exist, or already existed but was not modified.
Nop,
/// The entry did not exist and inserted.
Inserted,
/// The entry already existed and its value was updated.
Updated,
/// The entry existed and was invalidated.
/// Note: If `and_compute` tried to invalidate a not-exiting entry,
/// `Nop` will be returned instead of `Invalidated`
Invalidated,
}
Example
Here is an example of a future cache holding counters. (from a question in #179)
use moka::{future::Cache, ops::{Op, PerformedOp}};
let cache: Cache<String, u64> = Cache::new(100);
let key = "key".to_string();
// maybe_entry: Option<moka::Entry<K, V>>
// op_kind: moka::op::PerformedOp
let (maybe_entry, performed_op) = cache
.entry_by_ref(&key)
.and_compute(|entry| async move {
match entry {
// If the entry does not exist, insert a value of 1.
None => Op::Put(1),
// If the entry exists, increment the value by 1.
Some(count) => Op::Put(count.saturating_add(1)),
}
})
.await;
assert_eq!(performed_op, PerformedOp::Inserted);
assert_eq!(maybe_entry.unwrap().into_value(), 1);
Concurrency
The and_compute
should have the following attributes about concurrency:
- It is a single operation that is atomic with respect to other operations. It is not possible to observe an intermediate state where the entry is neither present nor absent.
- If there are simultaneous
and_compute
calls on the same key, only one call will proceed at a time. The other calls will wait until the first call completes.- This is also true between
and_compute
andor_insert_with
on the same key. Only one call will proceed at a time, and the other calls will wait until the first call completes.
- This is also true between
- Unlike
or_insert_with
, there is no call coercing between simultaneousand_compute
calls on the same key.- This is because, in typical use cases,
and_compute
will not do idempotent operation. (e.g. counting up a value)
- This is because, in typical use cases,
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request
Type
Projects
Status
Done