Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a3913f1
feat(cachet): add get_or_insert_with and try_get_or_insert_with
codingsnoop May 21, 2026
bdd8c2c
get or insert with
codingsnoop May 26, 2026
bb99884
add eviction telemetry
codingsnoop May 26, 2026
7203747
docs
codingsnoop May 26, 2026
3ca6969
spellcheck fix
codingsnoop May 26, 2026
e39727a
clippy
codingsnoop May 26, 2026
e981439
bump workspace version
codingsnoop May 26, 2026
52422ad
add nuance to docs
codingsnoop May 26, 2026
5e0d6e3
implement eviction listener threading
codingsnoop May 27, 2026
60be0d2
Merge branch 'u/emengesha/cachet-get-or-insert-with' of https://githu…
codingsnoop May 27, 2026
1301827
add impl unwindsafe and refunwindsafe
codingsnoop May 27, 2026
61de5b6
sort cargo.toml
codingsnoop May 27, 2026
b3c73f1
docs, version fixes
codingsnoop May 27, 2026
edcd67f
spelling
codingsnoop May 27, 2026
a486e05
pare docs
codingsnoop May 27, 2026
b2fe698
move telemetry out of cache and into inmemorycache
codingsnoop May 27, 2026
9bb9341
doc to specify eviction telemetry details in memory api
codingsnoop May 27, 2026
476d5e4
Merge branch 'main' into u/emengesha/cachet-get-or-insert-with
codingsnoop May 27, 2026
b8e56fc
fix: replace broken intra-doc link with plain code formatting in cach…
Copilot May 27, 2026
f692df2
Potential fix for pull request finding
codingsnoop May 27, 2026
e9f2d22
update readme
codingsnoop May 27, 2026
0ee4f33
Merge branch 'u/emengesha/cachet-get-or-insert-with' of https://githu…
codingsnoop May 27, 2026
57fa37a
add tests to in memory builder
codingsnoop May 27, 2026
732c4ad
nit
codingsnoop May 27, 2026
94cfeb1
fix up testing
codingsnoop May 28, 2026
816638f
fix cached at bug
codingsnoop May 28, 2026
676b2e9
update docs
codingsnoop May 28, 2026
0a7b66e
Merge branch 'main' into u/emengesha/cachet-get-or-insert-with
codingsnoop May 28, 2026
6516177
Merge branch 'main' into u/emengesha/cachet-get-or-insert-with
codingsnoop May 29, 2026
8a3442d
bump cachet_memory
codingsnoop May 29, 2026
2a86cc7
chain listeners instead of overwrite with eviction listner
codingsnoop May 29, 2026
ee7514e
Merge branch 'main' into u/emengesha/cachet-get-or-insert-with
codingsnoop May 29, 2026
28cca13
Merge branch 'u/emengesha/cachet-get-or-insert-with' of https://githu…
codingsnoop May 29, 2026
40d712a
fmt
codingsnoop May 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ homepage = "https://github.com/microsoft/oxidizer"
anyspawn = { path = "crates/anyspawn", default-features = false, version = "0.5.0" }
bytesbuf = { path = "crates/bytesbuf", default-features = false, version = "0.5.0" }
bytesbuf_io = { path = "crates/bytesbuf_io", default-features = false, version = "0.5.0" }
cachet = { path = "crates/cachet", default-features = false, version = "0.5.0" }
cachet_memory = { path = "crates/cachet_memory", default-features = false, version = "0.2.0" }
cachet = { path = "crates/cachet", default-features = false, version = "0.5.1" }
cachet_memory = { path = "crates/cachet_memory", default-features = false, version = "0.2.1" }
cachet_service = { path = "crates/cachet_service", default-features = false, version = "0.1.0" }
cachet_tier = { path = "crates/cachet_tier", default-features = false, version = "0.1.0" }
data_privacy = { path = "crates/data_privacy", default-features = false, version = "0.11.0" }
Expand Down
7 changes: 7 additions & 0 deletions crates/cachet/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [0.5.1] - 2026-05-21

