Skip to content

feat: pluggable cache store adapter for custom backends - #381

Merged
jlucaso1 merged 2 commits into
mainfrom
feat-typed-cache
Mar 17, 2026
Merged

feat: pluggable cache store adapter for custom backends#381
jlucaso1 merged 2 commits into
mainfrom
feat-typed-cache

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Mar 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds a CacheStore trait in wacore that allows replacing the default in-process moka caches with external backends (Redis, Memcached, SQLite, etc.)
  • TypedCache<K, V> wrapper dispatches to either moka (zero overhead) or a custom CacheStore with serde_json serialization
  • Per-cache granularity via CacheStores struct — users can override only specific caches (e.g., only group_cache and device_cache on Redis) while keeping others in-process
  • Coordination caches (session_locks, message_queues, signal_cache, etc.) always stay in-process since they hold live Rust objects

Usage

// Default — identical to before (all moka, zero change)
CacheConfig::default()

// Selective — only group + device on Redis
CacheConfig {
    cache_stores: CacheStores {
        group_cache: Some(redis.clone()),
        device_cache: Some(redis.clone()),
        ..Default::default()
    },
    ..Default::default()
}

// All pluggable caches on Redis
CacheConfig {
    cache_stores: CacheStores::all(redis),
    ..Default::default()
}

Pluggable caches

Cache Namespace Pluggable
group_cache "group"
device_cache "device"
device_registry_cache "device_registry"
lid_pn_cache "lid_pn_by_lid" / "lid_pn_by_pn"
session_locks ❌ (holds Mutex)
message_queues ❌ (holds channels)
signal_cache ❌ (write-behind flush)

New types

  • wacore::store::CacheStore — async trait for custom backends
  • CacheStores — per-cache optional store overrides
  • TypedCache<K, V> — moka-or-custom dispatcher with Borrow<Q> ergonomics

Serde derives added

  • GroupInfo, LidPnEntry, LearningSourceSerialize + Deserialize
  • AddressingModeDeserialize (already had Serialize)

Test plan

  • All 311 unit tests pass (cargo test -p whatsapp-rust --lib)
  • cargo clippy --all-targets clean
  • Default path (no custom store) is zero-overhead — same moka calls
  • Fully backward compatible: no existing API signatures changed

Summary by CodeRabbit

Release Notes

  • New Features
    • Introduced pluggable cache storage backends supporting custom implementations (Redis, Memcached, etc.)
    • Added per-cache backend customization via new CacheStores configuration for flexible cache management
    • Enhanced serialization support for GroupInfo, device data, and related types

Add a `CacheStore` trait that allows replacing the default in-process
moka caches with external backends like Redis, Memcached, or SQLite
for shared/distributed cache state.

- Define `CacheStore` trait in wacore (get/set/delete/clear/entry_count)
- Add `TypedCache<K, V>` wrapper dispatching to moka or custom store
  with zero overhead on the moka path (no serde, no extra allocs)
- Add `CacheStores` struct with per-cache `Option<Arc<dyn CacheStore>>`
  for granular control (e.g., only group_cache on Redis)
- Add `CacheStores::all(store)` convenience for overriding all at once
- Add Serialize/Deserialize to GroupInfo, LidPnEntry, LearningSource,
  AddressingMode for custom store serialization
- Wire up group_cache, device_cache, device_registry_cache, lid_pn_cache
- Coordination caches (session_locks, message_queues, signal_cache)
  remain in-process — they hold live Rust objects

Fully backward compatible: CacheConfig::default() uses moka everywhere.
@coderabbitai

coderabbitai Bot commented Mar 17, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@jlucaso1 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 25 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fdc79728-149d-47e1-872d-205fd779838c

📥 Commits

Reviewing files that changed from the base of the PR and between a655a9b and ac1b36b.

📒 Files selected for processing (4)
  • src/cache_config.rs
  • src/cache_store.rs
  • src/client.rs
  • src/lid_pn_cache.rs
📝 Walkthrough

Walkthrough

This PR introduces a pluggable cache abstraction layer to replace in-process moka caches with a configurable backend system. It adds a CacheStore trait defining standard cache operations (get, set, delete, clear), a TypedCache wrapper supporting both moka and custom backends, a CacheStores configuration struct for per-cache customization, and updates core caches (group, device, lid_pn, device_registry) to use the new abstraction. Serialization support is added to related types for compatibility.

