use std::collections::{HashMap, VecDeque};
use std::hash::Hash;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{LazyLock, OnceLock, RwLock};
use crate::exact::SignedSqrtRational;
use crate::su2::{FKey, Regge3j, Regge6j};
const DEFAULT_MAX_ENTRIES: usize = 1 << 20;
const DEFAULT_MAX_BYTES: usize = 64 << 20;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CoefficientCacheBudgets {
three_j_bytes: usize,
six_j_bytes: usize,
derived_f_bytes: usize,
#[cfg(feature = "cgc-gen")]
sun_product_bytes: usize,
#[cfg(feature = "cgc-gen")]
sun_cgc_bytes: usize,
#[cfg(feature = "cgc-gen")]
sun_f_bytes: usize,
#[cfg(feature = "cgc-gen")]
bcd_cgc_bytes: usize,
#[cfg(feature = "cgc-gen")]
bcd_f_bytes: usize,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CoefficientCacheTier {
ThreeJ,
SixJ,
DerivedF,
#[cfg(feature = "cgc-gen")]
SunProduct,
#[cfg(feature = "cgc-gen")]
SunCgc,
#[cfg(feature = "cgc-gen")]
SunF,
#[cfg(feature = "cgc-gen")]
BcdCgc,
#[cfg(feature = "cgc-gen")]
BcdF,
}
impl std::fmt::Display for CoefficientCacheTier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::ThreeJ => "three_j",
Self::SixJ => "six_j",
Self::DerivedF => "derived_f",
#[cfg(feature = "cgc-gen")]
Self::SunProduct => "sun_product",
#[cfg(feature = "cgc-gen")]
Self::SunCgc => "sun_cgc",
#[cfg(feature = "cgc-gen")]
Self::SunF => "sun_f",
#[cfg(feature = "cgc-gen")]
Self::BcdCgc => "bcd_cgc",
#[cfg(feature = "cgc-gen")]
Self::BcdF => "bcd_f",
})
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CacheTrimReport {
pub tier: CoefficientCacheTier,
pub removed_entries: usize,
pub removed_charged_bytes: usize,
pub remaining_entries: usize,
pub remaining_charged_bytes: usize,
}
impl CoefficientCacheBudgets {
pub fn disabled() -> Self {
let mut budgets = Self::default();
for tier in [
CoefficientCacheTier::ThreeJ,
CoefficientCacheTier::SixJ,
CoefficientCacheTier::DerivedF,
] {
budgets = budgets.with_limit(tier, 0);
}
#[cfg(feature = "cgc-gen")]
for tier in [
CoefficientCacheTier::SunProduct,
CoefficientCacheTier::SunCgc,
CoefficientCacheTier::SunF,
CoefficientCacheTier::BcdCgc,
CoefficientCacheTier::BcdF,
] {
budgets = budgets.with_limit(tier, 0);
}
budgets
}
pub fn with_limit(mut self, tier: CoefficientCacheTier, bytes: usize) -> Self {
match tier {
CoefficientCacheTier::ThreeJ => self.three_j_bytes = bytes,
CoefficientCacheTier::SixJ => self.six_j_bytes = bytes,
CoefficientCacheTier::DerivedF => self.derived_f_bytes = bytes,
#[cfg(feature = "cgc-gen")]
CoefficientCacheTier::SunProduct => self.sun_product_bytes = bytes,
#[cfg(feature = "cgc-gen")]
CoefficientCacheTier::SunCgc => self.sun_cgc_bytes = bytes,
#[cfg(feature = "cgc-gen")]
CoefficientCacheTier::SunF => self.sun_f_bytes = bytes,
#[cfg(feature = "cgc-gen")]
CoefficientCacheTier::BcdCgc => self.bcd_cgc_bytes = bytes,
#[cfg(feature = "cgc-gen")]
CoefficientCacheTier::BcdF => self.bcd_f_bytes = bytes,
}
self
}
pub fn limit(&self, tier: CoefficientCacheTier) -> usize {
match tier {
CoefficientCacheTier::ThreeJ => self.three_j_bytes,
CoefficientCacheTier::SixJ => self.six_j_bytes,
CoefficientCacheTier::DerivedF => self.derived_f_bytes,
#[cfg(feature = "cgc-gen")]
CoefficientCacheTier::SunProduct => self.sun_product_bytes,
#[cfg(feature = "cgc-gen")]
CoefficientCacheTier::SunCgc => self.sun_cgc_bytes,
#[cfg(feature = "cgc-gen")]
CoefficientCacheTier::SunF => self.sun_f_bytes,
#[cfg(feature = "cgc-gen")]
CoefficientCacheTier::BcdCgc => self.bcd_cgc_bytes,
#[cfg(feature = "cgc-gen")]
CoefficientCacheTier::BcdF => self.bcd_f_bytes,
}
}
}
impl Default for CoefficientCacheBudgets {
fn default() -> Self {
Self {
three_j_bytes: DEFAULT_MAX_BYTES,
six_j_bytes: DEFAULT_MAX_BYTES,
derived_f_bytes: DEFAULT_MAX_BYTES,
#[cfg(feature = "cgc-gen")]
sun_product_bytes: sun_product_cache::SUN_PRODUCT_MAX_BYTES,
#[cfg(feature = "cgc-gen")]
sun_cgc_bytes: cgc_cache::CGC_MAX_BYTES,
#[cfg(feature = "cgc-gen")]
sun_f_bytes: sun_f_cache::SUN_F_MAX_BYTES,
#[cfg(feature = "cgc-gen")]
bcd_cgc_bytes: bcd_cgc_cache::BCD_CGC_MAX_BYTES,
#[cfg(feature = "cgc-gen")]
bcd_f_bytes: bcd_f_cache::BCD_F_MAX_BYTES,
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CacheBudgetError {
AlreadyInitialized,
ExceedsMaximum {
tier: CoefficientCacheTier,
requested: usize,
maximum: usize,
},
AggregateOverflow,
}
impl std::fmt::Display for CacheBudgetError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AlreadyInitialized => {
f.write_str("coefficient-cache policy is already initialized")
}
Self::ExceedsMaximum {
tier,
requested,
maximum,
} => write!(
f,
"{tier} budget {requested} exceeds compiled maximum {maximum}"
),
Self::AggregateOverflow => {
f.write_str("coefficient-cache budget aggregate overflowed usize")
}
}
}
}
impl std::error::Error for CacheBudgetError {}
static CACHE_BUDGETS: OnceLock<CoefficientCacheBudgets> = OnceLock::new();
fn effective_budgets() -> &'static CoefficientCacheBudgets {
effective_budgets_in(&CACHE_BUDGETS)
}
fn effective_budgets_in(cell: &OnceLock<CoefficientCacheBudgets>) -> &CoefficientCacheBudgets {
cell.get_or_init(CoefficientCacheBudgets::default)
}
pub fn configure_cache_budgets(budgets: CoefficientCacheBudgets) -> Result<(), CacheBudgetError> {
validate_budgets(budgets)?;
configure_budgets_in(&CACHE_BUDGETS, budgets)
}
fn configure_budgets_in(
cell: &OnceLock<CoefficientCacheBudgets>,
budgets: CoefficientCacheBudgets,
) -> Result<(), CacheBudgetError> {
cell.set(budgets)
.map_err(|_| CacheBudgetError::AlreadyInitialized)
}
pub fn cache_budgets() -> CoefficientCacheBudgets {
*effective_budgets()
}
fn validate_budgets(b: CoefficientCacheBudgets) -> Result<(), CacheBudgetError> {
let maximum = CoefficientCacheBudgets::default();
let fields = [
(
CoefficientCacheTier::ThreeJ,
b.three_j_bytes,
maximum.three_j_bytes,
),
(
CoefficientCacheTier::SixJ,
b.six_j_bytes,
maximum.six_j_bytes,
),
(
CoefficientCacheTier::DerivedF,
b.derived_f_bytes,
maximum.derived_f_bytes,
),
#[cfg(feature = "cgc-gen")]
(
CoefficientCacheTier::SunProduct,
b.sun_product_bytes,
maximum.sun_product_bytes,
),
#[cfg(feature = "cgc-gen")]
(
CoefficientCacheTier::SunCgc,
b.sun_cgc_bytes,
maximum.sun_cgc_bytes,
),
#[cfg(feature = "cgc-gen")]
(
CoefficientCacheTier::SunF,
b.sun_f_bytes,
maximum.sun_f_bytes,
),
#[cfg(feature = "cgc-gen")]
(
CoefficientCacheTier::BcdCgc,
b.bcd_cgc_bytes,
maximum.bcd_cgc_bytes,
),
#[cfg(feature = "cgc-gen")]
(
CoefficientCacheTier::BcdF,
b.bcd_f_bytes,
maximum.bcd_f_bytes,
),
];
let mut total = 0usize;
for (tier, requested, limit) in fields {
if requested > limit {
return Err(CacheBudgetError::ExceedsMaximum {
tier,
requested,
maximum: limit,
});
}
total = total
.checked_add(requested)
.ok_or(CacheBudgetError::AggregateOverflow)?;
}
Ok(())
}
pub const BASE_CACHE_MAX_BYTES: usize = 192 << 20;
const _: () = assert!(BASE_CACHE_MAX_BYTES == 3 * DEFAULT_MAX_BYTES);
#[cfg(feature = "cgc-gen")]
pub const GENERATED_CACHE_MAX_BYTES: usize = (640 << 20) + (128 << 10);
#[cfg(feature = "cgc-gen")]
const _: () = assert!(
GENERATED_CACHE_MAX_BYTES
== cgc_cache::CGC_MAX_BYTES
+ sun_product_cache::SUN_PRODUCT_MAX_BYTES
+ sun_f_cache::SUN_F_MAX_BYTES
+ bcd_cgc_cache::BCD_CGC_MAX_BYTES
+ bcd_f_cache::BCD_F_MAX_BYTES
);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct CacheStats {
pub hits: u64,
pub misses: u64,
pub entries: usize,
pub bytes: usize,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct TierStats {
pub entries: usize,
pub bytes: usize,
pub hits: u64,
pub misses: u64,
pub evictions: u64,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct BaseCacheStats {
pub three_j: TierStats,
pub six_j: TierStats,
pub derived_f: TierStats,
}
impl BaseCacheStats {
pub fn total(&self) -> TierStats {
TierStats {
entries: self.three_j.entries + self.six_j.entries + self.derived_f.entries,
bytes: self.three_j.bytes + self.six_j.bytes + self.derived_f.bytes,
hits: self.three_j.hits + self.six_j.hits + self.derived_f.hits,
misses: self.three_j.misses + self.six_j.misses + self.derived_f.misses,
evictions: self.three_j.evictions + self.six_j.evictions + self.derived_f.evictions,
}
}
}
#[cfg(feature = "cgc-gen")]
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct GeneratedCacheStats {
pub sun_product: TierStats,
pub sun_cgc: TierStats,
pub sun_f: TierStats,
pub bcd_cgc: TierStats,
pub bcd_f: TierStats,
}
#[cfg(feature = "cgc-gen")]
impl GeneratedCacheStats {
pub fn total(&self) -> TierStats {
TierStats {
entries: self.sun_product.entries
+ self.sun_cgc.entries
+ self.sun_f.entries
+ self.bcd_cgc.entries
+ self.bcd_f.entries,
bytes: self.sun_product.bytes
+ self.sun_cgc.bytes
+ self.sun_f.bytes
+ self.bcd_cgc.bytes
+ self.bcd_f.bytes,
hits: self.sun_product.hits
+ self.sun_cgc.hits
+ self.sun_f.hits
+ self.bcd_cgc.hits
+ self.bcd_f.hits,
misses: self.sun_product.misses
+ self.sun_cgc.misses
+ self.sun_f.misses
+ self.bcd_cgc.misses
+ self.bcd_f.misses,
evictions: self.sun_product.evictions
+ self.sun_cgc.evictions
+ self.sun_f.evictions
+ self.bcd_cgc.evictions
+ self.bcd_f.evictions,
}
}
}
pub(crate) trait CacheCharge {
fn value_bytes(&self) -> usize;
}
pub(crate) trait CacheKeyCharge {
fn key_bytes(&self) -> usize;
}
macro_rules! fixed_key_charge {
($($ty:ty),+ $(,)?) => {
$(
impl CacheKeyCharge for $ty {
fn key_bytes(&self) -> usize {
std::mem::size_of::<Self>()
}
}
)+
};
}
fixed_key_charge!(u32, Regge3j, Regge6j, FKey);
#[cfg(feature = "cgc-gen")]
impl<I: CacheKeyCharge> CacheKeyCharge for (I, I, I) {
fn key_bytes(&self) -> usize {
self.0
.key_bytes()
.saturating_add(self.1.key_bytes())
.saturating_add(self.2.key_bytes())
}
}
#[cfg(feature = "cgc-gen")]
impl<I: CacheKeyCharge> CacheKeyCharge for (I, I, I, I, I, I) {
fn key_bytes(&self) -> usize {
self.0
.key_bytes()
.saturating_add(self.1.key_bytes())
.saturating_add(self.2.key_bytes())
.saturating_add(self.3.key_bytes())
.saturating_add(self.4.key_bytes())
.saturating_add(self.5.key_bytes())
}
}
impl CacheCharge for SignedSqrtRational {
fn value_bytes(&self) -> usize {
let r = self.radicand();
let value_limbs = (r.numer().bits() + r.denom().bits()).div_ceil(8) as usize;
const BIGINT_OVERHEAD: usize = 32;
std::mem::size_of::<SignedSqrtRational>() + 2 * BIGINT_OVERHEAD + value_limbs
}
}
impl CacheCharge for f64 {
fn value_bytes(&self) -> usize {
std::mem::size_of::<f64>()
}
}
fn entry_charge<K: CacheKeyCharge, V: CacheCharge>(key: &K, value: &V) -> usize {
value
.value_bytes()
.saturating_add(2usize.saturating_mul(key.key_bytes()))
}
struct Inner<K, V> {
map: HashMap<K, V>,
order: VecDeque<K>,
bytes: usize,
}
pub(crate) struct FifoCache<K, V> {
inner: RwLock<Inner<K, V>>,
hits: AtomicU64,
misses: AtomicU64,
evictions: AtomicU64,
max_entries: usize,
max_bytes: usize,
}
impl<K: Clone + Eq + Hash + CacheKeyCharge, V: Clone + CacheCharge> FifoCache<K, V> {
fn new(max_entries: usize, max_bytes: usize) -> Self {
FifoCache {
inner: RwLock::new(Inner {
map: HashMap::new(),
order: VecDeque::new(),
bytes: 0,
}),
hits: AtomicU64::new(0),
misses: AtomicU64::new(0),
evictions: AtomicU64::new(0),
max_entries,
max_bytes,
}
}
pub(crate) fn get_or_compute(&self, key: K, compute: impl FnOnce() -> V) -> V {
if let Some(v) = self.inner.read().unwrap().map.get(&key) {
self.hits.fetch_add(1, Ordering::Relaxed);
return v.clone();
}
let value = compute();
self.misses.fetch_add(1, Ordering::Relaxed);
let mut inner = self.inner.write().unwrap();
if let Some(v) = inner.map.get(&key) {
return v.clone();
}
let charge = entry_charge(&key, &value);
if !self.make_room(&mut inner, charge) {
return value;
}
inner.bytes = inner
.bytes
.checked_add(charge)
.expect("bounded cache charge");
inner.order.push_back(key.clone());
inner.map.insert(key, value.clone());
value
}
fn make_room(&self, inner: &mut Inner<K, V>, charge: usize) -> bool {
if self.max_entries == 0 || charge > self.max_bytes {
self.evictions.fetch_add(1, Ordering::Relaxed);
return false;
}
let bytes_before = self.max_bytes - charge;
while inner.map.len() >= self.max_entries || inner.bytes > bytes_before {
let Some(old) = inner.order.pop_front() else {
break;
};
if let Some(value) = inner.map.remove(&old) {
inner.bytes = inner
.bytes
.checked_sub(entry_charge(&old, &value))
.expect("cache charge accounting invariant");
self.evictions.fetch_add(1, Ordering::Relaxed);
}
}
debug_assert!(inner.bytes <= bytes_before);
true
}
#[cfg(feature = "cgc-gen")]
pub(crate) fn get(&self, key: &K) -> Option<V> {
let v = self.inner.read().unwrap().map.get(key).cloned();
if v.is_some() {
self.hits.fetch_add(1, Ordering::Relaxed);
}
v
}
#[cfg(feature = "cgc-gen")]
pub(crate) fn insert(&self, key: K, value: V) -> V {
self.misses.fetch_add(1, Ordering::Relaxed);
let mut inner = self.inner.write().unwrap();
if let Some(v) = inner.map.get(&key) {
return v.clone();
}
let charge = entry_charge(&key, &value);
if !self.make_room(&mut inner, charge) {
return value;
}
inner.bytes = inner
.bytes
.checked_add(charge)
.expect("bounded cache charge");
inner.order.push_back(key.clone());
inner.map.insert(key, value.clone());
value
}
fn reset(&self) {
let mut inner = self.inner.write().unwrap();
inner.map.clear();
inner.order.clear();
inner.bytes = 0;
self.hits.store(0, Ordering::Relaxed);
self.misses.store(0, Ordering::Relaxed);
self.evictions.store(0, Ordering::Relaxed);
}
fn trim_to(&self, target_bytes: usize) -> (usize, usize, usize, usize) {
let mut inner = self.inner.write().unwrap();
let mut removed_entries = 0usize;
let mut removed_bytes = 0usize;
while !inner.map.is_empty() && (target_bytes == 0 || inner.bytes > target_bytes) {
let old = inner.order.pop_front().expect("cache FIFO invariant");
let value = inner.map.remove(&old).expect("cache FIFO invariant");
let charge = entry_charge(&old, &value);
inner.bytes = inner
.bytes
.checked_sub(charge)
.expect("cache charge accounting invariant");
removed_entries = removed_entries
.checked_add(1)
.expect("cache entry count invariant");
removed_bytes = removed_bytes
.checked_add(charge)
.expect("cache charge accounting invariant");
}
self.evictions
.fetch_add(removed_entries as u64, Ordering::Relaxed);
(removed_entries, removed_bytes, inner.map.len(), inner.bytes)
}
fn tier_stats(&self) -> TierStats {
let inner = self.inner.read().unwrap();
TierStats {
entries: inner.map.len(),
bytes: inner.bytes,
hits: self.hits.load(Ordering::Relaxed),
misses: self.misses.load(Ordering::Relaxed),
evictions: self.evictions.load(Ordering::Relaxed),
}
}
fn snapshot(&self) -> (u64, u64, usize, usize) {
let inner = self.inner.read().unwrap();
(
self.hits.load(Ordering::Relaxed),
self.misses.load(Ordering::Relaxed),
inner.map.len(),
inner.bytes,
)
}
}
static CACHE_3J: LazyLock<FifoCache<Regge3j, SignedSqrtRational>> =
LazyLock::new(|| FifoCache::new(DEFAULT_MAX_ENTRIES, effective_budgets().three_j_bytes));
static CACHE_6J: LazyLock<FifoCache<Regge6j, SignedSqrtRational>> =
LazyLock::new(|| FifoCache::new(DEFAULT_MAX_ENTRIES, effective_budgets().six_j_bytes));
pub(crate) fn cache_3j() -> &'static FifoCache<Regge3j, SignedSqrtRational> {
&CACHE_3J
}
pub(crate) fn cache_6j() -> &'static FifoCache<Regge6j, SignedSqrtRational> {
&CACHE_6J
}
static CACHE_F: LazyLock<FifoCache<FKey, f64>> =
LazyLock::new(|| FifoCache::new(DEFAULT_MAX_ENTRIES, effective_budgets().derived_f_bytes));
pub(crate) fn cache_f() -> &'static FifoCache<FKey, f64> {
&CACHE_F
}
#[cfg(feature = "cgc-gen")]
mod cgc_cache {
use super::{CacheCharge, FifoCache};
use crate::sun::{Cgc, Irrep};
use std::sync::{Arc, LazyLock};
pub(crate) type CgcKey = (Irrep, Irrep, Irrep);
impl CacheCharge for Arc<Cgc> {
fn value_bytes(&self) -> usize {
self.storage_bytes()
}
}
const CGC_MAX_ENTRIES: usize = 1 << 16;
pub(super) const CGC_MAX_BYTES: usize = 256 << 20;
pub(crate) static CACHE_CGC: LazyLock<FifoCache<CgcKey, Arc<Cgc>>> =
LazyLock::new(|| FifoCache::new(CGC_MAX_ENTRIES, super::effective_budgets().sun_cgc_bytes));
}
#[cfg(feature = "cgc-gen")]
pub(crate) fn cache_cgc() -> &'static FifoCache<cgc_cache::CgcKey, std::sync::Arc<crate::sun::Cgc>>
{
&cgc_cache::CACHE_CGC
}
#[cfg(feature = "cgc-gen")]
mod sun_product_cache {
use super::FifoCache;
use crate::sun::{SunProduct, SunProductKey};
use std::sync::LazyLock;
pub(super) const SUN_PRODUCT_MAX_ENTRIES: usize = 256;
pub(super) const SUN_PRODUCT_MAX_BYTES: usize = 128 << 10;
pub(crate) static CACHE_SUN_PRODUCT: LazyLock<FifoCache<SunProductKey, SunProduct>> =
LazyLock::new(|| {
FifoCache::new(
SUN_PRODUCT_MAX_ENTRIES,
super::effective_budgets().sun_product_bytes,
)
});
}
#[cfg(feature = "cgc-gen")]
pub(crate) fn cache_sun_product(
) -> &'static FifoCache<crate::sun::SunProductKey, crate::sun::SunProduct> {
&sun_product_cache::CACHE_SUN_PRODUCT
}
#[cfg(feature = "cgc-gen")]
mod sun_f_cache {
use super::{CacheCharge, FifoCache};
use crate::sun::{FBlock, Irrep};
use std::sync::{Arc, LazyLock};
pub(crate) type SunFKey = (Irrep, Irrep, Irrep, Irrep, Irrep, Irrep);
impl CacheCharge for Arc<FBlock> {
fn value_bytes(&self) -> usize {
std::mem::size_of_val(self.data()) + std::mem::size_of::<FBlock>()
}
}
const SUN_F_MAX_ENTRIES: usize = 1 << 16;
pub(super) const SUN_F_MAX_BYTES: usize = 64 << 20;
pub(crate) static CACHE_SUN_F: LazyLock<FifoCache<SunFKey, Arc<FBlock>>> =
LazyLock::new(|| FifoCache::new(SUN_F_MAX_ENTRIES, super::effective_budgets().sun_f_bytes));
}
#[cfg(feature = "cgc-gen")]
pub(crate) fn cache_sun_f(
) -> &'static FifoCache<sun_f_cache::SunFKey, std::sync::Arc<crate::sun::FBlock>> {
&sun_f_cache::CACHE_SUN_F
}
#[cfg(feature = "cgc-gen")]
mod bcd_f_cache {
use super::FifoCache;
use crate::bcd::Irrep;
use crate::frcore::FBlock;
use std::sync::{Arc, LazyLock};
pub(crate) type BcdFKey = (Irrep, Irrep, Irrep, Irrep, Irrep, Irrep);
const BCD_F_MAX_ENTRIES: usize = 1 << 16;
pub(super) const BCD_F_MAX_BYTES: usize = 64 << 20;
pub(crate) static CACHE_BCD_F: LazyLock<FifoCache<BcdFKey, Arc<FBlock>>> =
LazyLock::new(|| FifoCache::new(BCD_F_MAX_ENTRIES, super::effective_budgets().bcd_f_bytes));
}
#[cfg(feature = "cgc-gen")]
pub(crate) fn cache_bcd_f(
) -> &'static FifoCache<bcd_f_cache::BcdFKey, std::sync::Arc<crate::frcore::FBlock>> {
&bcd_f_cache::CACHE_BCD_F
}
#[cfg(feature = "cgc-gen")]
mod bcd_cgc_cache {
use super::{CacheCharge, FifoCache};
use crate::bcd::{CatalogCgc, Irrep};
use std::sync::{Arc, LazyLock};
pub(crate) type BcdCgcKey = (Irrep, Irrep, Irrep);
impl CacheCharge for Arc<CatalogCgc> {
fn value_bytes(&self) -> usize {
self.storage_bytes()
}
}
const BCD_CGC_MAX_ENTRIES: usize = 1 << 16;
pub(super) const BCD_CGC_MAX_BYTES: usize = 256 << 20;
pub(crate) static CACHE_BCD_CGC: LazyLock<FifoCache<BcdCgcKey, Arc<CatalogCgc>>> =
LazyLock::new(|| {
FifoCache::new(
BCD_CGC_MAX_ENTRIES,
super::effective_budgets().bcd_cgc_bytes,
)
});
}
#[cfg(feature = "cgc-gen")]
pub(crate) fn cache_bcd_cgc(
) -> &'static FifoCache<bcd_cgc_cache::BcdCgcKey, std::sync::Arc<crate::bcd::CatalogCgc>> {
&bcd_cgc_cache::CACHE_BCD_CGC
}
pub fn reset() {
CACHE_3J.reset();
CACHE_6J.reset();
CACHE_F.reset();
#[cfg(feature = "cgc-gen")]
{
sun_product_cache::CACHE_SUN_PRODUCT.reset();
cgc_cache::CACHE_CGC.reset();
sun_f_cache::CACHE_SUN_F.reset();
bcd_f_cache::CACHE_BCD_F.reset();
bcd_cgc_cache::CACHE_BCD_CGC.reset();
}
}
pub fn trim_to(tier: CoefficientCacheTier, target_charged_bytes: usize) -> CacheTrimReport {
let (removed_entries, removed_charged_bytes, remaining_entries, remaining_charged_bytes) =
match tier {
CoefficientCacheTier::ThreeJ => CACHE_3J.trim_to(target_charged_bytes),
CoefficientCacheTier::SixJ => CACHE_6J.trim_to(target_charged_bytes),
CoefficientCacheTier::DerivedF => CACHE_F.trim_to(target_charged_bytes),
#[cfg(feature = "cgc-gen")]
CoefficientCacheTier::SunProduct => {
sun_product_cache::CACHE_SUN_PRODUCT.trim_to(target_charged_bytes)
}
#[cfg(feature = "cgc-gen")]
CoefficientCacheTier::SunCgc => cgc_cache::CACHE_CGC.trim_to(target_charged_bytes),
#[cfg(feature = "cgc-gen")]
CoefficientCacheTier::SunF => sun_f_cache::CACHE_SUN_F.trim_to(target_charged_bytes),
#[cfg(feature = "cgc-gen")]
CoefficientCacheTier::BcdCgc => {
bcd_cgc_cache::CACHE_BCD_CGC.trim_to(target_charged_bytes)
}
#[cfg(feature = "cgc-gen")]
CoefficientCacheTier::BcdF => bcd_f_cache::CACHE_BCD_F.trim_to(target_charged_bytes),
};
CacheTrimReport {
tier,
removed_entries,
removed_charged_bytes,
remaining_entries,
remaining_charged_bytes,
}
}
pub fn stats() -> CacheStats {
let (h3, m3, e3, b3) = CACHE_3J.snapshot();
let (h6, m6, e6, b6) = CACHE_6J.snapshot();
let (hf, mf, ef, bf) = CACHE_F.snapshot();
#[cfg(feature = "cgc-gen")]
let (hc, mc, ec, bc) = {
let (hp, mp, ep, bp) = sun_product_cache::CACHE_SUN_PRODUCT.snapshot();
let (h, m, e, b) = cgc_cache::CACHE_CGC.snapshot();
let (h2, m2, e2, b2) = sun_f_cache::CACHE_SUN_F.snapshot();
let (h3, m3, e3, b3) = bcd_f_cache::CACHE_BCD_F.snapshot();
let (h4, m4, e4, b4) = bcd_cgc_cache::CACHE_BCD_CGC.snapshot();
(
hp + h + h2 + h3 + h4,
mp + m + m2 + m3 + m4,
ep + e + e2 + e3 + e4,
bp + b + b2 + b3 + b4,
)
};
#[cfg(not(feature = "cgc-gen"))]
let (hc, mc, ec, bc) = (0u64, 0u64, 0usize, 0usize);
CacheStats {
hits: h3 + h6 + hf + hc,
misses: m3 + m6 + mf + mc,
entries: e3 + e6 + ef + ec,
bytes: b3 + b6 + bf + bc,
}
}
pub fn base_cache_stats() -> BaseCacheStats {
BaseCacheStats {
three_j: CACHE_3J.tier_stats(),
six_j: CACHE_6J.tier_stats(),
derived_f: CACHE_F.tier_stats(),
}
}
#[cfg(feature = "cgc-gen")]
pub fn generated_cache_stats() -> GeneratedCacheStats {
GeneratedCacheStats {
sun_product: sun_product_cache::CACHE_SUN_PRODUCT.tier_stats(),
sun_cgc: cgc_cache::CACHE_CGC.tier_stats(),
sun_f: sun_f_cache::CACHE_SUN_F.tier_stats(),
bcd_cgc: bcd_cgc_cache::CACHE_BCD_CGC.tier_stats(),
bcd_f: bcd_f_cache::CACHE_BCD_F.tier_stats(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use num_bigint::BigInt;
use num_rational::Ratio;
use std::sync::Arc;
fn val(n: i64) -> SignedSqrtRational {
SignedSqrtRational::from_prefactor_radical(
Ratio::from(BigInt::from(1)),
Ratio::new(BigInt::from(n.unsigned_abs()), BigInt::from(1)),
)
}
#[test]
fn hit_returns_stored_and_counts() {
let c: FifoCache<u32, SignedSqrtRational> = FifoCache::new(16, 1 << 20);
let mut computed = 0;
let a = c.get_or_compute(7, || {
computed += 1;
val(7)
});
let b = c.get_or_compute(7, || {
computed += 1;
val(999) });
assert_eq!(a, b);
assert_eq!(computed, 1, "second lookup must be a hit");
let (hits, misses, entries, _) = c.snapshot();
assert_eq!((hits, misses, entries), (1, 1, 1));
}
#[test]
fn entry_bound_evicts_oldest() {
let c: FifoCache<u32, SignedSqrtRational> = FifoCache::new(3, 1 << 30);
for k in 0..5u32 {
c.get_or_compute(k, || val(k as i64 + 1));
}
let (_, _, entries, _) = c.snapshot();
assert!(entries <= 3, "entry bound violated: {entries}");
assert!(c.inner.read().unwrap().map.contains_key(&4));
assert!(!c.inner.read().unwrap().map.contains_key(&0));
}
#[test]
fn byte_bound_evicts() {
let per = entry_charge(&0u32, &val(1));
let c: FifoCache<u32, SignedSqrtRational> = FifoCache::new(1_000_000, per * 2 + per / 2);
for k in 0..20u32 {
c.get_or_compute(k, || val(k as i64 + 1));
}
let (_, _, _, bytes) = c.snapshot();
assert!(
bytes <= per * 2 + per / 2,
"retained charge cap violated: {bytes}"
);
}
#[test]
fn trim_removes_fifo_prefix_with_exact_accounting() {
let c: FifoCache<u32, SignedSqrtRational> = FifoCache::new(16, 1 << 20);
let values = [
val(1),
SignedSqrtRational::from_prefactor_radical(
Ratio::from(BigInt::from(1)),
Ratio::new(BigInt::from(1) << 256, BigInt::from(1)),
),
val(3),
];
for (key, value) in (1..=3).zip(values.iter().cloned()) {
c.get_or_compute(key, || value);
}
let charges = (1..=3)
.zip(values.iter())
.map(|(key, value)| entry_charge(&key, value))
.collect::<Vec<_>>();
assert!(charges[1] > charges[0]);
let (_, _, before_entries, before_bytes) = c.snapshot();
let (removed_entries, removed_bytes, remaining_entries, remaining_bytes) =
c.trim_to(charges[1] + charges[2] - 1);
assert_eq!((before_entries, before_bytes), (3, charges.iter().sum()));
assert_eq!(
(removed_entries, removed_bytes),
(2, charges[0] + charges[1])
);
assert_eq!((remaining_entries, remaining_bytes), (1, charges[2]));
assert!(remaining_bytes < charges[1] + charges[2] - 1);
assert!(!c.inner.read().unwrap().map.contains_key(&1));
assert!(!c.inner.read().unwrap().map.contains_key(&2));
assert!(c.inner.read().unwrap().map.contains_key(&3));
assert_eq!(c.tier_stats().evictions, 2);
assert_eq!(c.trim_to(charges[2]), (0, 0, 1, charges[2]));
assert_eq!(c.trim_to(0), (1, charges[2], 0, 0));
assert_eq!(c.tier_stats().evictions, 3);
c.reset();
assert_eq!(c.tier_stats(), TierStats::default());
}
#[test]
fn in_flight_miss_can_publish_after_trim() {
use std::sync::mpsc;
let cache = Arc::new(FifoCache::<u32, SignedSqrtRational>::new(16, 1 << 20));
let (started_tx, started_rx) = mpsc::channel();
let (resume_tx, resume_rx) = mpsc::channel();
let worker_cache = Arc::clone(&cache);
let worker = std::thread::spawn(move || {
worker_cache.get_or_compute(9, || {
started_tx.send(()).unwrap();
resume_rx.recv().unwrap();
val(9)
})
});
started_rx.recv().unwrap();
assert_eq!(cache.trim_to(0), (0, 0, 0, 0));
resume_tx.send(()).unwrap();
assert_eq!(worker.join().unwrap(), val(9));
assert_eq!(cache.tier_stats().entries, 1);
}
#[test]
fn eviction_thrash_never_changes_values() {
let c: FifoCache<u32, SignedSqrtRational> = FifoCache::new(1, 1 << 30);
for round in 0..3 {
for k in 0..200u32 {
let got = c.get_or_compute(k, || val(k as i64 * 3 + 1));
assert_eq!(got, val(k as i64 * 3 + 1), "round {round} key {k}");
}
}
}
#[test]
fn f64_tier_hit_skips_recompute() {
let c: FifoCache<u32, f64> = FifoCache::new(16, 1 << 20);
let mut rounded = 0;
let a = c.get_or_compute(9, || {
rounded += 1;
val(9).to_f64() });
let b = c.get_or_compute(9, || {
rounded += 1;
val(999).to_f64() });
assert_eq!(a, b);
assert_eq!(rounded, 1, "a hit must not re-run the rounding closure");
let (hits, misses, entries, _) = c.snapshot();
assert_eq!((hits, misses, entries), (1, 1, 1));
}
#[test]
fn f64_tier_charge_is_fixed() {
assert_eq!((1.0f64).value_bytes(), std::mem::size_of::<f64>());
assert_eq!((-3.5f64).value_bytes(), std::mem::size_of::<f64>());
assert_eq!(
entry_charge(&7u32, &1.0f64),
std::mem::size_of::<f64>() + 2 * std::mem::size_of::<u32>(),
"fixed-size key accounting must remain unchanged"
);
}
#[cfg(feature = "cgc-gen")]
#[test]
fn generated_keys_charge_owned_irrep_weights() {
use super::bcd_f_cache::BcdFKey;
use super::cgc_cache::CgcKey;
use crate::bcd::{Irrep as BcdIrrep, Series};
use crate::sun::Irrep as SunIrrep;
let sun = SunIrrep::from_dynkin(&[1; 12]).unwrap();
let sun_key: CgcKey = (sun.clone(), sun.clone(), sun);
let sun_irrep_bytes = std::mem::size_of::<SunIrrep>() + 13 * std::mem::size_of::<i64>();
assert_eq!(sun_key.key_bytes(), 3 * sun_irrep_bytes);
let bcd = BcdIrrep::from_dynkin(Series::C, &[1; 12]).unwrap();
let bcd_key: BcdFKey = (
bcd.clone(),
bcd.clone(),
bcd.clone(),
bcd.clone(),
bcd.clone(),
bcd,
);
let bcd_irrep_bytes = std::mem::size_of::<BcdIrrep>() + 12 * std::mem::size_of::<i64>();
assert_eq!(bcd_key.key_bytes(), 6 * bcd_irrep_bytes);
}
#[cfg(feature = "cgc-gen")]
#[test]
fn sun_product_tier_caps_charge_and_oversize_eviction_are_exact() {
use super::sun_product_cache::{SUN_PRODUCT_MAX_BYTES, SUN_PRODUCT_MAX_ENTRIES};
use crate::sun::{Irrep as SunIrrep, SunProduct, SunProductKey};
use std::collections::BTreeMap;
assert_eq!(SUN_PRODUCT_MAX_ENTRIES, 256);
assert_eq!(SUN_PRODUCT_MAX_BYTES, 128 << 10);
let a = SunIrrep::from_dynkin(&[2, 1]).unwrap();
let b = SunIrrep::from_dynkin(&[1, 2]).unwrap();
let key = SunProductKey::new(&a, &b);
let key_bytes = 2 * std::mem::size_of::<SunIrrep>()
+ (a.rank() + b.rank()) * std::mem::size_of::<i64>();
assert_eq!(key.key_bytes(), key_bytes);
let trivial = SunIrrep::trivial(3).unwrap();
let adjoint = SunIrrep::from_dynkin(&[1, 1]).unwrap();
let product =
SunProduct::from_map(BTreeMap::from([(trivial.clone(), 1), (adjoint.clone(), 2)]));
let value_bytes = std::mem::size_of::<SunProduct>()
+ 2 * std::mem::size_of::<usize>()
+ 2 * std::mem::size_of::<(SunIrrep, u32)>()
+ (trivial.rank() + adjoint.rank()) * std::mem::size_of::<i64>();
assert_eq!(product.value_bytes(), value_bytes);
let charge = value_bytes + 2 * key_bytes;
assert_eq!(entry_charge(&key, &product), charge);
let cache: FifoCache<SunProductKey, SunProduct> = FifoCache::new(usize::MAX, charge - 1);
assert_eq!(
cache.get_or_compute(key, || product.clone()),
product,
"an oversize exact value is still returned"
);
assert_eq!(
cache.tier_stats(),
TierStats {
misses: 1,
evictions: 1,
..TierStats::default()
}
);
}
#[cfg(feature = "cgc-gen")]
#[test]
fn concurrent_sun_product_misses_return_the_write_recheck_winner() {
use crate::sun::{Irrep as SunIrrep, SunProduct, SunProductKey};
use std::collections::BTreeMap;
use std::sync::{Arc, Barrier};
let a = SunIrrep::trivial(3).unwrap();
let b = SunIrrep::from_dynkin(&[1, 0]).unwrap();
let key = SunProductKey::new(&a, &b);
let channel = b.clone();
let cache = Arc::new(FifoCache::new(16, 1 << 20));
let all_computing = Arc::new(Barrier::new(8));
let products = (0..8)
.map(|_| {
let cache = Arc::clone(&cache);
let key = key.clone();
let channel = channel.clone();
let all_computing = Arc::clone(&all_computing);
std::thread::spawn(move || {
cache.get_or_compute(key, || {
let product = SunProduct::from_map(BTreeMap::from([(channel, 1)]));
all_computing.wait();
product
})
})
})
.collect::<Vec<_>>()
.into_iter()
.map(|handle| handle.join().unwrap())
.collect::<Vec<_>>();
assert!(products[1..]
.iter()
.all(|product| products[0].ptr_eq(product)));
assert_eq!(cache.tier_stats().entries, 1);
assert_eq!(cache.tier_stats().misses, 8);
}
#[cfg(feature = "cgc-gen")]
#[test]
fn local_trim_releases_one_sun_product_arc_owner() {
use crate::sun::{Irrep as SunIrrep, SunProduct, SunProductKey};
use std::collections::BTreeMap;
let three = SunIrrep::from_dynkin(&[1, 0]).unwrap();
let three_bar = SunIrrep::from_dynkin(&[0, 1]).unwrap();
let key = SunProductKey::new(&three, &three_bar);
let external = SunProduct::from_map(BTreeMap::from([
(SunIrrep::trivial(3).unwrap(), 1),
(SunIrrep::from_dynkin(&[1, 1]).unwrap(), 1),
]));
let local = FifoCache::new(16, 1 << 20);
local.insert(key, external.clone());
let before = external.strong_count();
assert!(
before >= 2,
"caller and local cache own the shared channels"
);
assert_eq!(local.trim_to(0).0, 1);
assert_eq!(external.strong_count(), before - 1);
assert_eq!(external.iter().count(), 2);
}
#[cfg(feature = "cgc-gen")]
#[test]
fn deep_key_bytes_drive_eviction_oversize_and_reset() {
use super::bcd_f_cache::BcdFKey;
use super::cgc_cache::CgcKey;
use crate::bcd::{Irrep as BcdIrrep, Series};
use crate::sun::Irrep as SunIrrep;
let sun = |label| SunIrrep::from_dynkin(&[label; 12]).unwrap();
let first: CgcKey = (sun(1), sun(1), sun(1));
let second: CgcKey = (sun(2), sun(2), sun(2));
let one_entry_budget = entry_charge(&first, &1.0f64);
let cache: FifoCache<CgcKey, f64> = FifoCache::new(usize::MAX, one_entry_budget);
cache.insert(first.clone(), 1.0);
cache.insert(second, 2.0);
assert_eq!(cache.tier_stats().entries, 1);
assert_eq!(cache.tier_stats().evictions, 1);
assert!(
cache.get(&first).is_none(),
"the oldest deep key is evicted"
);
cache.reset();
assert_eq!(cache.tier_stats(), TierStats::default());
let bcd = BcdIrrep::from_dynkin(Series::C, &[1; 12]).unwrap();
let bcd_key: BcdFKey = (
bcd.clone(),
bcd.clone(),
bcd.clone(),
bcd.clone(),
bcd.clone(),
bcd,
);
let shallow_only = std::mem::size_of::<f64>() + 2 * std::mem::size_of::<BcdFKey>();
assert!(entry_charge(&bcd_key, &1.0f64) > shallow_only);
let oversize: FifoCache<BcdFKey, f64> = FifoCache::new(usize::MAX, shallow_only);
oversize.insert(bcd_key, 1.0);
assert_eq!(oversize.tier_stats().entries, 0);
assert_eq!(oversize.tier_stats().bytes, 0);
assert_eq!(oversize.tier_stats().evictions, 1);
}
#[cfg(feature = "cgc-gen")]
#[test]
fn cgc_tier_charges_storage_bytes_and_evicts_by_bytes() {
use super::cgc_cache::CgcKey;
use crate::sun::{cgc, Cgc, Irrep};
use std::sync::Arc;
let irr = |d: &[i64]| Irrep::from_dynkin(d).unwrap();
let a = Arc::new(cgc(&irr(&[1, 0]), &irr(&[0, 1]), &irr(&[1, 1])).unwrap()); let b = Arc::new(cgc(&irr(&[1, 1]), &irr(&[1, 1]), &irr(&[1, 1])).unwrap());
assert!(a.value_bytes() >= a.storage_bytes());
assert_eq!(a.value_bytes(), a.storage_bytes());
let ka = (irr(&[1, 0]), irr(&[0, 1]), irr(&[1, 1]));
let kb = (irr(&[1, 1]), irr(&[1, 1]), irr(&[1, 1]));
let budget = a.value_bytes().max(b.value_bytes()) + 2 * ka.key_bytes() + 8;
let c: FifoCache<CgcKey, Arc<Cgc>> = FifoCache::new(1_000_000, budget);
c.insert(ka.clone(), a);
c.insert(kb, b);
let (_, _, entries, bytes) = c.snapshot();
assert!(entries <= 1, "retained-charge cap kept {entries} entries");
assert!(
bytes <= budget,
"retained charge exceeded cap: {bytes} > {budget}"
);
assert!(c.get(&ka).is_none());
}
#[cfg(feature = "cgc-gen")]
#[test]
fn sun_f_tier_charges_block_bytes_and_evicts_by_bytes() {
use super::sun_f_cache::SunFKey;
use crate::sun::{f_symbol, FBlock, Irrep};
use std::sync::Arc;
let irr = |d: &[i64]| Irrep::from_dynkin(d).unwrap();
let e8 = irr(&[1, 1]);
let a = Arc::new(f_symbol(&e8, &e8, &e8, &e8, &e8, &e8).unwrap());
let triv = Irrep::trivial(3).unwrap();
let three = irr(&[1, 0]);
let six = irr(&[2, 0]);
let b = Arc::new(f_symbol(&triv, &three, &three, &six, &three, &six).unwrap());
assert_eq!(
a.value_bytes(),
std::mem::size_of_val(a.data()) + std::mem::size_of::<FBlock>()
);
assert!(a.value_bytes() > b.value_bytes(), "2⁴ block > 1⁴ block");
let ka = (
e8.clone(),
e8.clone(),
e8.clone(),
e8.clone(),
e8.clone(),
e8.clone(),
);
let kb = (
triv.clone(),
three.clone(),
three.clone(),
six.clone(),
three.clone(),
six.clone(),
);
let budget = a.value_bytes() + 2 * ka.key_bytes() + 8;
let c: FifoCache<SunFKey, Arc<FBlock>> = FifoCache::new(1_000_000, budget);
c.insert(ka.clone(), a);
c.insert(kb, b);
let (_, _, entries, bytes) = c.snapshot();
assert!(entries <= 1, "retained-charge cap kept {entries} entries");
assert!(
bytes <= budget,
"retained charge exceeded cap: {bytes} > {budget}"
);
assert!(c.get(&ka).is_none(), "oldest not evicted");
}
#[test]
fn reset_clears_entries_and_counters() {
let c: FifoCache<u32, SignedSqrtRational> = FifoCache::new(16, 1 << 20);
c.get_or_compute(1, || val(1));
c.get_or_compute(1, || val(1));
c.reset();
let (hits, misses, entries, bytes) = c.snapshot();
assert_eq!((hits, misses, entries, bytes), (0, 0, 0, 0));
}
#[test]
fn evictions_counted_on_entry_bound() {
let c: FifoCache<u32, SignedSqrtRational> = FifoCache::new(3, 1 << 30);
for k in 0..5u32 {
c.get_or_compute(k, || val(k as i64 + 1));
}
let ts = c.tier_stats();
assert_eq!(ts.entries, 3);
assert_eq!(ts.evictions, 2, "5 inserts over a cap of 3 evict exactly 2");
}
#[test]
fn evictions_counted_on_byte_bound() {
let per = entry_charge(&0u32, &val(1));
let c: FifoCache<u32, SignedSqrtRational> = FifoCache::new(1_000_000, per * 2 + per / 2);
for k in 0..20u32 {
c.get_or_compute(k, || val(k as i64 + 1));
}
assert!(
c.tier_stats().evictions > 0,
"retained-charge pressure must count evictions"
);
}
#[test]
fn oversize_entry_counts_as_eviction() {
let c: FifoCache<u32, SignedSqrtRational> = FifoCache::new(1_000_000, 1);
c.get_or_compute(7, || val(7));
let ts = c.tier_stats();
assert_eq!(ts.entries, 0, "oversize entry is not retained");
assert_eq!(ts.bytes, 0);
assert_eq!(ts.evictions, 1, "a rejected oversize entry counts");
}
#[test]
fn reset_zeroes_evictions() {
let c: FifoCache<u32, SignedSqrtRational> = FifoCache::new(1, 1 << 30);
for k in 0..5u32 {
c.get_or_compute(k, || val(k as i64 + 1));
}
assert!(c.tier_stats().evictions > 0, "precondition: some evictions");
c.reset();
assert_eq!(
c.tier_stats(),
TierStats::default(),
"reset zeroes every field"
);
}
#[test]
fn concurrent_mixed_hit_miss_equals_sequential() {
let c: Arc<FifoCache<u32, SignedSqrtRational>> = Arc::new(FifoCache::new(1 << 20, 1 << 30));
let keys: Vec<u32> = (0..64).collect();
let seq: Vec<SignedSqrtRational> = keys.iter().map(|&k| val(k as i64 + 1)).collect();
let mut handles = Vec::new();
for t in 0..8u32 {
let c = Arc::clone(&c);
handles.push(std::thread::spawn(move || {
let mut out = Vec::new();
for i in 0..64u32 {
let k = (i + t) % 64;
out.push((k, c.get_or_compute(k, || val(k as i64 + 1))));
}
out
}));
}
for h in handles {
for (k, got) in h.join().unwrap() {
assert_eq!(got, seq[k as usize], "thread value diverged at key {k}");
}
}
}
#[test]
fn policy_observation_races_configuration_once() {
let cell = Arc::new(OnceLock::new());
let configured = CoefficientCacheBudgets::disabled();
let barrier = Arc::new(std::sync::Barrier::new(2));
let observer_cell = Arc::clone(&cell);
let observer_barrier = Arc::clone(&barrier);
let observer = std::thread::spawn(move || {
observer_barrier.wait();
*effective_budgets_in(&observer_cell)
});
barrier.wait();
let configured_result = configure_budgets_in(&cell, configured);
let observed = observer.join().unwrap();
match configured_result {
Ok(()) => assert_eq!(observed, configured),
Err(CacheBudgetError::AlreadyInitialized) => {
assert_eq!(observed, CoefficientCacheBudgets::default())
}
Err(error) => panic!("unexpected configuration result: {error}"),
}
}
}