- ✨ Features

- Add `get_or_insert_with` and `try_get_or_insert_with` methods that accept closures returning `CacheEntry<V>`, enabling per-entry TTL control on cache-miss computations.
Comment thread
codingsnoop marked this conversation as resolved.
Comment thread
codingsnoop marked this conversation as resolved.
Comment thread
codingsnoop marked this conversation as resolved.
- Add eviction telemetry via `cache.eviction` and `cache.expired`, opt-in through `InMemoryCacheBuilder::with_eviction_telemetry` together with the new `CacheBuilder::memory_with` helper.

## [0.5.0] - 2026-05-19

- ✔️ Tasks
Expand Down
6 changes: 5 additions & 1 deletion crates/cachet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[package]
name = "cachet"
description = "A composable, customizable multi-tier caching library with rich feature support."
version = "0.5.0"
version = "0.5.1"
Comment thread
codingsnoop marked this conversation as resolved.
readme = "README.md"
Comment thread
codingsnoop marked this conversation as resolved.
Comment thread
codingsnoop marked this conversation as resolved.
Comment thread
codingsnoop marked this conversation as resolved.
keywords = ["oxidizer", "caching", "concurrency"]
categories = ["caching", "concurrency"]
Expand Down Expand Up @@ -99,6 +99,10 @@ name = "refresh"
harness = false
required-features = ["test-util"]

[[test]]
name = "eviction"
required-features = ["memory", "logs"]

[[example]]
name = "simple"
required-features = ["memory"]
Expand Down
24 changes: 12 additions & 12 deletions crates/cachet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ See the `telemetry_subscriber` example for a complete demonstration.
|Level|Events|
|-----|------|
|ERROR|`cache.get_error`, `cache.insert_error`, `cache.invalidate_error`, `cache.clear_error`|
|INFO|`cache.expired`, `cache.refresh_miss`, `cache.inserted`, `cache.insert_rejected`, `cache.invalidated`, `cache.fallback`|
|INFO|`cache.expired`, `cache.refresh_miss`, `cache.inserted`, `cache.insert_rejected`, `cache.invalidated`, `cache.fallback`, `cache.eviction`|
|DEBUG|`cache.hit`, `cache.miss`, `cache.refresh_hit`, `cache.cleared`|


Expand All @@ -265,26 +265,26 @@ See the `telemetry_subscriber` example for a complete demonstration.
This crate was developed as part of <a href="../..">The Oxidizer Project</a>. Browse this crate's <a href="https://github.com/microsoft/oxidizer/tree/main/crates/cachet">source code</a>.
</sub>

