diff --git a/Cargo.lock b/Cargo.lock index 307b9c91..21fadba2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4242,6 +4242,7 @@ dependencies = [ "serde", "siphasher 1.0.1", "tokio", + "utils", "whatlang", "xxhash-rust", ] @@ -5089,6 +5090,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick_cache" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d7c94f8935a9df96bb6380e8592c70edf497a643f94bd23b2f76b399385dbf4" +dependencies = [ + "ahash 0.8.11", + "equivalent", + "hashbrown 0.14.5", + "parking_lot", +] + [[package]] name = "quinn" version = "0.11.6" @@ -7497,17 +7510,16 @@ dependencies = [ "base64 0.22.1", "blake3", "chrono", - "dashmap", "form_urlencoded", "futures", "http-body-util", - "lru-cache", "mail-auth", "mail-send", "parking_lot", "pem", "privdrop", "psl", + "quick_cache", "rand 0.8.5", "rcgen 0.13.2", "regex", diff --git a/crates/common/src/auth/access_token.rs b/crates/common/src/auth/access_token.rs index 67ee6deb..671eae93 100644 --- a/crates/common/src/auth/access_token.rs +++ b/crates/common/src/auth/access_token.rs @@ -6,7 +6,7 @@ use directory::{ backend::internal::{lookup::DirectoryStore, PrincipalField}, - Permission, Principal, QueryBy, + Permission, Principal, QueryBy, PERMISSIONS_BITSET_SIZE, }; use jmap_proto::{ request::RequestMethod, @@ -15,14 +15,15 @@ use jmap_proto::{ use std::{ hash::{DefaultHasher, Hash, Hasher}, sync::Arc, - time::Instant, }; use store::query::acl::AclQuery; use trc::AddContext; -use utils::map::{ - bitmap::{Bitmap, BitmapItem}, - ttl_dashmap::TtlMap, - vec_map::VecMap, +use utils::{ + cache::TtlEntry, + map::{ + bitmap::{Bitmap, BitmapItem}, + vec_map::VecMap, + }, }; use crate::Server; @@ -109,6 +110,7 @@ impl Server { .unwrap_or_default(), quota: principal.quota(), permissions, + obj_size: 0, }) } @@ -183,27 +185,52 @@ impl Server { } } - Ok(access_token) + Ok(access_token.update_size()) } - pub fn cache_access_token(&self, access_token: Arc) { - self.inner.data.access_tokens.insert_with_ttl( - access_token.primary_id(), - access_token, - Instant::now() + self.core.jmap.session_cache_ttl, - ); + pub async fn get_or_build_access_token( + &self, + principal: Principal, + ) -> trc::Result> { + match self + .inner + .cache + .access_tokens + .get_value_or_guard_async(&principal.id()) + .await + { + Ok(token) => Ok(token), + Err(guard) => { + let token = Arc::new( + self.update_access_token(self.build_access_token(principal).await?) + .await?, + ); + let _ = guard.insert(TtlEntry::new( + token.clone(), + self.core.jmap.session_cache_ttl, + )); + Ok(token) + } + } } pub async fn get_cached_access_token(&self, primary_id: u32) -> trc::Result> { - if let Some(access_token) = self.inner.data.access_tokens.get_with_ttl(&primary_id) { - Ok(access_token) - } else { - // Refresh ACL token - self.get_access_token(primary_id).await.map(|access_token| { - let access_token = Arc::new(access_token); - self.cache_access_token(access_token.clone()); - access_token - }) + match self + .inner + .cache + .access_tokens + .get_value_or_guard_async(&primary_id) + .await + { + Ok(token) => Ok(token), + Err(guard) => { + let token = Arc::new(self.get_access_token(primary_id).await?); + let _ = guard.insert(TtlEntry::new( + token.clone(), + self.core.jmap.session_cache_ttl, + )); + Ok(token) + } } } } @@ -467,4 +494,17 @@ impl AccessToken { tenant: self.tenant, } } + + pub fn update_size(mut self) -> Self { + self.obj_size = ((std::mem::size_of::() * 2) + + (std::mem::size_of::() * 3) + + (self.member_of.len() * std::mem::size_of::()) + + (self.access_to.len() * (std::mem::size_of::() + std::mem::size_of::())) + + self.name.len() + + self.description.as_ref().map_or(0, |v| v.len()) + + self.emails.iter().map(|v| v.len()).sum::() + + (PERMISSIONS_BITSET_SIZE * std::mem::size_of::())) + as u64; + self + } } diff --git a/crates/common/src/auth/mod.rs b/crates/common/src/auth/mod.rs index 7de1b884..c911e7df 100644 --- a/crates/common/src/auth/mod.rs +++ b/crates/common/src/auth/mod.rs @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{net::IpAddr, sync::Arc, time::Instant}; +use std::{net::IpAddr, sync::Arc}; use directory::{ core::secret::verify_secret_hash, Directory, Permission, Permissions, Principal, QueryBy, @@ -12,7 +12,10 @@ use directory::{ use jmap_proto::types::collection::Collection; use mail_send::Credentials; use oauth::GrantType; -use utils::map::{bitmap::Bitmap, ttl_dashmap::TtlMap, vec_map::VecMap}; +use utils::{ + cache::CacheItemWeight, + map::{bitmap::Bitmap, vec_map::VecMap}, +}; use crate::Server; @@ -32,6 +35,7 @@ pub struct AccessToken { pub quota: u64, pub permissions: Permissions, pub tenant: Option, + pub obj_size: u64, } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] @@ -72,21 +76,7 @@ impl Server { } } _ => match self.authenticate_credentials(req, directory).await { - Ok(principal) => { - if let Some(access_token) = - self.inner.data.access_tokens.get_with_ttl(&principal.id()) - { - Ok(access_token) - } else { - self.build_access_token(principal) - .await - .map(|access_token| { - let access_token = Arc::new(access_token); - self.cache_access_token(access_token.clone()); - access_token - }) - } - } + Ok(principal) => self.get_or_build_access_token(principal).await, Err(err) => Err(err), }, } @@ -195,14 +185,6 @@ impl Server { )) } } - - pub fn cache_session(&self, session_id: String, access_token: &AccessToken) { - self.inner.data.http_auth_cache.insert_with_ttl( - session_id, - access_token.primary_id(), - Instant::now() + self.core.jmap.session_cache_ttl, - ); - } } impl<'x> AuthRequest<'x> { @@ -247,6 +229,12 @@ impl<'x> AuthRequest<'x> { } } +impl CacheItemWeight for AccessToken { + fn weight(&self) -> u64 { + self.obj_size + } +} + pub(crate) trait CredentialsUsername { fn login(&self) -> Option<&str>; } diff --git a/crates/common/src/auth/roles.rs b/crates/common/src/auth/roles.rs index d94decf1..3e1aae8a 100644 --- a/crates/common/src/auth/roles.rs +++ b/crates/common/src/auth/roles.rs @@ -9,9 +9,11 @@ use std::sync::{Arc, LazyLock}; use ahash::AHashSet; use directory::{ backend::internal::{lookup::DirectoryStore, PrincipalField}, - Permission, Permissions, QueryBy, ROLE_ADMIN, ROLE_TENANT_ADMIN, ROLE_USER, + Permission, Permissions, QueryBy, PERMISSIONS_BITSET_SIZE, ROLE_ADMIN, ROLE_TENANT_ADMIN, + ROLE_USER, }; use trc::AddContext; +use utils::cache::CacheItemWeight; use crate::Server; @@ -33,10 +35,19 @@ impl Server { ROLE_ADMIN => Ok(ADMIN_PERMISSIONS.clone()), ROLE_TENANT_ADMIN => Ok(TENANT_ADMIN_PERMISSIONS.clone()), role_id => { - if let Some(role_permissions) = self.inner.data.permissions.get(&role_id) { - Ok(role_permissions.clone()) - } else { - self.build_role_permissions(role_id).await + match self + .inner + .cache + .permissions + .get_value_or_guard_async(&role_id) + .await + { + Ok(permissions) => Ok(permissions), + Err(guard) => { + let permissions = self.build_role_permissions(role_id).await?; + let _ = guard.insert(permissions.clone()); + Ok(permissions) + } } } } @@ -81,7 +92,7 @@ impl Server { } role_id => { // Try with the cache - if let Some(role_permissions) = self.inner.data.permissions.get(&role_id) { + if let Some(role_permissions) = self.inner.cache.permissions.get(&role_id) { return_permissions.union(role_permissions.as_ref()); } else { let mut role_permissions = RolePermissions::default(); @@ -133,7 +144,7 @@ impl Server { } else { // Cache role self.inner - .data + .cache .permissions .insert(role_id, Arc::new(role_permissions)); } @@ -147,13 +158,7 @@ impl Server { } } - // Cache role - let return_permissions = Arc::new(return_permissions); - self.inner - .data - .permissions - .insert(role_id, return_permissions.clone()); - Ok(return_permissions) + Ok(Arc::new(return_permissions)) } } @@ -213,3 +218,9 @@ fn admin_permissions() -> Arc { disabled: Permissions::new(), }) } + +impl CacheItemWeight for RolePermissions { + fn weight(&self) -> u64 { + (PERMISSIONS_BITSET_SIZE * std::mem::size_of::() * 2) as u64 + } +} diff --git a/crates/common/src/config/inner.rs b/crates/common/src/config/inner.rs index 797eb240..0358c1f4 100644 --- a/crates/common/src/config/inner.rs +++ b/crates/common/src/config/inner.rs @@ -12,14 +12,13 @@ use dashmap::DashMap; use mail_send::smtp::tls::build_tls_connector; use parking_lot::RwLock; use utils::{ + cache::{Cache, CacheWithTtl}, config::Config, - lru_cache::{LruCache, LruCached}, - map::ttl_dashmap::{TtlDashMap, TtlMap}, snowflake::SnowflakeIdGenerator, }; use crate::{ - listener::blocked::BlockedIps, manager::webadmin::WebAdminManager, Data, + listener::blocked::BlockedIps, manager::webadmin::WebAdminManager, Caches, Data, ThrottleKeyHasherBuilder, TlsConnectors, }; @@ -59,12 +58,8 @@ impl Data { }) .ok() .map(Arc::new), - access_tokens: TtlDashMap::with_capacity(capacity, shard_amount), - http_auth_cache: TtlDashMap::with_capacity(capacity, shard_amount), blocked_ips: RwLock::new(BlockedIps::parse(config).blocked_ip_addresses), blocked_ips_version: 0.into(), - permissions: Default::default(), - permissions_version: 0.into(), jmap_id_gen: id_generator.clone(), queue_id_gen: id_generator.clone(), span_id_gen: id_generator, @@ -83,15 +78,6 @@ impl Data { RandomState::default(), shard_amount, ), - account_cache: LruCache::with_capacity( - config.property("cache.account.size").unwrap_or(2048), - ), - mailbox_cache: LruCache::with_capacity( - config.property("cache.mailbox.size").unwrap_or(2048), - ), - threads_cache: LruCache::with_capacity( - config.property("cache.thread.size").unwrap_or(2048), - ), logos: Default::default(), smtp_session_throttle: DashMap::with_capacity_and_hasher_and_shard_amount( capacity, @@ -109,17 +95,29 @@ impl Data { } } +impl Caches { + pub fn parse(config: &mut Config) -> Self { + Caches { + access_tokens: CacheWithTtl::from_config(config, "cache.access-tokens"), + http_auth: CacheWithTtl::from_config(config, "cache.http-auth"), + permissions: Cache::from_config(config, "cache.permissions"), + permissions_version: 0.into(), + account: Cache::from_config(config, "cache.account"), + mailbox: Cache::from_config(config, "cache.mailbox"), + threads: Cache::from_config(config, "cache.threads"), + bayes: CacheWithTtl::from_config(config, "cache.bayes"), + dnsbl: CacheWithTtl::from_config(config, "cache.dnsbl"), + } + } +} + impl Default for Data { fn default() -> Self { Self { tls_certificates: Default::default(), tls_self_signed_cert: Default::default(), - access_tokens: Default::default(), - http_auth_cache: Default::default(), blocked_ips: Default::default(), blocked_ips_version: 0.into(), - permissions: Default::default(), - permissions_version: 0.into(), jmap_id_gen: Default::default(), queue_id_gen: Default::default(), span_id_gen: Default::default(), @@ -127,9 +125,6 @@ impl Default for Data { config_version: Default::default(), jmap_limiter: Default::default(), imap_limiter: Default::default(), - account_cache: LruCache::with_capacity(2048), - mailbox_cache: LruCache::with_capacity(2048), - threads_cache: LruCache::with_capacity(2048), logos: Default::default(), smtp_session_throttle: Default::default(), smtp_queue_throttle: Default::default(), diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 71108b4b..fac65848 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -27,13 +27,13 @@ use listener::{ }; use manager::webadmin::{Resource, WebAdminManager}; +use nlp::bayes::{TokenHash, Weights}; use parking_lot::{Mutex, RwLock}; use rustls::sign::CertifiedKey; use tokio::sync::{mpsc, Notify}; use tokio_rustls::TlsConnector; use utils::{ - lru_cache::LruCache, - map::ttl_dashmap::{ADashMap, TtlDashMap}, + cache::{Cache, CacheItemWeight, CacheWithTtl}, snowflake::SnowflakeIdGenerator, }; @@ -92,6 +92,7 @@ pub struct Server { pub struct Inner { pub shared_core: ArcSwap, pub data: Data, + pub cache: Caches, pub ipc: Ipc, } @@ -99,15 +100,9 @@ pub struct Data { pub tls_certificates: ArcSwap>>, pub tls_self_signed_cert: Option>, - pub access_tokens: TtlDashMap>, - pub http_auth_cache: TtlDashMap, - pub blocked_ips: RwLock>, pub blocked_ips_version: AtomicU8, - pub permissions: ADashMap>, - pub permissions_version: AtomicU8, - pub asn_geo_data: AsnGeoLookupData, pub jmap_id_gen: SnowflakeIdGenerator, @@ -120,10 +115,6 @@ pub struct Data { pub jmap_limiter: DashMap, RandomState>, pub imap_limiter: DashMap, RandomState>, - pub account_cache: LruCache>, - pub mailbox_cache: LruCache>, - pub threads_cache: LruCache>, - pub logos: Mutex>>>>, pub smtp_session_throttle: DashMap, @@ -131,6 +122,21 @@ pub struct Data { pub smtp_connectors: TlsConnectors, } +pub struct Caches { + pub access_tokens: CacheWithTtl>, + pub http_auth: CacheWithTtl, + + pub permissions: Cache>, + pub permissions_version: AtomicU8, + + pub account: Cache>, + pub mailbox: Cache>, + pub threads: Cache>, + + pub bayes: CacheWithTtl>, + pub dnsbl: CacheWithTtl>>>, +} + pub struct Ipc { pub state_tx: mpsc::Sender, pub housekeeper_tx: mpsc::Sender, @@ -165,6 +171,7 @@ pub struct Account { pub mailbox_state: AHashMap, pub state_email: Option, pub state_mailbox: Option, + pub obj_size: u64, } #[derive(Debug, Default, Clone)] @@ -190,6 +197,7 @@ pub struct MailboxState { pub total_messages: usize, pub modseq: Option, pub next_state: Option>, + pub obj_size: u64, } #[derive(Debug, Clone)] @@ -231,6 +239,49 @@ pub struct Core { pub enterprise: Option, } +impl CacheItemWeight for AccountId { + fn weight(&self) -> u64 { + (std::mem::size_of::() * 2) as u64 + } +} + +impl CacheItemWeight for MailboxId { + fn weight(&self) -> u64 { + (std::mem::size_of::() * 2) as u64 + } +} + +impl CacheItemWeight for Threads { + fn weight(&self) -> u64 { + ((self.threads.len() + 1) * std::mem::size_of::()) as u64 + } +} + +impl CacheItemWeight for MailboxState { + fn weight(&self) -> u64 { + self.obj_size + } +} + +impl CacheItemWeight for Account { + fn weight(&self) -> u64 { + self.obj_size + } +} + +impl MailboxState { + pub fn calculate_weight(&self) -> u64 { + (std::mem::size_of::() * 5) as u64 + + (self.id_to_imap.len() * std::mem::size_of::() + std::mem::size_of::()) + as u64 + + (self.uid_to_id.len() * std::mem::size_of::()) as u64 + + self + .next_state + .as_ref() + .map_or(0, |n| n.next_state.calculate_weight()) + } +} + pub trait IntoString: Sized { fn into_string(self) -> String; } @@ -322,6 +373,25 @@ impl Default for Inner { shared_core: Default::default(), data: Default::default(), ipc: Default::default(), + cache: Default::default(), + } + } +} + +#[cfg(feature = "test_mode")] +#[allow(clippy::derivable_impls)] +impl Default for Caches { + fn default() -> Self { + Self { + access_tokens: CacheWithTtl::new(1024, 10 * 1024 * 1024), + http_auth: CacheWithTtl::new(1024, 10 * 1024 * 1024), + permissions: Cache::new(1024, 10 * 1024 * 1024), + permissions_version: Default::default(), + account: Cache::new(1024, 10 * 1024 * 1024), + mailbox: Cache::new(1024, 10 * 1024 * 1024), + threads: Cache::new(1024, 10 * 1024 * 1024), + bayes: CacheWithTtl::new(1024, 10 * 1024 * 1024), + dnsbl: CacheWithTtl::new(1024, 10 * 1024 * 1024), } } } diff --git a/crates/common/src/manager/boot.rs b/crates/common/src/manager/boot.rs index 74785670..bfb47fd8 100644 --- a/crates/common/src/manager/boot.rs +++ b/crates/common/src/manager/boot.rs @@ -21,7 +21,7 @@ use utils::{ use crate::{ config::{server::Listeners, telemetry::Telemetry}, ipc::{DeliveryEvent, HousekeeperEvent, QueueEvent, ReportingEvent, StateEvent}, - Core, Data, Inner, Ipc, IPC_CHANNEL_BUFFER, + Caches, Core, Data, Inner, Ipc, IPC_CHANNEL_BUFFER, }; use super::{ @@ -334,6 +334,9 @@ impl BootManager { // Parse data let data = Data::parse(&mut config); + // Parse caches + let cache = Caches::parse(&mut config); + // Enable telemetry #[cfg(feature = "enterprise")] telemetry.enable(core.is_enterprise_edition()); @@ -365,6 +368,7 @@ impl BootManager { shared_core: ArcSwap::from_pointee(core), data, ipc, + cache, }); // Parse TCP acceptors diff --git a/crates/directory/src/lib.rs b/crates/directory/src/lib.rs index 230257c0..b3d3693d 100644 --- a/crates/directory/src/lib.rs +++ b/crates/directory/src/lib.rs @@ -269,7 +269,8 @@ pub enum Permission { // WARNING: add new ids at the end (TODO: use static ids) } -pub type Permissions = Bitset<{ Permission::COUNT.div_ceil(std::mem::size_of::()) }>; +pub const PERMISSIONS_BITSET_SIZE: usize = Permission::COUNT.div_ceil(std::mem::size_of::()); +pub type Permissions = Bitset; pub const ROLE_ADMIN: u32 = u32::MAX; pub const ROLE_TENANT_ADMIN: u32 = u32::MAX - 1; diff --git a/crates/imap/src/core/mailbox.rs b/crates/imap/src/core/mailbox.rs index 5f872df6..d874568f 100644 --- a/crates/imap/src/core/mailbox.rs +++ b/crates/imap/src/core/mailbox.rs @@ -25,7 +25,6 @@ use jmap_proto::{ use parking_lot::Mutex; use store::query::log::{Change, Query}; use trc::AddContext; -use utils::lru_cache::LruCached; use super::{Account, MailboxId, MailboxSync, Session, SessionData}; @@ -115,8 +114,8 @@ impl SessionData { if let Some(cached_account) = self .server .inner - .data - .account_cache + .cache + .account .get(&cached_account_id) .and_then(|cached_account| { if cached_account.state_mailbox == state_mailbox @@ -212,7 +211,19 @@ impl SessionData { mailbox_state: AHashMap::with_capacity(mailboxes.len()), state_mailbox, state_email, + obj_size: 0, }; + account.obj_size = (std::mem::size_of::() + + (std::mem::size_of::() * 3) + + account.prefix.as_ref().map_or(0, |p| p.len()) + + account + .mailbox_names + .keys() + .map(|k| k.len() + std::mem::size_of::()) + .sum::() + + (account.mailbox_state.len() + * (std::mem::size_of::() + std::mem::size_of::()))) + as u64; loop { while let Some((mailbox_id, mailbox_parent_id, mailbox)) = iter.next() { @@ -322,8 +333,8 @@ impl SessionData { // Update cache self.server .inner - .data - .account_cache + .cache + .account .insert(cached_account_id, Arc::new(account.clone())); Ok(account) @@ -469,17 +480,11 @@ impl SessionData { } // Update cache - if let Some(cached_account_) = self - .server - .inner - .data - .account_cache - .lock() - .get_mut(&AccountId { - account_id, - primary_id: access_token.primary_id(), - }) - { + let ac_id = AccountId { + account_id, + primary_id: access_token.primary_id(), + }; + if let Some(cached_account_) = self.server.inner.cache.account.get(&ac_id) { if cached_account_.state_mailbox != state_mailbox || cached_account_.state_email != state_email { @@ -493,7 +498,11 @@ impl SessionData { }); cached_account.state_mailbox = state_mailbox; cached_account.state_email = state_email; - *cached_account_ = Arc::new(cached_account); + self.server + .inner + .cache + .account + .insert(ac_id, Arc::new(cached_account)); } } } else { diff --git a/crates/imap/src/core/message.rs b/crates/imap/src/core/message.rs index 0706eb89..a3873091 100644 --- a/crates/imap/src/core/message.rs +++ b/crates/imap/src/core/message.rs @@ -16,7 +16,6 @@ use jmap_proto::{ }; use store::write::assert::HashedValue; use trc::AddContext; -use utils::lru_cache::LruCached; use crate::core::ImapId; @@ -104,7 +103,7 @@ impl SessionData { uid_to_id.insert(uid, message_id); } - Ok(MailboxState { + let mut state = MailboxState { uid_next: uid_max + 1, uid_validity, total_messages: id_to_imap.len(), @@ -113,7 +112,11 @@ impl SessionData { uid_max, modseq, next_state: None, - }) + obj_size: 0, + }; + state.obj_size = state.calculate_weight(); + + Ok(state) } pub async fn synchronize_messages( @@ -150,8 +153,8 @@ impl SessionData { // Update cache self.server .inner - .data - .mailbox_cache + .cache + .mailbox .insert(mailbox.id, Arc::new(new_state.clone())); // Update state diff --git a/crates/imap/src/op/acl.rs b/crates/imap/src/op/acl.rs index f52026d3..f51c6ef0 100644 --- a/crates/imap/src/op/acl.rs +++ b/crates/imap/src/op/acl.rs @@ -368,7 +368,11 @@ impl Session { } // Invalidate ACLs - data.server.inner.data.access_tokens.remove(&acl_account_id); + data.server + .inner + .cache + .access_tokens + .remove(&acl_account_id); trc::event!( Imap(trc::ImapEvent::SetAcl), diff --git a/crates/imap/src/op/select.rs b/crates/imap/src/op/select.rs index 5d6c1853..25a0879e 100644 --- a/crates/imap/src/op/select.rs +++ b/crates/imap/src/op/select.rs @@ -21,7 +21,6 @@ use imap_proto::{ use crate::core::{SavedSearch, SelectedMailbox, Session, State}; use common::listener::SessionStream; use jmap_proto::types::id::Id; -use utils::lru_cache::LruCached; use super::{ImapContext, ToModSeq}; @@ -47,39 +46,41 @@ impl Session { if let Some(mailbox) = data.get_mailbox_by_name(&arguments.mailbox_name) { // Try obtaining the mailbox from the cache - let state = - { - let modseq = data - .get_modseq(mailbox.account_id) - .await - .imap_ctx(&arguments.tag, trc::location!())?; + let state = { + let modseq = data + .get_modseq(mailbox.account_id) + .await + .imap_ctx(&arguments.tag, trc::location!())?; - if let Some(cached_state) = - self.server.inner.data.mailbox_cache.get(&mailbox).and_then( - |cached_state| { - if cached_state.modseq.unwrap_or(0) >= modseq.unwrap_or(0) { - Some(cached_state) - } else { - None - } - }, - ) - { - cached_state.as_ref().clone() - } else { - let new_state = Arc::new( - data.fetch_messages(&mailbox) - .await - .imap_ctx(&arguments.tag, trc::location!())?, - ); - self.server - .inner - .data - .mailbox_cache - .insert(mailbox, new_state.clone()); - new_state.as_ref().clone() - } - }; + if let Some(cached_state) = + self.server + .inner + .cache + .mailbox + .get(&mailbox) + .and_then(|cached_state| { + if cached_state.modseq.unwrap_or(0) >= modseq.unwrap_or(0) { + Some(cached_state) + } else { + None + } + }) + { + cached_state.as_ref().clone() + } else { + let new_state = Arc::new( + data.fetch_messages(&mailbox) + .await + .imap_ctx(&arguments.tag, trc::location!())?, + ); + self.server + .inner + .cache + .mailbox + .insert(mailbox, new_state.clone()); + new_state.as_ref().clone() + } + }; // Synchronize messages let closed_previous = self.state.close_mailbox(); diff --git a/crates/jmap/src/api/management/principal.rs b/crates/jmap/src/api/management/principal.rs index f8d87d4b..9897d93b 100644 --- a/crates/jmap/src/api/management/principal.rs +++ b/crates/jmap/src/api/management/principal.rs @@ -372,17 +372,11 @@ impl PrincipalManager for Server { } } - // Remove entries from cache - self.inner - .data - .http_auth_cache - .retain(|_, id| id.item != account_id); - if matches!(typ, Type::Role | Type::Tenant) { // Update permissions cache - self.inner.data.permissions.clear(); + self.inner.cache.permissions.clear(); self.inner - .data + .cache .permissions_version .fetch_add(1, Ordering::Relaxed); } @@ -419,14 +413,12 @@ impl PrincipalManager for Server { // Validate changes let mut needs_assert = false; - let mut expire_session = false; let mut expire_token = false; let mut is_role_change = false; for change in &changes { match change.field { PrincipalField::Secrets => { - expire_session = true; needs_assert = true; } PrincipalField::Name @@ -528,25 +520,17 @@ impl PrincipalManager for Server { ) .await?; - if expire_session { - // Remove entries from cache - self.inner - .data - .http_auth_cache - .retain(|_, id| id.item != account_id); - } - if is_role_change { // Update permissions cache - self.inner.data.permissions.clear(); + self.inner.cache.permissions.clear(); self.inner - .data + .cache .permissions_version .fetch_add(1, Ordering::Relaxed); } if expire_token { - self.inner.data.access_tokens.remove(&account_id); + self.inner.cache.access_tokens.remove(&account_id); } Ok(JsonResponse::new(json!({ @@ -646,12 +630,6 @@ impl PrincipalManager for Server { .set([("authentication.fallback-admin.secret", password)], true) .await?; - // Remove entries from cache - self.inner - .data - .http_auth_cache - .retain(|_, id| id.item != u32::MAX); - return Ok(JsonResponse::new(json!({ "data": (), })) @@ -713,12 +691,6 @@ impl PrincipalManager for Server { ) .await?; - // Remove entries from cache - self.inner - .data - .http_auth_cache - .retain(|_, id| id.item != access_token.primary_id()); - Ok(JsonResponse::new(json!({ "data": (), })) diff --git a/crates/jmap/src/auth/acl.rs b/crates/jmap/src/auth/acl.rs index e7a40117..08f3e835 100644 --- a/crates/jmap/src/auth/acl.rs +++ b/crates/jmap/src/auth/acl.rs @@ -358,7 +358,7 @@ impl AclMethods for Server { fn refresh_acls(&self, changes: &Object, current: &Option>>) { if let Value::Acl(acl_changes) = changes.get(&Property::Acl) { - let access_tokens = &self.inner.data.access_tokens; + let access_tokens = &self.inner.cache.access_tokens; if let Some(Value::Acl(acl_current)) = current .as_ref() .and_then(|current| current.inner.properties.get(&Property::Acl)) diff --git a/crates/jmap/src/auth/authenticate.rs b/crates/jmap/src/auth/authenticate.rs index c26f071e..5b063ba5 100644 --- a/crates/jmap/src/auth/authenticate.rs +++ b/crates/jmap/src/auth/authenticate.rs @@ -10,7 +10,6 @@ use common::{auth::AuthRequest, listener::limiter::InFlight, Server}; use hyper::header; use mail_parser::decoders::base64::base64_decode; use mail_send::Credentials; -use utils::map::ttl_dashmap::TtlMap; use crate::api::{http::HttpSessionData, HttpRequest}; @@ -36,57 +35,60 @@ impl Authenticator for Server { allow_api_access: bool, ) -> trc::Result<(InFlight, Arc)> { if let Some((mechanism, token)) = req.authorization() { - let access_token = - if let Some(account_id) = self.inner.data.http_auth_cache.get_with_ttl(token) { - self.get_cached_access_token(account_id).await? - } else { - let credentials = if mechanism.eq_ignore_ascii_case("basic") { - // Decode the base64 encoded credentials - decode_plain_auth(token).ok_or_else(|| { - trc::AuthEvent::Error - .into_err() - .details("Failed to decode Basic auth request.") - .id(token.to_string()) - .caused_by(trc::location!()) - })? - } else if mechanism.eq_ignore_ascii_case("bearer") { - // Enforce anonymous rate limit - self.is_http_anonymous_request_allowed(&session.remote_ip) - .await?; - - decode_bearer_token(token, allow_api_access).ok_or_else(|| { - trc::AuthEvent::Error - .into_err() - .details("Failed to decode Bearer token.") - .id(token.to_string()) - .caused_by(trc::location!()) - })? - } else { - // Enforce anonymous rate limit - self.is_http_anonymous_request_allowed(&session.remote_ip) - .await?; - - return Err(trc::AuthEvent::Error + let access_token = if let Some(account_id) = self.inner.cache.http_auth.get(token) { + self.get_cached_access_token(account_id).await? + } else { + let credentials = if mechanism.eq_ignore_ascii_case("basic") { + // Decode the base64 encoded credentials + decode_plain_auth(token).ok_or_else(|| { + trc::AuthEvent::Error .into_err() - .reason("Unsupported authentication mechanism.") - .details(token.to_string()) - .caused_by(trc::location!())); - }; - - // Authenticate - let access_token = self - .authenticate(&AuthRequest::from_credentials( - credentials, - session.session_id, - session.remote_ip, - )) + .details("Failed to decode Basic auth request.") + .id(token.to_string()) + .caused_by(trc::location!()) + })? + } else if mechanism.eq_ignore_ascii_case("bearer") { + // Enforce anonymous rate limit + self.is_http_anonymous_request_allowed(&session.remote_ip) .await?; - // Cache session - self.cache_session(token.to_string(), &access_token); - access_token + decode_bearer_token(token, allow_api_access).ok_or_else(|| { + trc::AuthEvent::Error + .into_err() + .details("Failed to decode Bearer token.") + .id(token.to_string()) + .caused_by(trc::location!()) + })? + } else { + // Enforce anonymous rate limit + self.is_http_anonymous_request_allowed(&session.remote_ip) + .await?; + + return Err(trc::AuthEvent::Error + .into_err() + .reason("Unsupported authentication mechanism.") + .details(token.to_string()) + .caused_by(trc::location!())); }; + // Authenticate + let access_token = self + .authenticate(&AuthRequest::from_credentials( + credentials, + session.session_id, + session.remote_ip, + )) + .await?; + + // Cache session + self.inner.cache.http_auth.insert( + token.to_string(), + access_token.primary_id(), + self.core.jmap.session_cache_ttl, + ); + access_token + }; + // Enforce authenticated rate limit self.is_http_authenticated_request_allowed(&access_token) .await diff --git a/crates/jmap/src/email/cache.rs b/crates/jmap/src/email/cache.rs index 830ee312..8521120b 100644 --- a/crates/jmap/src/email/cache.rs +++ b/crates/jmap/src/email/cache.rs @@ -10,7 +10,6 @@ use common::{Server, Threads}; use jmap_proto::types::{collection::Collection, property::Property}; use std::future::Future; use trc::AddContext; -use utils::lru_cache::LruCached; use crate::JmapMethods; @@ -38,12 +37,8 @@ impl ThreadCache for Server { .caused_by(trc::location!())?; // Lock the cache - let thread_cache = if let Some(thread_cache) = self - .inner - .data - .threads_cache - .get(&account_id) - .and_then(|t| { + let thread_cache = if let Some(thread_cache) = + self.inner.cache.threads.get(&account_id).and_then(|t| { if t.modseq.unwrap_or(0) >= modseq.unwrap_or(0) { Some(t) } else { @@ -66,8 +61,8 @@ impl ThreadCache for Server { modseq, }); self.inner - .data - .threads_cache + .cache + .threads .insert(account_id, thread_cache.clone()); thread_cache }; diff --git a/crates/jmap/src/services/gossip/mod.rs b/crates/jmap/src/services/gossip/mod.rs index 959e1fe2..5135d583 100644 --- a/crates/jmap/src/services/gossip/mod.rs +++ b/crates/jmap/src/services/gossip/mod.rs @@ -111,7 +111,7 @@ impl From<&Gossiper> for PeerStatus { .load(Ordering::Relaxed), gen_permissions: cluster .inner - .data + .cache .permissions_version .load(Ordering::Relaxed), } diff --git a/crates/jmap/src/services/gossip/ping.rs b/crates/jmap/src/services/gossip/ping.rs index b2e67de7..7841e7de 100644 --- a/crates/jmap/src/services/gossip/ping.rs +++ b/crates/jmap/src/services/gossip/ping.rs @@ -182,7 +182,7 @@ impl Gossiper { // Reload settings if update_permissions { - self.inner.data.permissions.clear(); + self.inner.cache.permissions.clear(); } if update_config || update_lists { diff --git a/crates/jmap/src/services/housekeeper.rs b/crates/jmap/src/services/housekeeper.rs index d0af0ef3..bcafad4a 100644 --- a/crates/jmap/src/services/housekeeper.rs +++ b/crates/jmap/src/services/housekeeper.rs @@ -28,7 +28,6 @@ use smtp::reporting::SmtpReporting; use store::{write::now, PurgeStore}; use tokio::sync::mpsc; use trc::{Collector, MetricType, PurgeEvent}; -use utils::map::ttl_dashmap::TtlMap; use crate::{email::delete::EmailDeletion, JmapMethods, LONG_SLUMBER}; @@ -330,13 +329,11 @@ pub fn spawn_housekeeper(inner: Arc, mut rx: mpsc::Receiver for i64 { } } +impl CacheItemWeight for Weights { + fn weight(&self) -> u64 { + std::mem::size_of::() as u64 + } +} + +impl CacheItemWeight for TokenHash { + fn weight(&self) -> u64 { + self.len as u64 + } +} + impl TokenHash { pub fn serialize_index(prefix: u8, account_id: Option) -> Vec { if let Some(account_id) = account_id { diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index e2812782..ecda31f3 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -15,7 +15,6 @@ serde = { version = "1.0", features = ["derive"]} mail-auth = { version = "0.5" } smtp-proto = { version = "0.1" } mail-send = { version = "0.4", default-features = false, features = ["cram-md5", "ring", "tls12"] } -dashmap = "6.0" ahash = { version = "0.8" } chrono = "0.4" rand = "0.8.5" @@ -31,10 +30,10 @@ parking_lot = "0.12" futures = "0.3" regex = "1.7.0" blake3 = "1.3.3" -lru-cache = "0.1.2" http-body-util = "0.1.0" form_urlencoded = "1.1.0" psl = "2" +quick_cache = "0.6.9" [target.'cfg(unix)'.dependencies] privdrop = "0.5.3" diff --git a/crates/utils/src/cache.rs b/crates/utils/src/cache.rs new file mode 100644 index 00000000..31f9640c --- /dev/null +++ b/crates/utils/src/cache.rs @@ -0,0 +1,238 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use std::{ + hash::Hash, + net::IpAddr, + sync::Arc, + time::{Duration, Instant}, +}; + +use quick_cache::{ + sync::{DefaultLifecycle, PlaceholderGuard}, + Equivalent, Weighter, +}; + +use crate::config::Config; + +pub struct Cache( + quick_cache::sync::Cache, +); +pub struct CacheWithTtl( + quick_cache::sync::Cache, CacheItemWeighter>, +); + +#[derive(Clone)] +pub struct TtlEntry { + value: V, + expires: Instant, +} + +impl Cache { + pub fn from_config(config: &mut Config, key: &str) -> Self { + Self::new( + config + .property_or_default((key, "capacity"), "1024") + .unwrap_or(100), + config + .property_or_default((key, "size"), "10485760") + .unwrap_or(10485760), + ) + } + + pub fn new(estimated_items_capacity: usize, weight_capacity: u64) -> Self { + Self(quick_cache::sync::Cache::with_weighter( + estimated_items_capacity, + weight_capacity, + CacheItemWeighter, + )) + } + + #[inline(always)] + pub fn get(&self, key: &Q) -> Option + where + Q: Hash + Equivalent + ?Sized, + { + self.0.get(key) + } + + #[inline(always)] + pub async fn get_value_or_guard_async<'a, Q>( + &'a self, + key: &Q, + ) -> Result< + V, + PlaceholderGuard<'a, K, V, CacheItemWeighter, ahash::RandomState, DefaultLifecycle>, + > + where + Q: Hash + Equivalent + ToOwned + ?Sized, + { + self.0.get_value_or_guard_async(key).await + } + + #[inline(always)] + pub fn insert(&self, key: K, value: V) { + self.0.insert(key, value); + } + + #[inline(always)] + pub fn remove(&self, key: &K) { + self.0.remove(key); + } + + #[inline(always)] + pub fn clear(&self) { + self.0.clear(); + } +} + +impl CacheWithTtl { + pub fn from_config(config: &mut Config, key: &str) -> Self { + Self::new( + config + .property_or_default((key, "capacity"), "1024") + .unwrap_or(100), + config + .property_or_default((key, "size"), "10485760") + .unwrap_or(10485760), + ) + } + + pub fn new(estimated_items_capacity: usize, weight_capacity: u64) -> Self { + Self(quick_cache::sync::Cache::with_weighter( + estimated_items_capacity, + weight_capacity, + CacheItemWeighter, + )) + } + + #[inline(always)] + pub fn get(&self, key: &Q) -> Option + where + Q: Hash + Equivalent + ?Sized, + { + self.0.get(key).and_then(|v| { + if v.expires > Instant::now() { + Some(v.value) + } else { + None + } + }) + } + + #[inline(always)] + pub async fn get_value_or_guard_async<'a, Q>( + &'a self, + key: &Q, + ) -> Result< + V, + PlaceholderGuard< + 'a, + K, + TtlEntry, + CacheItemWeighter, + ahash::RandomState, + DefaultLifecycle>, + >, + > + where + Q: Hash + Equivalent + ToOwned + ?Sized, + { + match self.0.get_value_or_guard_async(key).await { + Ok(value) => { + if value.expires > Instant::now() { + Ok(value.value) + } else { + self.0.remove(key); + self.0.get_value_or_guard_async(key).await.map(|v| v.value) + } + } + Err(err) => Err(err), + } + } + + #[inline(always)] + pub fn insert(&self, key: K, value: V, expires: Duration) { + self.0.insert(key, TtlEntry::new(value, expires)); + } + + #[inline(always)] + pub fn remove(&self, key: &K) { + self.0.remove(key); + } + + #[inline(always)] + pub fn clear(&self) { + self.0.clear(); + } +} + +#[derive(Clone)] +pub struct CacheItemWeighter; + +impl Weighter for CacheItemWeighter { + fn weight(&self, key: &K, val: &V) -> u64 { + key.weight() + val.weight() + } +} + +pub trait CacheItemWeight { + fn weight(&self) -> u64; +} + +impl CacheItemWeight for TtlEntry { + fn weight(&self) -> u64 { + self.value.weight() + 8 + } +} + +impl CacheItemWeight for Option { + fn weight(&self) -> u64 { + match self { + Some(v) => v.weight(), + None => 1, + } + } +} + +impl CacheItemWeight for Arc { + fn weight(&self) -> u64 { + self.as_ref().weight() + } +} + +impl CacheItemWeight for u64 { + fn weight(&self) -> u64 { + std::mem::size_of::() as u64 + } +} + +impl CacheItemWeight for String { + fn weight(&self) -> u64 { + self.len() as u64 + } +} + +impl CacheItemWeight for u32 { + fn weight(&self) -> u64 { + std::mem::size_of::() as u64 + } +} + +impl CacheItemWeight for Vec { + fn weight(&self) -> u64 { + (self.len() * std::mem::size_of::()) as u64 + } +} + +impl TtlEntry { + pub fn new(value: T, expires: Duration) -> Self { + Self { + value, + expires: Instant::now() + expires, + } + } +} diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index 6fc740b9..decb0860 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -6,10 +6,10 @@ use std::sync::Arc; +pub mod cache; pub mod codec; pub mod config; pub mod glob; -pub mod lru_cache; pub mod map; pub mod snowflake; pub mod url_params; diff --git a/crates/utils/src/lru_cache.rs b/crates/utils/src/lru_cache.rs deleted file mode 100644 index 15250404..00000000 --- a/crates/utils/src/lru_cache.rs +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL - */ - -use std::{borrow::Borrow, hash::Hash}; - -use parking_lot::Mutex; - -pub type LruCache = Mutex>; - -pub trait LruCached: Sized { - fn with_capacity(capacity: usize) -> Self; - fn get(&self, name: &Q) -> Option - where - K: Borrow, - Q: Hash + Eq + ?Sized; - fn insert(&self, name: K, value: V) -> Option; -} - -impl LruCached for LruCache { - fn with_capacity(capacity: usize) -> Self { - Mutex::new(lru_cache::LruCache::with_hasher( - capacity, - ahash::RandomState::new(), - )) - } - - fn get(&self, name: &Q) -> Option - where - K: Borrow, - Q: Hash + Eq + ?Sized, - { - self.lock().get_mut(name).map(|entry| entry.clone()) - } - - fn insert(&self, name: K, item: V) -> Option { - self.lock().insert(name, item) - } -} diff --git a/crates/utils/src/map/mod.rs b/crates/utils/src/map/mod.rs index 88094be6..3657e86c 100644 --- a/crates/utils/src/map/mod.rs +++ b/crates/utils/src/map/mod.rs @@ -6,5 +6,4 @@ pub mod bitmap; pub mod mutex_map; -pub mod ttl_dashmap; pub mod vec_map; diff --git a/crates/utils/src/map/ttl_dashmap.rs b/crates/utils/src/map/ttl_dashmap.rs deleted file mode 100644 index 089e01f7..00000000 --- a/crates/utils/src/map/ttl_dashmap.rs +++ /dev/null @@ -1,64 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL - */ - -use std::{borrow::Borrow, hash::Hash, time::Instant}; - -use dashmap::DashMap; - -pub type TtlDashMap = DashMap, ahash::RandomState>; -pub type ADashMap = DashMap; - -#[derive(Debug, Clone)] -pub struct LruItem { - pub item: V, - valid_until: Instant, -} - -pub trait TtlMap: Sized { - fn with_capacity(capacity: usize, shard_amount: usize) -> Self; - fn get_with_ttl(&self, name: &Q) -> Option - where - K: Borrow, - Q: Hash + Eq + ?Sized; - fn insert_with_ttl(&self, name: K, value: V, valid_until: Instant) -> V; - fn cleanup(&self); -} - -impl TtlMap for TtlDashMap { - fn with_capacity(capacity: usize, shard_amount: usize) -> Self { - DashMap::with_capacity_and_hasher_and_shard_amount( - capacity, - ahash::RandomState::new(), - shard_amount, - ) - } - - fn get_with_ttl(&self, name: &Q) -> Option - where - K: Borrow, - Q: Hash + Eq + ?Sized, - { - match self.get(name) { - Some(entry) if entry.valid_until >= Instant::now() => entry.item.clone().into(), - _ => None, - } - } - - fn insert_with_ttl(&self, name: K, item: V, valid_until: Instant) -> V { - self.insert( - name, - LruItem { - item: item.clone(), - valid_until, - }, - ); - item - } - - fn cleanup(&self) { - self.retain(|_, entry| entry.valid_until >= Instant::now()); - } -} diff --git a/tests/src/http_server.rs b/tests/src/http_server.rs index 66dd477c..6b0abecc 100644 --- a/tests/src/http_server.rs +++ b/tests/src/http_server.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use ahash::AHashMap; -use common::{config::server::Listeners, listener::SessionData, Core, Data, Inner}; +use common::{config::server::Listeners, listener::SessionData, Caches, Core, Data, Inner}; use hyper::{body, server::conn::http1, service::service_fn, Method, Uri}; use hyper_util::rt::TokioIo; use jmap::api::{http::fetch_body, HttpResponse}; @@ -69,6 +69,7 @@ pub async fn spawn_mock_http_server( .await .into_shared(), data: Data::parse(&mut settings), + cache: Caches::parse(&mut settings), ..Default::default() }); settings.errors.clear(); diff --git a/tests/src/imap/mod.rs b/tests/src/imap/mod.rs index 3467391a..96c20f95 100644 --- a/tests/src/imap/mod.rs +++ b/tests/src/imap/mod.rs @@ -34,7 +34,7 @@ use common::{ }, core::BuildServer, manager::boot::build_ipc, - Core, Data, Inner, Server, + Caches, Core, Data, Inner, Server, }; use ::store::Stores; @@ -331,12 +331,15 @@ async fn init_imap_tests(store_id: &str, delete_if_exists: bool) -> IMAPTest { let tracers = Telemetry::parse(&mut config, &stores); let core = Core::parse(&mut config, stores, Default::default()).await; let data = Data::parse(&mut config); + let cache = Caches::parse(&mut config); + let store = core.storage.data.clone(); let (ipc, mut ipc_rxs) = build_ipc(); let inner = Arc::new(Inner { shared_core: core.into_shared(), data, ipc, + cache, }); // Parse acceptors diff --git a/tests/src/jmap/auth_acl.rs b/tests/src/jmap/auth_acl.rs index 76fb9e65..c01f2d86 100644 --- a/tests/src/jmap/auth_acl.rs +++ b/tests/src/jmap/auth_acl.rs @@ -670,7 +670,7 @@ pub async fn test(params: &mut JMAPTest) { .add_to_group(name, "sales@example.com") .await; } - server.inner.data.access_tokens.clear(); + server.inner.cache.access_tokens.clear(); john_client.refresh_session().await.unwrap(); jane_client.refresh_session().await.unwrap(); bill_client.refresh_session().await.unwrap(); @@ -770,8 +770,8 @@ pub async fn test(params: &mut JMAPTest) { .data .remove_from_group("jdoe@example.com", "sales@example.com") .await; - server.inner.data.http_auth_cache.clear(); - server.inner.data.access_tokens.clear(); + server.inner.cache.http_auth.clear(); + server.inner.cache.access_tokens.clear(); assert_forbidden( john_client .set_default_account_id(sales_id.to_string()) diff --git a/tests/src/jmap/mod.rs b/tests/src/jmap/mod.rs index a0765e31..bb95b37c 100644 --- a/tests/src/jmap/mod.rs +++ b/tests/src/jmap/mod.rs @@ -4,12 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{ - fmt::Debug, - path::PathBuf, - sync::Arc, - time::{Duration, Instant}, -}; +use std::{fmt::Debug, path::PathBuf, sync::Arc, time::Duration}; use base64::{ engine::general_purpose::{self, STANDARD}, @@ -26,7 +21,7 @@ use common::{ boot::build_ipc, config::{ConfigManager, Patterns}, }, - Core, Data, Inner, Server, + Caches, Core, Data, Inner, Server, }; use enterprise::{insert_test_metrics, EnterpriseCore}; use hyper::{header::AUTHORIZATION, Method}; @@ -46,7 +41,7 @@ use store::{ IterateParams, Stores, ValueKey, SUBSPACE_PROPERTY, }; use tokio::sync::watch; -use utils::{config::Config, map::ttl_dashmap::TtlMap, BlobHash}; +use utils::{config::Config, BlobHash}; use webhooks::{spawn_mock_webhook_endpoint, MockWebhookEndpoint}; use crate::{ @@ -529,23 +524,18 @@ pub async fn emails_purge_tombstoned(server: &Server) { .unwrap(); for account_id in account_ids { - let do_add = server - .inner - .data - .access_tokens - .get_with_ttl(&account_id) - .is_none(); + let do_add = server.inner.cache.access_tokens.get(&account_id).is_none(); if do_add { - server.inner.data.access_tokens.insert_with_ttl( + server.inner.cache.access_tokens.insert( account_id, Arc::new(AccessToken::from_id(account_id)), - Instant::now() + Duration::from_secs(3600), + Duration::from_secs(3600), ); } server.emails_purge_tombstoned(account_id).await.unwrap(); if do_add { - server.inner.data.access_tokens.remove(&account_id); + server.inner.cache.access_tokens.remove(&account_id); } } } @@ -590,12 +580,14 @@ async fn init_jmap_tests(store_id: &str, delete_if_exists: bool) -> JMAPTest { .await .enable_enterprise(); let data = Data::parse(&mut config); + let cache = Caches::parse(&mut config); let store = core.storage.data.clone(); let (ipc, mut ipc_rxs) = build_ipc(); let inner = Arc::new(Inner { shared_core: core.into_shared(), data, ipc, + cache, }); // Parse acceptors diff --git a/tests/src/jmap/push_subscription.rs b/tests/src/jmap/push_subscription.rs index a846a722..8373f7be 100644 --- a/tests/src/jmap/push_subscription.rs +++ b/tests/src/jmap/push_subscription.rs @@ -13,7 +13,7 @@ use std::{ }; use base64::{engine::general_purpose, Engine}; -use common::{config::server::Listeners, listener::SessionData, Core, Data, Inner}; +use common::{config::server::Listeners, listener::SessionData, Caches, Core, Data, Inner}; use ece::EcKeyComponents; use hyper::{body, header::CONTENT_ENCODING, server::conn::http1, service::service_fn, StatusCode}; use hyper_util::rt::TokioIo; @@ -104,6 +104,7 @@ pub async fn test(params: &mut JMAPTest) { .await .into_shared(), data: Data::parse(&mut settings), + cache: Caches::parse(&mut settings), ..Default::default() }); settings.errors.clear(); diff --git a/tests/src/smtp/mod.rs b/tests/src/smtp/mod.rs index 08074ef0..2a593389 100644 --- a/tests/src/smtp/mod.rs +++ b/tests/src/smtp/mod.rs @@ -152,6 +152,7 @@ impl TestSMTP { shared_core: self.server.core.as_ref().clone().into_shared(), data: Default::default(), ipc, + cache: Default::default(), } .into(), ipc_rxs, @@ -179,6 +180,7 @@ impl TestSMTP { shared_core, data, ipc, + cache: Default::default(), } .into(), },