moka dependency and makes PortableCache the only in-process cache backend on every target, including wasm32.
Why moka was removed
moka was the single largest contributor to the release binary — 1.8 MiB of .text (15.8%) — almost entirely from per-cache-type monomorphization. Its do_run_pending_tasks alone was emitted 84 times (710 KiB) across the ~15 distinct Cache types the client instantiates; each new typed cache dragged in moka’s full generic machinery (~100 KiB+).
PortableCache was already shipping on wasm32 targets and mirrors the full moka Cache API (capacity + TTL/TTI eviction, single-flight get_with/get_with_by_ref). Making it the sole backend required no call-site changes.
Binary size impact
Real release profile (fat LTO,codegen-units=1, panic=abort, strip):
| Metric | before (moka) | after (PortableCache) | Δ |
|---|---|---|---|
| Stripped size | 13.35 MiB | 10.81 MiB | −2.55 MiB (−19.1%) |
.text | 11.31 MiB | 8.89 MiB | −2.42 MiB (−21.4%) |
| LLVM IR lines | 1,275,789 | 664,220 | −47.9% |
Cargo.lock crates | 357 | 354 | −3 |
PortableCache hardening
Making PortableCache the sole native backend surfaced a few behavioural gaps that were addressed in this PR:- Monotonic TTL/TTI — expiry now uses
wacore::time::Instantinstead of the wall clock, so a system-clock jump can’t expire entries early. This matches moka’s timer semantics and preventssession_recreate_history’s throttle backstop from being bypassed. - Eager single-flight init-lock reclamation —
get_with/get_with_by_refnow drop a key’s init lock once no other caller holds it, instead of waiting forrun_pending_tasks. Fixes unboundedinit_locksgrowth in high-cardinality caches (session locks, chat lanes, message-id dedup) that never callrun_pending_tasks. - Reliable async
clear()— newPortableCache::clear()awaits the write lock.cleanup_connection_stateandTypedCache::clearnow use it instead of the best-effort syncinvalidate_all(), which could skip the clear under contention and leave a staleChatLaneafter reconnect. snapshot_entries()— new async method that awaits the read lock for a reliable snapshot; used bySenderKeyDeviceCache::invalidate_entries_for_devicewhere a missed entry would silently drop an SKDM fanout.
Breaking changes
moka-cache feature removed
The moka-cache Cargo feature no longer exists. Remove it from your Cargo.toml:
TypedCache::from_moka renamed to from_local
If you construct a TypedCache directly in your own code, update the constructor name:
portable_cache module is now always public
whatsapp_rust::portable_cache is no longer cfg-gated. Any conditional compilation on #[cfg(any(not(feature = "moka-cache"), target_arch = "wasm32"))] around imports of that module should be removed.
Trade-offs
PortableCache differs from moka in two ways relevant to very high-throughput deployments:
- Eviction policy: FIFO instead of TinyLFU (lower hit rate under heavily skewed access patterns)
- Concurrency: one
RwLockper cache vs moka’s sharded, lock-free reads (more contention under heavy concurrent access)