Changes

Cohort / File(s) Summary
Core Cache Abstraction
wacore/src/store/cache.rs, wacore/src/store/mod.rs
New public async trait CacheStore defining standard operations (get, set, delete, clear, entry_count) with namespace-based partitioning and error handling. Module exports CacheStore for public use.
TypedCache Implementation
src/cache_store.rs
New generic TypedCache<K, V> type with dual backends: moka-backed (in-process, zero-overhead) or custom CacheStore-backed (serialization via serde_json). Methods: get, insert, invalidate, invalidate_all, run_pending_tasks, entry_count with appropriate logging for errors.
Cache Configuration
src/cache_config.rs
Adds CacheStores struct with optional Arc fields for each cache type (group, device, device_registry, lid_pn). Extends CacheConfig with cache_stores field and custom Debug impl to mask store presence. Includes all() constructor for uniform store setup.
Client Cache Integration
src/client.rs
Updates group_cache, device_cache, and device_registry_cache field types from Cache/OnceCell variants to TypedCache equivalents. Adjusts get_group_cache and get_device_cache accessors to return &TypedCache and initialize from either persistent stores or moka caches.
LidPnCache Backend Switch
src/lid_pn_cache.rs
Replaces moka Cache fields (lid_to_entry, pn_to_entry) with TypedCache equivalents. Extends with_config signature to accept optional Arc for backend selection. Updates key access patterns to use owned keys via to_owned().
Serialization Support
wacore/src/client/context.rs, wacore/src/types/lid_pn.rs, wacore/src/types/message.rs
GroupInfo, LearningSource, and LidPnEntry gain serde::Serialize/Deserialize derives (LearningSource uses rename_all="snake_case"). AddressingMode adds Deserialize derive. Enables serialization required by CacheStore path in TypedCache.
Module Exports
src/lib.rs
Adds public re-exports for CacheStores, new public module cache_store, and CacheStore trait. Expands cache_config exports to include CacheStores alongside CacheConfig and CacheEntryConfig.

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant TC as TypedCache
    participant MK as Moka Cache
    participant CS as CacheStore Backend
    
    App->>TC: get(key)
    alt Moka Path
        TC->>MK: get(key)
        MK-->>TC: Option<V>
        TC-->>App: Option<V>
    else Custom Store Path
        TC->>CS: get(namespace, key)
        CS-->>TC: Option<Vec<u8>>
        TC->>TC: deserialize JSON to V
        TC-->>App: Option<V>
    end
    
    App->>TC: insert(key, value)
    alt Moka Path
        TC->>MK: insert(key, value)
    else Custom Store Path
        TC->>TC: serialize V to JSON
        TC->>CS: set(namespace, key, bytes, ttl)
        CS-->>TC: Result<()>
    end
    
    App->>TC: invalidate(key)
    alt Moka Path
        TC->>MK: invalidate(key)
    else Custom Store Path
        TC->>CS: delete(namespace, key)
        CS-->>TC: Result<()>
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Hops of joy through caching layers,
Moka whispers, custom stores declare,
TypedCache bridges both so neat,
Pluggable backends make the feat,
One abstraction, infinitely sweet!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: pluggable cache store adapter for custom backends' accurately and concisely summarizes the main change of introducing a pluggable cache backend mechanism.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-typed-cache
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Mar 17, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchfeat-typed-cache
Testbedubuntu-latest
Click to view all benchmark results
BenchmarkInstructionsBenchmark Result
instructions
(Result Δ%)
Upper Boundary
instructions
(Limit %)
binary_benchmark::attr_parser_group::bench_attr_parser attr_lookup:setup_attr_marshaled()📈 view plot
🚷 view threshold
6,123.00
(-9.25%)Baseline: 6,747.42
7,084.79
(86.42%)
binary_benchmark::child_iteration_group::bench_get_children_by_tag📈 view plot
🚷 view threshold
850,843.00
(+0.01%)Baseline: 850,800.30
893,340.32
(95.24%)
binary_benchmark::jid_optimization_group::bench_jid_to_owned_access jid_access:setup_jid_heavy_marshaled()📈 view plot
🚷 view threshold
20,097.00
(-12.92%)Baseline: 23,078.65
24,232.58
(82.93%)
binary_benchmark::marshal_group::bench_marshal_allocating📈 view plot
🚷 view threshold
119,321.00
(-6.84%)Baseline: 128,084.16
134,488.37
(88.72%)
binary_benchmark::marshal_group::bench_marshal_auto_allocating📈 view plot
🚷 view threshold
119,349.00
(+0.07%)Baseline: 119,263.86
125,227.05
(95.31%)
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating📈 view plot
🚷 view threshold
534,117.00
(+0.01%)Baseline: 534,040.52
560,742.54
(95.25%)
binary_benchmark::marshal_group::bench_marshal_auto_long_string📈 view plot
🚷 view threshold
17,408.00
(+0.27%)Baseline: 17,361.35
18,229.42
(95.49%)
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating📈 view plot
🚷 view threshold
17,210,016.00
(+0.39%)Baseline: 17,142,811.19
17,999,951.75
(95.61%)
binary_benchmark::marshal_group::bench_marshal_exact_allocating📈 view plot
🚷 view threshold
176,790.00
(+0.03%)Baseline: 176,734.22
185,570.93
(95.27%)
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating📈 view plot
🚷 view threshold
535,527.00
(+0.01%)Baseline: 535,453.27
562,225.93
(95.25%)
binary_benchmark::marshal_group::bench_marshal_exact_long_string📈 view plot
🚷 view threshold
19,457.00
(+0.22%)Baseline: 19,414.94
20,385.69
(95.44%)
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating📈 view plot
🚷 view threshold
42,911,885.00
(+0.30%)Baseline: 42,783,675.01
44,922,858.76
(95.52%)
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating📈 view plot
🚷 view threshold
534,556.00
(+0.01%)Baseline: 534,479.52
561,203.49
(95.25%)
binary_benchmark::marshal_group::bench_marshal_long_string📈 view plot
🚷 view threshold
17,381.00
(-3.19%)Baseline: 17,954.57
18,852.30
(92.20%)
binary_benchmark::marshal_group::bench_marshal_many_children_allocating📈 view plot
🚷 view threshold
17,210,857.00
(+0.39%)Baseline: 17,143,657.20
18,000,840.06
(95.61%)
binary_benchmark::marshal_group::bench_marshal_reusing_buffer📈 view plot
🚷 view threshold
129,222.00
(-2.78%)Baseline: 132,917.88
139,563.77
(92.59%)
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer📈 view plot
🚷 view threshold
119,421.00
(+0.07%)Baseline: 119,335.86
125,302.65
(95.31%)
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled()📈 view plot
🚷 view threshold
94,461.00
(-4.64%)Baseline: 99,061.38
104,014.45
(90.82%)
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled()📈 view plot
🚷 view threshold
7,378.00
(-5.37%)Baseline: 7,796.90
8,186.75
(90.12%)
binary_benchmark::roundtrip_group::bench_roundtrip_auto large:setup_large_marshaled()📈 view plot
🚷 view threshold
94,492.00
(+0.37%)Baseline: 94,147.75
98,855.14
(95.59%)
binary_benchmark::roundtrip_group::bench_roundtrip_auto small:setup_small_marshaled()📈 view plot
🚷 view threshold
7,401.00
(+0.72%)Baseline: 7,347.88
7,715.28
(95.93%)
binary_benchmark::roundtrip_group::bench_roundtrip_exact large:setup_large_marshaled()📈 view plot
🚷 view threshold
110,277.00
(+0.31%)Baseline: 109,932.75
115,429.39
(95.54%)
binary_benchmark::roundtrip_group::bench_roundtrip_exact small:setup_small_marshaled()📈 view plot
🚷 view threshold
8,913.00
(+0.60%)Baseline: 8,859.88
9,302.88
(95.81%)
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled()📈 view plot
🚷 view threshold
45,476.00
(-4.02%)Baseline: 47,382.23
49,751.34
(91.41%)
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled()📈 view plot
🚷 view threshold
2,717.00
(-6.55%)Baseline: 2,907.59
3,052.97
(89.00%)
binary_benchmark::unpack_group::bench_unpack_compressed📈 view plot
🚷 view threshold
556,092.00
(+3.81%)Baseline: 535,687.46
562,471.83
(98.87%)
binary_benchmark::unpack_group::bench_unpack_uncompressed📈 view plot
🚷 view threshold
771.00
(-0.46%)Baseline: 774.55
813.28
(94.80%)
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data()📈 view plot
🚷 view threshold
27,685,543.00
(-0.12%)Baseline: 27,718,318.04
29,104,233.94
(95.13%)
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message()📈 view plot
🚷 view threshold
5,540,322.00
(-0.18%)Baseline: 5,550,304.90
5,827,820.14
(95.07%)
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session()📈 view plot
🚷 view threshold
178,094.00
(+0.03%)Baseline: 178,040.90
186,942.95
(95.27%)
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session()📈 view plot
🚷 view threshold
178,905.00
(+0.03%)Baseline: 178,852.25
187,794.86
(95.27%)
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users()📈 view plot
🚷 view threshold
17,353,314.00
(+0.40%)Baseline: 17,284,358.24
18,148,576.15
(95.62%)
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender()📈 view plot
🚷 view threshold
295,884.00
(+0.01%)Baseline: 295,845.30
310,637.56
(95.25%)
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message()📈 view plot
🚷 view threshold
12,621,353.00
(+0.18%)Baseline: 12,598,811.68
13,228,752.27
(95.41%)
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution()📈 view plot
🚷 view threshold
715,555.00
(-0.02%)Baseline: 715,711.30
751,496.87
(95.22%)
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions()📈 view plot
🚷 view threshold
41,823.00
(+0.03%)Baseline: 41,811.18
43,901.74
(95.27%)
libsignal_benchmark::session_optimization_group::bench_message_key_eviction eviction:setup_message_key_eviction()📈 view plot
🚷 view threshold
15,561,842.00
(+0.00%)Baseline: 15,561,705.93
16,339,791.23
(95.24%)
libsignal_benchmark::session_optimization_group::bench_out_of_order_decryption out_of_order:setup_out_of_order_messages()📈 view plot
🚷 view threshold
5,504,859.00
(-0.12%)Baseline: 5,511,347.83
5,786,915.22
(95.13%)
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session()📈 view plot
🚷 view threshold
956,774.00
(-0.23%)Baseline: 958,998.51
1,006,948.43
(95.02%)
libsignal_benchmark::signature_group::bench_key_generation keygen📈 view plot
🚷 view threshold
2,822,723.00
(-0.01%)Baseline: 2,822,884.24
2,964,028.46
(95.23%)
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message()📈 view plot
🚷 view threshold
3,444,364.00
(-1.16%)Baseline: 3,484,902.58
3,659,147.71
(94.13%)
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message()📈 view plot
🚷 view threshold
124,657,015.00
(-0.60%)Baseline: 125,404,027.11
131,674,228.47
(94.67%)
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message()📈 view plot
🚷 view threshold
11,812.00
(+0.12%)Baseline: 11,797.36
12,387.23
(95.36%)
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message()📈 view plot
🚷 view threshold
3,825.00
(+0.05%)Baseline: 3,823.27
4,014.44
(95.28%)
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended()📈 view plot
🚷 view threshold
87,720.00
(-0.27%)Baseline: 87,961.20
92,359.26
(94.98%)
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple()📈 view plot
🚷 view threshold
79,754.00
(-0.32%)Baseline: 80,010.03
84,010.53
(94.93%)
reporting_token_benchmark::key_derivation_group::bench_key_derivation📈 view plot
🚷 view threshold
51,011.00
(-0.02%)Baseline: 51,021.26
53,572.32
(95.22%)
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message()📈 view plot
🚷 view threshold
5,762.00
(+0.27%)Baseline: 5,746.20
6,033.51
(95.50%)
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message()📈 view plot
🚷 view threshold
2,122.00
(+0.19%)Baseline: 2,117.89
2,223.79
(95.42%)
reporting_token_benchmark::token_calculation_group::bench_token_calculation📈 view plot
🚷 view threshold
21,920.00
(+0.02%)Baseline: 21,915.51
23,011.28
(95.26%)
🐰 View full continuous benchmarking report in Bencher