[__cargo_doc2readme_dependencies_info]: ggGkYW0CYXSEGy4k8ldDFPOhG2VNeXtD5nnKG6EPY6OfW5wBG8g18NOFNdxpYXKEG-n6c1asXb8uG6CeIkUqNVu-GxjETKbvQrZwG4I5xAXRHmeEYWSIgmhieXRlc2J1ZmUwLjUuMIJmY2FjaGV0ZTAuNS4wgm1jYWNoZXRfbWVtb3J5ZTAuMi4wgm5jYWNoZXRfc2VydmljZWUwLjEuMIJrY2FjaGV0X3RpZXJlMC4xLjCCZHRpY2tlMC4zLjCCZ3RyYWNpbmdmMC4xLjQ0gml1bmlmbGlnaHRlMC4yLjA
[__link0]: https://docs.rs/cachet/0.5.0/cachet/?search=TimeToRefresh
[__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjJhdIQbLiTyV0MU86EbZU15e0PmecoboQ9jo59bnAEbyDXw04U13GlhYvRhcoQbg_hDqE88LP4bMh0J5Y4y4Osb0zDJ1kwqOsoblCGrm49Rx2thZIiCaGJ5dGVzYnVmZTAuNS4wgmZjYWNoZXRlMC41LjGCbWNhY2hldF9tZW1vcnllMC4yLjGCbmNhY2hldF9zZXJ2aWNlZTAuMS4wgmtjYWNoZXRfdGllcmUwLjEuMIJkdGlja2UwLjMuMIJndHJhY2luZ2YwLjEuNDSCaXVuaWZsaWdodGUwLjIuMA
[__link0]: https://docs.rs/cachet/0.5.1/cachet/?search=TimeToRefresh
[__link1]: https://crates.io/crates/uniflight/0.2.0
[__link10]: https://docs.rs/cachet_tier/0.1.0/cachet_tier/?search=CacheTier
[__link11]: https://docs.rs/cachet/0.5.0/cachet/?search=InsertPolicy
[__link12]: https://docs.rs/cachet/0.5.0/cachet/?search=TimeToRefresh
[__link11]: https://docs.rs/cachet/0.5.1/cachet/?search=InsertPolicy
[__link12]: https://docs.rs/cachet/0.5.1/cachet/?search=TimeToRefresh
[__link13]: https://docs.rs/cachet_tier/0.1.0/cachet_tier/?search=Error
[__link14]: https://crates.io/crates/cachet_tier/0.1.0
[__link15]: https://crates.io/crates/cachet_memory/0.2.0
[__link15]: https://crates.io/crates/cachet_memory/0.2.1
[__link16]: https://docs.rs/moka
[__link17]: https://crates.io/crates/cachet_service/0.1.0
[__link18]: https://docs.rs/cachet/0.5.0/cachet/?search=telemetry::attributes
[__link18]: https://docs.rs/cachet/0.5.1/cachet/?search=telemetry::attributes
[__link19]: https://docs.rs/bytesbuf/0.5.0/bytesbuf/?search=BytesView
[__link2]: https://docs.rs/cachet/0.5.0/cachet/?search=CacheBuilder::stampede_protection
[__link2]: https://docs.rs/cachet/0.5.1/cachet/?search=CacheBuilder::stampede_protection
[__link20]: https://crates.io/crates/tracing/0.1.44
[__link21]: https://docs.rs/cachet/0.5.0/cachet/?search=telemetry::attributes
[__link21]: https://docs.rs/cachet/0.5.1/cachet/?search=telemetry::attributes
[__link3]: https://docs.rs/cachet_tier/0.1.0/cachet_tier/?search=CacheTier
[__link4]: https://docs.rs/cachet_tier/0.1.0/cachet_tier/?search=DynamicCache
[__link5]: https://docs.rs/cachet/0.5.0/cachet/?search=InsertPolicy
[__link5]: https://docs.rs/cachet/0.5.1/cachet/?search=InsertPolicy
[__link6]: https://docs.rs/tick/0.3.0/tick/?search=Clock
[__link7]: https://docs.rs/cachet/0.5.0/cachet/?search=Cache
[__link8]: https://docs.rs/cachet/0.5.0/cachet/?search=CacheBuilder
[__link7]: https://docs.rs/cachet/0.5.1/cachet/?search=Cache
[__link8]: https://docs.rs/cachet/0.5.1/cachet/?search=CacheBuilder
[__link9]: https://docs.rs/cachet_tier/0.1.0/cachet_tier/?search=CacheEntry
7 changes: 6 additions & 1 deletion crates/cachet/src/builder/buildable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ where
}

fn build_tier(self, clock: Clock, telemetry: CacheTelemetry) -> Self::TierOutput {
CacheWrapper::new(type_name::<CT>(self.name), self.storage, clock, self.ttl, telemetry, self.policy)
let name = type_name::<CT>(self.name);
#[cfg(feature = "memory")]
if let Some(hook) = &self.eviction_hook {
hook.init(telemetry.clone(), name);
}
CacheWrapper::new(name, self.storage, clock, self.ttl, telemetry, self.policy)
}
}

