diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 01:10:40 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 01:10:40 +0100 |
| commit | 0c5d2791ab8d310bc201a3b0212c1004343abf82 (patch) | |
| tree | 5aac11e9377995caf83b2174a318ed74437af6b2 | |
| parent | events: never join or move voice just to play TTS (diff) | |
| download | senator-sperma-0c5d2791ab8d310bc201a3b0212c1004343abf82.tar.gz senator-sperma-0c5d2791ab8d310bc201a3b0212c1004343abf82.zip | |
autocomplete: bound the in-memory caches
| -rw-r--r-- | src/main.rs | 30 |
1 files changed, 24 insertions, 6 deletions
diff --git a/src/main.rs b/src/main.rs index 8d11d76..56f81cb 100644 --- a/src/main.rs +++ b/src/main.rs | |||
| @@ -25,6 +25,10 @@ use database::DbPool; | |||
| 25 | /// How long a search cache entry stays fresh (10 seconds). | 25 | /// How long a search cache entry stays fresh (10 seconds). |
| 26 | const AC_SEARCH_CACHE_TTL: Duration = Duration::from_secs(10); | 26 | const AC_SEARCH_CACHE_TTL: Duration = Duration::from_secs(10); |
| 27 | 27 | ||
| 28 | /// Hard caps so autocomplete traffic cannot grow memory without bound. | ||
| 29 | const AC_CACHE_MAX_ENTRIES: usize = 2_000; | ||
| 30 | const AC_SEARCH_CACHE_MAX_ENTRIES: usize = 256; | ||
| 31 | |||
| 28 | /// Per-user autocomplete stash: (UserId, filename) → SearchHit. | 32 | /// Per-user autocomplete stash: (UserId, filename) → SearchHit. |
| 29 | type AcCache = tokio::sync::Mutex<HashMap<(serenity::UserId, String), copyparty::SearchHit>>; | 33 | type AcCache = tokio::sync::Mutex<HashMap<(serenity::UserId, String), copyparty::SearchHit>>; |
| 30 | 34 | ||
| @@ -70,7 +74,13 @@ impl Data { | |||
| 70 | key: String, | 74 | key: String, |
| 71 | hit: copyparty::SearchHit, | 75 | hit: copyparty::SearchHit, |
| 72 | ) { | 76 | ) { |
| 73 | self.ac_cache.lock().await.insert((user_id, key), hit); | 77 | let mut cache = self.ac_cache.lock().await; |
| 78 | if cache.len() >= AC_CACHE_MAX_ENTRIES { | ||
| 79 | // Entries are only useful for the command invocation that | ||
| 80 | // triggered the autocomplete, so dropping all of them is safe. | ||
| 81 | cache.clear(); | ||
| 82 | } | ||
| 83 | cache.insert((user_id, key), hit); | ||
| 74 | } | 84 | } |
| 75 | 85 | ||
| 76 | pub async fn ac_lookup( | 86 | pub async fn ac_lookup( |
| @@ -88,9 +98,10 @@ impl Data { | |||
| 88 | /// Look up search cache for an autocomplete query. Returns `None` if | 98 | /// Look up search cache for an autocomplete query. Returns `None` if |
| 89 | /// uncached or stale. | 99 | /// uncached or stale. |
| 90 | pub async fn ac_search_cache_lookup(&self, query: &str) -> Option<Vec<copyparty::SearchHit>> { | 100 | pub async fn ac_search_cache_lookup(&self, query: &str) -> Option<Vec<copyparty::SearchHit>> { |
| 91 | let cache = self.ac_search_cache.lock().await; | 101 | let mut cache = self.ac_search_cache.lock().await; |
| 92 | let (hits, cached_at) = cache.get(query)?; | 102 | let (hits, cached_at) = cache.get(query)?; |
| 93 | if cached_at.elapsed() > AC_SEARCH_CACHE_TTL { | 103 | if cached_at.elapsed() > AC_SEARCH_CACHE_TTL { |
| 104 | cache.remove(query); | ||
| 94 | None | 105 | None |
| 95 | } else { | 106 | } else { |
| 96 | Some(hits.clone()) | 107 | Some(hits.clone()) |
| @@ -99,10 +110,17 @@ impl Data { | |||
| 99 | 110 | ||
| 100 | /// Store search results into the shared cache. | 111 | /// Store search results into the shared cache. |
| 101 | pub async fn ac_search_cache_store(&self, query: String, hits: Vec<copyparty::SearchHit>) { | 112 | pub async fn ac_search_cache_store(&self, query: String, hits: Vec<copyparty::SearchHit>) { |
| 102 | self.ac_search_cache | 113 | let mut cache = self.ac_search_cache.lock().await; |
| 103 | .lock() | 114 | cache.retain(|_, (_, cached_at)| cached_at.elapsed() <= AC_SEARCH_CACHE_TTL); |
| 104 | .await | 115 | if cache.len() >= AC_SEARCH_CACHE_MAX_ENTRIES |
| 105 | .insert(query, (hits, Instant::now())); | 116 | && let Some(oldest) = cache |
| 117 | .iter() | ||
| 118 | .min_by_key(|(_, (_, cached_at))| *cached_at) | ||
| 119 | .map(|(query, _)| query.clone()) | ||
| 120 | { | ||
| 121 | cache.remove(&oldest); | ||
| 122 | } | ||
| 123 | cache.insert(query, (hits, Instant::now())); | ||
| 106 | } | 124 | } |
| 107 | } | 125 | } |
| 108 | 126 | ||
