#![warn(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
#![warn(missing_docs)]
#[cfg(all(feature = "workers", feature = "redis"))]
compile_error!(
"features `workers` and `redis` are mutually exclusive — Workers runtime cannot use fred"
);
#[cfg(all(feature = "workers", feature = "l1"))]
compile_error!("features `workers` and `l1` are mutually exclusive — L1 reads `std::time::Instant`, which has no clock on wasm32-unknown-unknown, the Workers target");
#[cfg(all(feature = "workers", feature = "reliability"))]
compile_error!("features `workers` and `reliability` are mutually exclusive — retry/breaker timers need tokio `time`, unavailable in wasm32");
#[cfg(all(feature = "workers", feature = "memcached"))]
compile_error!("features `workers` and `memcached` are mutually exclusive — Workers runtime has no TCP sockets");
#[cfg(all(feature = "workers", feature = "file"))]
compile_error!(
"features `workers` and `file` are mutually exclusive — Workers runtime has no filesystem"
);
#[cfg(all(feature = "l1", target_arch = "wasm32", target_os = "unknown"))]
compile_error!("feature `l1` is not supported on wasm32-unknown-unknown — std::time::Instant has no clock there, so the client's first L1 read or write would panic; set `default-features = false, features = [\"encryption\"]` plus your backend feature, leaving out `l1`");
pub mod backend;
pub mod client;
pub mod config;
pub mod error;
pub mod flight;
pub mod interop;
pub mod metrics;
pub mod serializer;
pub mod session;
pub mod url_validator;
mod intents;
#[cfg(feature = "encryption")]
pub mod encryption;
#[cfg(feature = "l1")]
pub mod l1;
#[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
pub mod reliability;
pub use client::{CacheKit, CacheKitBuilder, SharedBackend, SwrRead, SwrToken};
pub use config::CachekitConfig;
pub use error::{BackendError, BackendErrorKind, CachekitError};
pub use metrics::L1Stats;
#[cfg(feature = "encryption")]
pub use client::SecureCache;
#[cfg(feature = "encryption")]
pub use encryption::EncryptionLayer;
#[cfg(feature = "macros")]
pub use cachekit_macros::cachekit;
pub use flight::SingleFlight;
#[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
pub use reliability::{
BackpressureConfig, CircuitBreakerConfig, CircuitState, ReliabilityConfig, RetryConfig,
};
#[cfg(all(
any(feature = "l1", feature = "reliability"),
not(target_arch = "wasm32")
))]
pub(crate) fn random_unit() -> f64 {
thread_local! {
static STATE: JitterState = const { std::cell::Cell::new(None) };
}
let z = STATE.with(|state| next_jitter(state, std::process::id()));
((z >> 11) as f64) / ((1u64 << 53) as f64)
}
#[cfg(all(feature = "l1", target_arch = "wasm32"))]
pub(crate) fn random_unit() -> f64 {
let bits = uuid::Uuid::new_v4().as_u128() & ((1u128 << 53) - 1);
(bits as f64) / ((1u64 << 53) as f64)
}
#[cfg(all(
any(feature = "l1", feature = "reliability"),
not(target_arch = "wasm32")
))]
type JitterState = std::cell::Cell<Option<(u32, u64)>>;
#[cfg(all(
any(feature = "l1", feature = "reliability"),
not(target_arch = "wasm32")
))]
fn next_jitter(state: &JitterState, pid: u32) -> u64 {
use std::hash::{BuildHasher, Hasher};
let current = match state.get() {
Some((owner, current)) if owner == pid => current,
_ => {
let mut hasher = std::collections::hash_map::RandomState::new().build_hasher();
hasher.write_u32(pid);
hasher.finish()
}
};
let next = current.wrapping_add(0x9E37_79B9_7F4A_7C15);
state.set(Some((pid, next)));
let mut z = next;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
#[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
#[doc(hidden)]
pub fn __swr_spawn<F>(fut: F)
where
F: std::future::Future<Output = ()> + Send + 'static,
{
if let Ok(handle) = tokio::runtime::Handle::try_current() {
drop(handle.spawn(fut));
}
}
#[cfg(not(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32"))))]
#[doc(hidden)]
pub fn __swr_spawn<F>(_fut: F)
where
F: std::future::Future<Output = ()> + 'static,
{
}
pub mod prelude {
pub use crate::{
BackendError, BackendErrorKind, CacheKit, CacheKitBuilder, CachekitConfig, CachekitError,
SwrRead, SwrToken,
};
#[cfg(feature = "encryption")]
pub use crate::{EncryptionLayer, SecureCache};
#[cfg(feature = "macros")]
pub use crate::cachekit;
}
#[cfg(all(
test,
any(feature = "l1", feature = "reliability"),
not(target_arch = "wasm32")
))]
mod random_unit_tests {
use super::{next_jitter, random_unit, JitterState};
#[test]
fn a_new_pid_reseeds_instead_of_replaying_the_parent() {
let parent = JitterState::new(None);
next_jitter(&parent, 100);
let child_a = JitterState::new(parent.get());
let child_b = JitterState::new(parent.get());
let from_parent = next_jitter(&parent, 100);
let from_a = next_jitter(&child_a, 101);
let from_b = next_jitter(&child_b, 102);
assert_ne!(from_parent, from_a);
assert_ne!(from_parent, from_b);
assert_ne!(from_a, from_b);
let replay = JitterState::new(child_a.get());
assert_eq!(next_jitter(&replay, 101), next_jitter(&child_a, 101));
}
#[test]
fn stays_in_unit_interval_and_spreads() {
let draws: Vec<f64> = (0..10_000).map(|_| random_unit()).collect();
assert!(draws.iter().all(|x| (0.0..1.0).contains(x)));
let mean = draws.iter().sum::<f64>() / draws.len() as f64;
assert!((mean - 0.5).abs() < 0.02, "mean {mean}");
let low = draws.iter().filter(|x| **x < 0.1).count();
let high = draws.iter().filter(|x| **x >= 0.9).count();
assert!(low > 800 && high > 800, "tails {low} / {high}");
}
#[test]
#[allow(clippy::expect_used)]
fn threads_draw_different_sequences() {
let draw = || (0..4).map(|_| random_unit().to_bits()).collect::<Vec<_>>();
let a = std::thread::spawn(draw).join().expect("thread a");
let b = std::thread::spawn(draw).join().expect("thread b");
assert_ne!(a, b);
}
}