Expand Down
57 changes: 55 additions & 2 deletions crates/cachet/src/builder/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@

use std::hash::Hash;
use std::marker::PhantomData;
#[cfg(feature = "memory")]
use std::sync::Arc;
use std::time::Duration;

#[cfg(feature = "memory")]
use cachet_memory::InMemoryCache;
use cachet_memory::{InMemoryCache, InMemoryCacheBuilder};
use tick::Clock;

use super::buildable::Buildable;
use super::fallback::FallbackBuilder;
use super::sealed::{CacheTierBuilder, Sealed};
#[cfg(feature = "memory")]
use crate::eviction::EvictionHook;
use crate::policy::InsertPolicy;
use crate::telemetry::CacheTelemetry;
use crate::{Cache, CacheTier};
Expand Down Expand Up @@ -44,6 +48,8 @@ pub struct CacheBuilder<K, V, CT = ()> {
pub(crate) clock: Clock,
pub(crate) telemetry: CacheTelemetry,
pub(crate) stampede_protection: bool,
#[cfg(feature = "memory")]
pub(crate) eviction_hook: Option<Arc<EvictionHook>>,
pub(crate) _phantom: PhantomData<(K, V)>,
}

Expand All @@ -57,6 +63,8 @@ impl<K, V> CacheBuilder<K, V, ()> {
clock,
telemetry: CacheTelemetry::new(),
stampede_protection: false,
#[cfg(feature = "memory")]
eviction_hook: None,
_phantom: PhantomData,
}
}
Expand Down Expand Up @@ -90,6 +98,8 @@ impl<K, V> CacheBuilder<K, V, ()> {
clock: self.clock,
telemetry: self.telemetry,
stampede_protection: self.stampede_protection,
#[cfg(feature = "memory")]
eviction_hook: self.eviction_hook,
_phantom: PhantomData,
}
}
Expand All @@ -115,7 +125,50 @@ impl<K, V> CacheBuilder<K, V, ()> {
K: Hash + Eq + Clone + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
{
self.storage(InMemoryCache::<K, V>::new())
self.memory_with(|b| b)
}

/// Configures the cache to use in-memory storage, exposing the inner
/// [`InMemoryCacheBuilder`] for additional configuration (capacity, TTL,
/// eviction policy, custom hasher, etc.).
///
/// Call [`InMemoryCacheBuilder::with_eviction_telemetry`] inside the
/// closure to emit `cache.eviction` on capacity evictions and
/// `cache.expired` on background TTL/TTI expiry.
///
/// # Panics
///
/// Panics if the configured [`InMemoryCacheBuilder`] fails validation
/// (for example, `max_capacity < initial_capacity`).
///
/// # Examples
///
/// ```no_run
/// use cachet::Cache;
/// use tick::Clock;
///
/// let clock = Clock::new_tokio();
/// let cache = Cache::builder::<String, i32>(clock)
/// .memory_with(|b| b.max_capacity(1_000).with_eviction_telemetry())
/// .build();
/// ```
#[cfg(feature = "memory")]
#[must_use]
pub fn memory_with<F>(mut self, configure: F) -> CacheBuilder<K, V, InMemoryCache<K, V>>
where
K: Hash + Eq + Clone + Send + Sync + 'static,
V: Clone + Send + Sync + 'static,
F: FnOnce(InMemoryCacheBuilder<K, V>) -> InMemoryCacheBuilder<K, V>,
{
let mut builder = configure(InMemoryCacheBuilder::<K, V>::new());
if builder.eviction_telemetry_enabled() {
let hook = Arc::new(EvictionHook::new());
let hook_for_listener = Arc::clone(&hook);
builder = builder.on_eviction(move |cause| hook_for_listener.handle(cause));
self.eviction_hook = Some(hook);
}
let storage = builder.build().expect("InMemoryCacheBuilder configuration must be valid");
self.storage(storage)
}

/// Configures the cache to use a service as the storage backend.
Expand Down
Loading
Loading