@jlucaso1

Copy link
Copy Markdown
Collaborator Author

@claude review

@claude

claude Bot commented Mar 17, 2026

Copy link
Copy Markdown

Claude finished @jlucaso1's task in 1m 48s —— View job


PR Review: Pluggable Cache Store Adapter

  • Read all changed files
  • Analyze architecture and design
  • Check for bugs, security issues, and performance concerns
  • Post review feedback

Overall this is a clean, well-structured PR. The TypedCache abstraction is nicely designed with zero overhead on the moka path. The trait definition in wacore with the implementation in the main crate is a good layering choice. Here are my findings:

Issues

1. TOCTOU race in LidPnCache::add (medium)
src/lid_pn_cache.rs:117-135 — The add method does a get + conditional insert on the PN map without atomicity. With a custom backend (e.g., Redis), two concurrent add() calls for the same phone number could race: both read the old value, both decide to update, and the older entry wins if it writes last. This is an existing concern that gets worse with external backends since moka has eventual consistency guarantees that help mask this.

For now this is probably acceptable since the PR description notes it's a cache (best-effort), but worth documenting with a comment. Fix this →

2. entry_count() is sync but CacheStore::entry_count() is async (low)
src/cache_store.rs:195-199TypedCache::entry_count() is a sync method that returns 0 for custom backends. The trait has an async fn entry_count() that's never called. This means diagnostics (MemoryDiagnostics at src/client.rs:1060-1062) will always report 0 for custom-backed caches. Consider either:

  • Making TypedCache::entry_count() async and calling the trait method, or
  • Removing CacheStore::entry_count() from the trait since it's unused

