Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
26d66b7
Add serialize feature with BincodeEncoder/BincodeCodec and Distribute…
schgoo Apr 8, 2026
36f2ed8
Add serialize integration tests
schgoo Apr 9, 2026
0c331c3
Add serialization to transform builder and serialize codices
schgoo Apr 15, 2026
7e09426
Add examples, tighten serialize test
schgoo Apr 15, 2026
1a0d1ee
Add impl Into<CacheEntry<V>> for insert
schgoo Apr 15, 2026
08c4af9
Make decode take by value to avoid clones
schgoo Apr 15, 2026
1e7bab6
Fixes
schgoo Apr 15, 2026
c3896a7
static analysis fixes
schgoo Apr 15, 2026
b1c232f
Switch to postcard for serialization
schgoo Apr 16, 2026
bd929f8
Use thread local memory pool
schgoo Apr 16, 2026
41ac706
Remove DistributedCacheTier for now
schgoo Apr 16, 2026
34611bf
Remove unnecessary CacheEntry PartialEq implementation. Temporarily r…
schgoo Apr 17, 2026
8f3970e
feat(cachet): add format version byte, Option decode, and cfg-gated t…
schgoo Apr 28, 2026
3ecd068
Update docs
schgoo Apr 28, 2026
7706bf9
Update serialization docs from bincode to postcard
schgoo Apr 28, 2026
4b1ad30
Update serialization docs from bincode to postcard
schgoo Apr 28, 2026
0a2432d
1. builder/serialize.rs — cachet_tier::CacheTier → crate::CacheTier (…
schgoo Apr 28, 2026
bd8344d
Merge with main
schgoo Apr 28, 2026
2781db5
spelling, sorting, formatting
schgoo Apr 28, 2026
aa398b6
Code coverage
schgoo Apr 28, 2026
24a41d8
Remove unnecessary unsafe, fix clippy and fmt
schgoo Apr 28, 2026
4eb3bdd
Code coverage
schgoo Apr 28, 2026
a17038f
Remove accidental addition
schgoo Apr 28, 2026
21850c2
clippy
schgoo Apr 28, 2026
c4a4b51
Remove serialize telemetry, will add it back later with some telemetr…
schgoo Apr 30, 2026
202d070
Formatting
schgoo Apr 30, 2026
20e1d54
BytesBuf::hash unit tests. Doc fixes
schgoo May 1, 2026
6d76e6f
Code coverage
schgoo May 1, 2026
5f5090c
Fix unused use statement
schgoo May 1, 2026
41a7895
Code coverage
schgoo May 1, 2026
506d011
Add MockCodec for transform testing
schgoo May 1, 2026
caf4a5b
Spelling and formatting
schgoo May 1, 2026
ae5b44b
Code coverage
schgoo May 1, 2026
16880b3
Init GlobalPool for serialization codecs once per cache
schgoo May 6, 2026
7e1a503
PR fixes
schgoo May 6, 2026
6dd465f
Merge with main
schgoo May 6, 2026
7ea3ac6
Don't need test-util for MockCodec
schgoo May 6, 2026
6666e97
Clippy
schgoo May 6, 2026
48032e7
Merge with main
schgoo May 7, 2026
39e479f
Merge with main
schgoo May 7, 2026
c540912
Remove unnecessary external exposures
schgoo May 7, 2026
720f4a6
Merge branch 'main' into u/schgoo/serialize
schgoo May 8, 2026
1604454
Merge with main
schgoo May 11, 2026
a1a800b
Merge branch 'main' into u/schgoo/serialize
schgoo May 12, 2026
38ef89f
Formatting
schgoo May 12, 2026
a75aa9f
Merge branch 'u/schgoo/serialize' of https://github.com/microsoft/oxi…
schgoo May 12, 2026
aaeaf8a
Formatting
schgoo May 12, 2026
cae0af6
Merge branch 'main' into u/schgoo/serialize
schgoo May 12, 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
2 changes: 2 additions & 0 deletions .spelling
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Barbosa
Base64-encoded
bitflag
bitwise
bincode
bool
branch_name
btree_map
Expand Down Expand Up @@ -327,6 +328,7 @@ uncategorized
unconfigured
Multithreaded
uncontended
undecodable
unhandleable
unicode
uniflight
Expand Down
64 changes: 50 additions & 14 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ parking_lot = { version = "0.12.5", default-features = false }
pct-str = { version = "3.0.1", default-features = false }
pin-project = { version = "1.1.8", default-features = false }
pin-project-lite = { version = "0.2.13", default-features = false }
postcard = { version = "1.1.3", default-features = false, features = ["use-std"] }
pretty_assertions = { version = "1.4.1", default-features = false }
prettyplease = { version = "0.2.37", default-features = false }
proc-macro2 = { version = "1.0.103", default-features = false }
Expand Down
70 changes: 70 additions & 0 deletions crates/bytesbuf/src/view.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use std::hash::{Hash, Hasher};
use std::iter;
use std::num::NonZero;
use std::ops::{Bound, RangeBounds};
Expand Down Expand Up @@ -864,6 +865,24 @@ impl<const LEN: usize> PartialEq<BytesView> for &[u8; LEN] {

impl Eq for BytesView {}

impl Hash for BytesView {
fn hash<H: Hasher>(&self, state: &mut H) {
// Hash all bytes in logical order, consistent with PartialEq.
// We also hash the cached length as an additional input, but equality and hashing are
// both defined by the logical byte sequence rather than how it is segmented into spans.
//
// We iterate over spans_reversed using an index to avoid cloning the view,
// which would increment atomic reference counts for every span.
self.len.hash(state);
let mut span_idx = self.spans_reversed.len();
while span_idx > 0 {
span_idx -= 1;
let span: &[u8] = &self.spans_reversed[span_idx];
state.write(span);
}
}
}

/// Iterator over the slices of a [`BytesView`] and their metadata.
///
/// Returned by [`BytesView::slices()`] and provides each slice together with its
Expand Down Expand Up @@ -1713,4 +1732,55 @@ mod tests {
// We assume 64-bit pointers - any support for 32-bit is problem for the future.
assert_eq!(size_of::<BytesView>(), 272);
}

#[test]
fn hash_equal_views_produce_same_hash() {
use std::hash::{DefaultHasher, Hash, Hasher};

let a = BytesView::from(vec![1, 2, 3, 4, 5]);
let b = BytesView::from(vec![1, 2, 3, 4, 5]);

let hash_of = |v: &BytesView| {
let mut h = DefaultHasher::new();
v.hash(&mut h);
h.finish()
};

assert_eq!(hash_of(&a), hash_of(&b));
}

#[test]
fn hash_multi_span_matches_single_span() {
use std::hash::{DefaultHasher, Hash, Hasher};

let single = BytesView::from(vec![1, 2, 3, 4, 5, 6]);

let mut multi = BytesView::from(vec![1, 2, 3]);
multi.append(BytesView::from(vec![4, 5, 6]));

let hash_of = |v: &BytesView| {
let mut h = DefaultHasher::new();
v.hash(&mut h);
h.finish()
};

assert_eq!(single, multi);
assert_eq!(hash_of(&single), hash_of(&multi));
}

#[test]
fn hash_different_views_produce_different_hash() {
use std::hash::{DefaultHasher, Hash, Hasher};

let a = BytesView::from(vec![1, 2, 3]);
let b = BytesView::from(vec![4, 5, 6]);

let hash_of = |v: &BytesView| {
let mut h = DefaultHasher::new();
v.hash(&mut h);
h.finish()
};

assert_ne!(hash_of(&a), hash_of(&b));
}
}
17 changes: 16 additions & 1 deletion crates/cachet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ all-features = true
allowed_external_types = [
# Workspace sibling crates
"anyspawn::spawner::Spawner",
"bytesbuf::view::BytesView",
"cachet_memory::*",
"cachet_service::*",
"cachet_tier::*",
"layered::service::Service",
"tick::clock::Clock",
# External dependencies
"opentelemetry::metrics::meter::Meter",
"opentelemetry::metrics::meter::MeterProvider",
"serde_core::de::DeserializeOwned",
"serde_core::ser::Serialize",
]

[features]
Expand All @@ -40,9 +42,12 @@ test-util = ["cachet_tier/test-util", "tick/test-util"]
memory = ["dep:cachet_memory"]
metrics = ["dep:opentelemetry", "opentelemetry/metrics"]
service = ["dep:cachet_service", "dep:layered"]
serialize = ["dep:serde", "dep:postcard", "dep:bytesbuf"]
telemetry = []

[dependencies]
anyspawn = { workspace = true, features = ["tokio"] }
bytesbuf = { workspace = true, optional = true }
cachet_memory = { workspace = true, optional = true }
cachet_service = { workspace = true, optional = true }
cachet_tier = { workspace = true }
Expand All @@ -52,13 +57,17 @@ ohno = { workspace = true }
opentelemetry = { workspace = true, optional = true }
parking_lot = { workspace = true }
pin-project-lite = { workspace = true }
postcard = { workspace = true, optional = true }
serde = { workspace = true, optional = true, features = ["derive"] }
thread_aware = { workspace = true }
tick = { workspace = true, features = [] }
tracing = { workspace = true, optional = true }
uniflight = { workspace = true }

[dev-dependencies]
alloc_tracker = { workspace = true }
bytesbuf = { workspace = true }
cachet_memory = { workspace = true }
cachet_tier = { workspace = true, features = ["test-util"] }
criterion = { workspace = true }
dynosaur = { workspace = true }
Expand All @@ -67,8 +76,10 @@ opentelemetry = { workspace = true, features = [
"logs",
] }
opentelemetry_sdk = { workspace = true, features = ["logs", "testing"] }
postcard = { workspace = true }
recoverable = { workspace = true }
seatbelt = { path = "../seatbelt", features = ["retry", "tower-service"] }
serde = { workspace = true, features = ["derive"] }
testing_aids = { path = "../testing_aids" }
tick = { workspace = true, features = ["test-util", "tokio"] }
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
Expand Down Expand Up @@ -143,5 +154,9 @@ required-features = ["memory", "service"]
name = "type_mapping"
required-features = ["memory", "test-util"]

[[example]]
name = "serialization"
required-features = ["memory", "serialize"]

[lints]
workspace = true
29 changes: 28 additions & 1 deletion crates/cachet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ most commonly used types from all of them.
|`metrics`|❌|Enables OpenTelemetry metrics (`cache.event.count`, `cache.operation.duration`, `cache.size`).|
|`logs`|❌|Enables structured `tracing` log events for every cache activity.|
|`service`|❌|Enables `ServiceAdapter`, `CacheServiceExt`, and `CacheOperation`/`CacheResponse` types for service middleware integration.|
|`serialize`|❌|Enables `.serialize()` on builders for automatic postcard serialization of keys and values to `BytesView`.|
|`test-util`|❌|Enables `MockCache`, frozen-clock utilities, and other test helpers.|

## Examples
Expand Down Expand Up @@ -201,6 +202,31 @@ let cache = Cache::builder::<String, String>(clock)
.build();
```

### Serialization Boundary

When a fallback tier operates on serialized bytes (e.g., Redis), use `.serialize()`
to add a postcard serialization boundary. Keys and values are automatically serialized
to [`BytesView`][__link18] before reaching the fallback tier, and
deserialized on the way back.

```rust
use cachet::{Cache, FallbackPromotionPolicy};
use tick::Clock;

let clock = Clock::new_tokio();
let remote = Cache::builder::<bytesbuf::BytesView, bytesbuf::BytesView>(clock.clone()).memory();

let cache = Cache::builder::<String, String>(clock)
.memory()
.serialize()
.fallback(remote)
.promotion_policy(FallbackPromotionPolicy::always())
.build();

// Keys and values are String on the outside, BytesView in the fallback tier.
cache.insert("key".to_string(), "value".to_string()).await?;
```

## Telemetry

Enable with `metrics` and/or `logs` features. Configure via `.enable_metrics()` and `.enable_logs()`.
Expand Down Expand Up @@ -238,7 +264,7 @@ Event name: `cache.event` with fields `cache.name`, `cache.operation`,
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]: ggGkYW0CYXSEGy4k8ldDFPOhG2VNeXtD5nnKG6EPY6OfW5wBG8g18NOFNdxpYXKEG8FPUL0qKt72Gy-GD_CgRPCCG9SaPObQ7vYiG4uWqBhKhhTAYWSGgmZjYWNoZXRlMC4yLjCCbWNhY2hldF9tZW1vcnllMC4xLjCCbmNhY2hldF9zZXJ2aWNlZTAuMS4wgmtjYWNoZXRfdGllcmUwLjEuMIJkdGlja2UwLjMuMIJpdW5pZmxpZ2h0ZTAuMS4w
[__cargo_doc2readme_dependencies_info]: ggGkYW0CYXSEGy4k8ldDFPOhG2VNeXtD5nnKG6EPY6OfW5wBG8g18NOFNdxpYXKEG1DRY_ouWcOzG58PK1HRUDW5G5JDU0oAlprIG37b8vyW3Z6AYWSHgmhieXRlc2J1ZmUwLjQuMoJmY2FjaGV0ZTAuMi4wgm1jYWNoZXRfbWVtb3J5ZTAuMS4wgm5jYWNoZXRfc2VydmljZWUwLjEuMIJrY2FjaGV0X3RpZXJlMC4xLjCCZHRpY2tlMC4zLjCCaXVuaWZsaWdodGUwLjEuMA
[__link0]: https://docs.rs/cachet/0.2.0/cachet/?search=TimeToRefresh
[__link1]: https://crates.io/crates/uniflight/0.1.0
[__link10]: https://docs.rs/cachet_tier/0.1.0/cachet_tier/?search=CacheTier
Expand All @@ -249,6 +275,7 @@ This crate was developed as part of <a href="../..">The Oxidizer Project</a>. Br
[__link15]: https://crates.io/crates/cachet_memory/0.1.0
[__link16]: https://docs.rs/moka
[__link17]: https://crates.io/crates/cachet_service/0.1.0
[__link18]: https://docs.rs/bytesbuf/0.4.2/bytesbuf/?search=BytesView
[__link2]: https://docs.rs/cachet/0.2.0/cachet/?search=CacheBuilder::stampede_protection
[__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
Expand Down
Loading
Loading