1use std::collections::BTreeMap;
2use std::sync::Arc;
3use std::time::Duration;
4
5use crate::time::Instant;
6
7#[cfg(test)]
8use std::sync::atomic::{AtomicUsize, Ordering};
9
10use anyhow::Result;
11#[cfg(test)]
12use parking_lot::Mutex;
13use parking_lot::RwLock;
14use rivet_envoy_client::handle::EnvoyHandle;
15
16use crate::error::ActorRuntime;
17use crate::types::ListOpts;
18
19pub(crate) const APPLY_BATCH_CHUNK_SIZE: usize = 128;
23
24#[derive(Clone)]
25pub struct Kv {
26 backend: KvBackend,
27 actor_id: String,
28}
29
30#[derive(Clone)]
31enum KvBackend {
32 Unconfigured,
33 Envoy(EnvoyHandle),
34 InMemory(Arc<InMemoryKv>),
35}
36
37struct InMemoryKv {
38 store: RwLock<BTreeMap<Vec<u8>, Vec<u8>>>,
41 #[cfg(test)]
42 stats: InMemoryKvStats,
43}
44
45#[cfg(test)]
46#[derive(Clone, Debug, Default, PartialEq, Eq)]
47pub(crate) struct KvApplyBatchSnapshot {
48 pub puts: Vec<(Vec<u8>, Vec<u8>)>,
49 pub deletes: Vec<Vec<u8>>,
50}
51
52#[cfg(test)]
53#[derive(Default)]
54struct InMemoryKvStats {
55 apply_batch_calls: AtomicUsize,
56 batch_get_calls: AtomicUsize,
57 batch_delete_calls: AtomicUsize,
58 last_apply_batch: Mutex<Option<KvApplyBatchSnapshot>>,
60 apply_batch_before_write_lock: Mutex<Option<Arc<dyn Fn() + Send + Sync + 'static>>>,
61 delete_range_after_write_lock: Mutex<Option<Arc<dyn Fn() + Send + Sync + 'static>>>,
62}
63
64impl Kv {
65 pub fn new(handle: EnvoyHandle, actor_id: impl Into<String>) -> Self {
67 Self {
68 backend: KvBackend::Envoy(handle),
69 actor_id: actor_id.into(),
70 }
71 }
72
73 pub fn new_in_memory() -> Self {
74 Self {
75 backend: KvBackend::InMemory(Arc::new(InMemoryKv {
76 store: RwLock::new(BTreeMap::new()),
77 #[cfg(test)]
78 stats: InMemoryKvStats::default(),
79 })),
80 actor_id: String::new(),
81 }
82 }
83
84 pub async fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
85 let mut values = self.batch_get(&[key]).await?;
86 Ok(values.pop().flatten())
87 }
88
89 pub async fn put(&self, key: &[u8], value: &[u8]) -> Result<()> {
90 self.batch_put(&[(key, value)]).await
91 }
92
93 pub async fn delete(&self, key: &[u8]) -> Result<()> {
94 self.batch_delete(&[key]).await
95 }
96
97 pub async fn delete_range(&self, start: &[u8], end: &[u8]) -> Result<()> {
98 let started_at = Instant::now();
99 let result = match &self.backend {
100 KvBackend::Envoy(handle) => {
101 handle
102 .kv_delete_range(self.actor_id.clone(), start.to_vec(), end.to_vec())
103 .await
104 }
105 KvBackend::InMemory(store) => {
106 let start = start.to_vec();
107 let end = end.to_vec();
108 let mut entries = store.store.write();
109
110 #[cfg(test)]
111 {
112 let hook = store.stats.delete_range_after_write_lock.lock().clone();
113 if let Some(hook) = hook {
114 hook();
115 }
116 }
117
118 entries.retain(|key, _| key < &start || key >= &end);
119 Ok(())
120 }
121 KvBackend::Unconfigured => Err(kv_not_configured_error()),
122 };
123 self.log_call("delete_range", None, None, started_at, &result);
124 result
125 }
126
127 pub async fn list_prefix(
128 &self,
129 prefix: &[u8],
130 opts: ListOpts,
131 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
132 let started_at = Instant::now();
133 let result = match &self.backend {
134 KvBackend::Envoy(handle) => {
135 handle
136 .kv_list_prefix(
137 self.actor_id.clone(),
138 prefix.to_vec(),
139 Some(opts.reverse),
140 opts.limit.map(u64::from),
141 )
142 .await
143 }
144 KvBackend::InMemory(entries) => {
145 let mut listed: Vec<_> = entries
146 .store
147 .read()
148 .iter()
149 .filter(|(key, _)| key.starts_with(prefix))
150 .map(|(key, value)| (key.clone(), value.clone()))
151 .collect();
152 apply_list_opts(&mut listed, opts);
153 Ok(listed)
154 }
155 KvBackend::Unconfigured => Err(kv_not_configured_error()),
156 };
157 let result_count = result.as_ref().ok().map(Vec::len);
158 self.log_call("list_prefix", None, result_count, started_at, &result);
159 result
160 }
161
162 pub async fn list_range(
163 &self,
164 start: &[u8],
165 end: &[u8],
166 opts: ListOpts,
167 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
168 match &self.backend {
169 KvBackend::Envoy(handle) => {
170 handle
171 .kv_list_range(
172 self.actor_id.clone(),
173 start.to_vec(),
174 end.to_vec(),
175 true,
176 Some(opts.reverse),
177 opts.limit.map(u64::from),
178 )
179 .await
180 }
181 KvBackend::InMemory(entries) => {
182 let mut listed: Vec<_> = entries
183 .store
184 .read()
185 .range(start.to_vec()..end.to_vec())
186 .map(|(key, value)| (key.clone(), value.clone()))
187 .collect();
188 apply_list_opts(&mut listed, opts);
189 Ok(listed)
190 }
191 KvBackend::Unconfigured => Err(kv_not_configured_error()),
192 }
193 }
194
195 pub async fn batch_get(&self, keys: &[&[u8]]) -> Result<Vec<Option<Vec<u8>>>> {
196 let started_at = Instant::now();
197 let result = match &self.backend {
198 KvBackend::Envoy(handle) => {
199 handle
200 .kv_get(
201 self.actor_id.clone(),
202 keys.iter().map(|key| key.to_vec()).collect(),
203 )
204 .await
205 }
206 KvBackend::InMemory(entries) => {
207 #[cfg(test)]
208 entries.stats.batch_get_calls.fetch_add(1, Ordering::SeqCst);
209 let entries = entries.store.read();
210 Ok(keys.iter().map(|key| entries.get(*key).cloned()).collect())
211 }
212 KvBackend::Unconfigured => Err(kv_not_configured_error()),
213 };
214 self.log_call("batch_get", Some(keys.len()), None, started_at, &result);
215 result
216 }
217
218 pub async fn batch_put(&self, entries: &[(&[u8], &[u8])]) -> Result<()> {
219 let started_at = Instant::now();
220 let result = match &self.backend {
221 KvBackend::Envoy(handle) => {
222 handle
223 .kv_put(
224 self.actor_id.clone(),
225 entries
226 .iter()
227 .map(|(key, value)| (key.to_vec(), value.to_vec()))
228 .collect(),
229 )
230 .await
231 }
232 KvBackend::InMemory(store) => {
233 let mut store = store.store.write();
234 for (key, value) in entries {
235 store.insert(key.to_vec(), value.to_vec());
236 }
237 Ok(())
238 }
239 KvBackend::Unconfigured => Err(kv_not_configured_error()),
240 };
241 self.log_call("batch_put", Some(entries.len()), None, started_at, &result);
242 result
243 }
244
245 pub async fn apply_batch(
246 &self,
247 puts: &[(Vec<u8>, Vec<u8>)],
248 deletes: &[Vec<u8>],
249 ) -> Result<()> {
250 match &self.backend {
251 KvBackend::Envoy(_) => {
252 if !puts.is_empty() {
253 let put_refs: Vec<(&[u8], &[u8])> = puts
254 .iter()
255 .map(|(key, value)| (key.as_slice(), value.as_slice()))
256 .collect();
257 self.batch_put(&put_refs).await?;
258 }
259
260 if !deletes.is_empty() {
261 let delete_refs: Vec<&[u8]> = deletes.iter().map(Vec::as_slice).collect();
262 self.batch_delete(&delete_refs).await?;
263 }
264
265 Ok(())
266 }
267 KvBackend::InMemory(store) => {
268 #[cfg(test)]
269 {
270 store.stats.apply_batch_calls.fetch_add(1, Ordering::SeqCst);
271 *store.stats.last_apply_batch.lock() = Some(KvApplyBatchSnapshot {
272 puts: puts.to_vec(),
273 deletes: deletes.to_vec(),
274 });
275 let hook = store.stats.apply_batch_before_write_lock.lock().clone();
276 if let Some(hook) = hook {
277 hook();
278 }
279 }
280 let mut store = store.store.write();
281 for key in deletes {
282 store.remove(key);
283 }
284 for (key, value) in puts {
285 store.insert(key.clone(), value.clone());
286 }
287 Ok(())
288 }
289 KvBackend::Unconfigured => Err(kv_not_configured_error()),
290 }
291 }
292
293 pub async fn batch_delete(&self, keys: &[&[u8]]) -> Result<()> {
294 let started_at = Instant::now();
295 let result = match &self.backend {
296 KvBackend::Envoy(handle) => {
297 handle
298 .kv_delete(
299 self.actor_id.clone(),
300 keys.iter().map(|key| key.to_vec()).collect(),
301 )
302 .await
303 }
304 KvBackend::InMemory(entries) => {
305 #[cfg(test)]
306 entries
307 .stats
308 .batch_delete_calls
309 .fetch_add(1, Ordering::SeqCst);
310 let mut entries = entries.store.write();
311 for key in keys {
312 entries.remove(*key);
313 }
314 Ok(())
315 }
316 KvBackend::Unconfigured => Err(kv_not_configured_error()),
317 };
318 self.log_call("delete", Some(keys.len()), None, started_at, &result);
319 result
320 }
321
322 fn backend_label(&self) -> &'static str {
323 match &self.backend {
324 KvBackend::Unconfigured => "unconfigured",
325 KvBackend::Envoy(_) => "envoy",
326 KvBackend::InMemory(_) => "in_memory",
327 }
328 }
329
330 fn log_call<T>(
331 &self,
332 operation: &'static str,
333 key_count: Option<usize>,
334 result_count: Option<usize>,
335 started_at: Instant,
336 result: &Result<T>,
337 ) {
338 let elapsed_us = duration_micros(started_at.elapsed());
339 match result {
340 Ok(_) => {
341 tracing::debug!(
342 actor_id = %self.actor_id,
343 backend = self.backend_label(),
344 operation,
345 key_count = ?key_count,
346 result_count = ?result_count,
347 elapsed_us,
348 outcome = "ok",
349 "kv call completed"
350 );
351 }
352 Err(error) => {
353 tracing::debug!(
354 actor_id = %self.actor_id,
355 backend = self.backend_label(),
356 operation,
357 key_count = ?key_count,
358 result_count = ?result_count,
359 elapsed_us,
360 outcome = "error",
361 error = %error,
362 "kv call completed"
363 );
364 }
365 }
366 }
367}
368
369fn kv_not_configured_error() -> anyhow::Error {
370 ActorRuntime::NotConfigured {
371 component: "kv handle".to_owned(),
372 }
373 .build()
374}
375
376fn duration_micros(duration: Duration) -> u64 {
377 duration.as_micros().try_into().unwrap_or(u64::MAX)
378}
379
380impl std::fmt::Debug for Kv {
381 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
382 f.debug_struct("Kv")
383 .field(
384 "configured",
385 &!matches!(self.backend, KvBackend::Unconfigured),
386 )
387 .field("in_memory", &matches!(self.backend, KvBackend::InMemory(_)))
388 .field("actor_id", &self.actor_id)
389 .finish()
390 }
391}
392
393impl Default for Kv {
394 fn default() -> Self {
395 Self {
396 backend: KvBackend::Unconfigured,
397 actor_id: String::new(),
398 }
399 }
400}
401
402#[cfg(test)]
403impl Kv {
404 pub(crate) fn test_apply_batch_call_count(&self) -> usize {
405 match &self.backend {
406 KvBackend::InMemory(store) => store.stats.apply_batch_calls.load(Ordering::SeqCst),
407 _ => 0,
408 }
409 }
410
411 pub(crate) fn test_batch_delete_call_count(&self) -> usize {
412 match &self.backend {
413 KvBackend::InMemory(store) => store.stats.batch_delete_calls.load(Ordering::SeqCst),
414 _ => 0,
415 }
416 }
417
418 pub(crate) fn test_batch_get_call_count(&self) -> usize {
419 match &self.backend {
420 KvBackend::InMemory(store) => store.stats.batch_get_calls.load(Ordering::SeqCst),
421 _ => 0,
422 }
423 }
424
425 pub(crate) fn test_last_apply_batch(&self) -> Option<KvApplyBatchSnapshot> {
426 match &self.backend {
427 KvBackend::InMemory(store) => store.stats.last_apply_batch.lock().clone(),
428 _ => None,
429 }
430 }
431
432 pub(crate) fn test_set_delete_range_after_write_lock_hook(
433 &self,
434 hook: impl Fn() + Send + Sync + 'static,
435 ) {
436 if let KvBackend::InMemory(store) = &self.backend {
437 *store.stats.delete_range_after_write_lock.lock() = Some(Arc::new(hook));
438 }
439 }
440
441 pub(crate) fn test_set_apply_batch_before_write_lock_hook(
442 &self,
443 hook: impl Fn() + Send + Sync + 'static,
444 ) {
445 if let KvBackend::InMemory(store) = &self.backend {
446 *store.stats.apply_batch_before_write_lock.lock() = Some(Arc::new(hook));
447 }
448 }
449}
450
451fn apply_list_opts(entries: &mut Vec<(Vec<u8>, Vec<u8>)>, opts: ListOpts) {
452 if opts.reverse {
453 entries.reverse();
454 }
455 if let Some(limit) = opts.limit {
456 entries.truncate(limit as usize);
457 }
458}
459
460#[cfg(test)]
462#[path = "../../tests/kv.rs"]
463pub(crate) mod tests;