The current approach is functional but confusing — the trait method exists but is dead code.

3. LidPnCache::clear calls fire-and-forget invalidate_all (low)
src/lid_pn_cache.rs:158-161clear() is async fn but calls invalidate_all() which is sync and spawns fire-and-forget tasks for custom backends. The caller might expect the clear to be complete when the future resolves. Consider making this consistent — either make clear non-async, or await the actual clear operation.

Suggestions

4. Repeated match pattern for store initialization
src/client.rs:625-631, 657-664, 670-677 — The match cache_config.cache_stores.X.clone() { Some(store) => ..., None => ... } pattern is repeated 3 times. Consider a helper on CacheEntryConfig:

impl CacheEntryConfig {
    pub(crate) fn build_typed_ttl<K, V>(
        &self,
        store: Option<Arc<dyn CacheStore>>,
        namespace: &'static str,
    ) -> TypedCache<K, V>
    where
        K: Hash + Eq + Display + Send + Sync + 'static,
        V: Clone + Serialize + DeserializeOwned + Send + Sync + 'static,
    {
        match store {
            Some(s) => TypedCache::from_store(s, namespace, self.timeout),
            None => TypedCache::from_moka(self.build_with_ttl()),
        }
    }
}

This would reduce the 3 match blocks to one-liners. Fix this →

5. Unnecessary .to_owned() allocations in LidPnCache
src/lid_pn_cache.rs:87,98,104,109 — Methods like get_current_lid call .get(&phone.to_owned()) which allocates a String just to pass to get(). Since TypedCache::get accepts &Q where K: Borrow<Q> and String: Borrow<str>, you should be able to pass &str directly:

pub async fn get_current_lid(&self, phone: &str) -> Option<String> {
    self.pn_to_entry.get(phone).await.map(|e| e.lid.clone())
}

This avoids an allocation per lookup. Fix this →

Looks Good

  • Clean separation: CacheStore trait in wacore (no Tokio dep), TypedCache in the main crate
  • Zero-overhead moka path — no serde in the hot path when using defaults
  • Good error handling: all custom store errors are logged and treated as cache misses
  • PhantomData<fn(K, V)> for variance correctness
  • Coordination caches correctly excluded from pluggability
  • Serde derives on GroupInfo, LidPnEntry, LearningSource, AddressingMode are appropriate
  • The Debug impl for CacheConfig correctly shows is_some() for store fields
  • Backward compatible — default path is unchanged

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/cache_store.rs (1)

195-199: Custom backend entry counts are currently discarded.

TypedCache::entry_count() always returns 0 for Inner::Custom, even when CacheStore::entry_count is implemented. This drops useful diagnostics.

Suggested API addition (non-breaking)
+    /// Entry count for both backends (async for custom stores).
+    pub async fn entry_count_async(&self) -> u64 {
+        match &self.inner {
+            Inner::Moka(cache) => cache.entry_count(),
+            Inner::Custom { store, namespace, .. } => {
+                match store.entry_count(namespace).await {
+                    Ok(count) => count,
+                    Err(e) => {
+                        log::warn!("TypedCache[{namespace}]: entry_count() error: {e}");
+                        0
+                    }
+                }
+            }
+        }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cache_store.rs` around lines 195 - 199, TypedCache::entry_count currently
discards counts for Inner::Custom by returning 0; change the Inner::Custom match
arm to delegate to the custom backend's entry_count implementation instead of
hardcoding 0. Locate the enum variant used in self.inner (Inner::Custom { ... })
and extract the stored custom cache/backend instance, then call its
CacheStore::entry_count (or equivalent method on the custom backend) and return
that value; keep a 0 fallback only if the custom backend truly lacks an
entry_count method.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cache_store.rs`:
- Around line 165-177: invalidate_all() currently unconditionally calls
tokio::spawn from a synchronous, public method which can panic if no Tokio
runtime exists; change the Inner::Custom branch to attempt to get a current
runtime with tokio::runtime::Handle::try_current() and, if successful, use
tokio::spawn(async move { if let Err(e) = store.clear(ns).await {
log::warn!(...) } }), but if try_current() fails, fall back to spawning a
standard thread (std::thread::spawn) and create a small single-threaded Tokio
runtime inside it
(tokio::runtime::Builder::new_current_thread().enable_all().build()) to
block_on(store.clear(ns)) and log errors similarly; update references to
invalidate_all, Inner::Custom, store.clear, and tokio::spawn in the diff
accordingly.

---

Nitpick comments:
In `@src/cache_store.rs`:
- Around line 195-199: TypedCache::entry_count currently discards counts for
Inner::Custom by returning 0; change the Inner::Custom match arm to delegate to
the custom backend's entry_count implementation instead of hardcoding 0. Locate
the enum variant used in self.inner (Inner::Custom { ... }) and extract the
stored custom cache/backend instance, then call its CacheStore::entry_count (or
equivalent method on the custom backend) and return that value; keep a 0
fallback only if the custom backend truly lacks an entry_count method.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 26c56938-65c8-4214-a807-dd3c20edc4f7

📥 Commits

Reviewing files that changed from the base of the PR and between 62fa5dd and a655a9b.

📒 Files selected for processing (10)
  • src/cache_config.rs
  • src/cache_store.rs
  • src/client.rs
  • src/lib.rs
  • src/lid_pn_cache.rs
  • wacore/src/client/context.rs
  • wacore/src/store/cache.rs
  • wacore/src/store/mod.rs
  • wacore/src/types/lid_pn.rs
  • wacore/src/types/message.rs

Comment thread src/cache_store.rs Outdated
- invalidate_all: use Handle::try_current() instead of tokio::spawn
  to avoid panic outside Tokio runtime (CodeRabbit)
- Add TypedCache::clear() async method and entry_count_async() that
  delegates to CacheStore::entry_count (CodeRabbit + Claude)
- LidPnCache::clear now awaits the actual clear instead of
  fire-and-forget invalidate_all (Claude)
- Remove unnecessary .to_owned() allocations in LidPnCache lookups;
  pass &str directly via Borrow<Q> bounds (Claude)
- Add build_typed_ttl helper on CacheEntryConfig to deduplicate the
  match store { Some => from_store, None => from_moka } pattern
  repeated in client.rs (Claude)
- Document TOCTOU race in LidPnCache::add for external backends (Claude)
@jlucaso1
jlucaso1 merged commit 864c909 into main Mar 17, 2026
8 checks passed
@jlucaso1
jlucaso1 deleted the feat-typed-cache branch March 17, 2026 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant