1#[cfg(test)]
2use std::collections::BTreeMap;
3#[cfg(test)]
4use std::sync::Arc;
5
6use anyhow::Result;
7#[cfg(test)]
8use parking_lot::{Mutex, RwLock};
9use rivet_envoy_client::handle::EnvoyHandle;
10
11use crate::types::ListOpts;
12
13#[derive(Clone)]
19pub(crate) struct LegacyActorKv {
20 backend: LegacyActorKvBackend,
21}
22
23#[derive(Clone)]
24enum LegacyActorKvBackend {
25 Envoy {
26 handle: EnvoyHandle,
27 actor_id: String,
28 },
29 #[cfg(test)]
30 InMemory(Arc<TestLegacyActorKv>),
31}
32
33#[cfg(test)]
34struct TestLegacyActorKv {
35 store: RwLock<BTreeMap<Vec<u8>, Vec<u8>>>,
36 delete_range_after_write_lock: Mutex<Option<Arc<dyn Fn() + Send + Sync + 'static>>>,
37 list_limit_cap: Mutex<Option<u32>>,
38 range_start_inclusive: Mutex<bool>,
39}
40
41impl LegacyActorKv {
42 pub(crate) fn new(handle: EnvoyHandle, actor_id: impl Into<String>) -> Self {
43 Self {
44 backend: LegacyActorKvBackend::Envoy {
45 handle,
46 actor_id: actor_id.into(),
47 },
48 }
49 }
50
51 #[cfg(test)]
52 pub(crate) fn new_in_memory() -> Self {
53 Self {
54 backend: LegacyActorKvBackend::InMemory(Arc::new(TestLegacyActorKv {
55 store: RwLock::new(BTreeMap::new()),
56 delete_range_after_write_lock: Mutex::new(None),
57 list_limit_cap: Mutex::new(None),
58 range_start_inclusive: Mutex::new(true),
59 })),
60 }
61 }
62
63 #[cfg(test)]
64 pub(crate) async fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
65 Ok(self.batch_get(&[key]).await?.pop().flatten())
66 }
67
68 pub(crate) async fn put(&self, key: &[u8], value: &[u8]) -> Result<()> {
69 self.batch_put(&[(key, value)]).await
70 }
71
72 pub(crate) async fn batch_get(&self, keys: &[&[u8]]) -> Result<Vec<Option<Vec<u8>>>> {
73 match &self.backend {
74 LegacyActorKvBackend::Envoy { handle, actor_id } => {
75 handle
76 .kv_get(
77 actor_id.clone(),
78 keys.iter().map(|key| key.to_vec()).collect(),
79 )
80 .await
81 }
82 #[cfg(test)]
83 LegacyActorKvBackend::InMemory(store) => {
84 let store = store.store.read();
85 Ok(keys.iter().map(|key| store.get(*key).cloned()).collect())
86 }
87 }
88 }
89
90 pub(crate) async fn batch_put(&self, entries: &[(&[u8], &[u8])]) -> Result<()> {
91 match &self.backend {
92 LegacyActorKvBackend::Envoy { handle, actor_id } => {
93 handle
94 .kv_put(
95 actor_id.clone(),
96 entries
97 .iter()
98 .map(|(key, value)| (key.to_vec(), value.to_vec()))
99 .collect(),
100 )
101 .await
102 }
103 #[cfg(test)]
104 LegacyActorKvBackend::InMemory(store) => {
105 let mut store = store.store.write();
106 for (key, value) in entries {
107 store.insert(key.to_vec(), value.to_vec());
108 }
109 Ok(())
110 }
111 }
112 }
113
114 pub(crate) async fn list_prefix(
115 &self,
116 prefix: &[u8],
117 opts: ListOpts,
118 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
119 match &self.backend {
120 LegacyActorKvBackend::Envoy { handle, actor_id } => {
121 handle
122 .kv_list_prefix(
123 actor_id.clone(),
124 prefix.to_vec(),
125 Some(opts.reverse),
126 opts.limit.map(u64::from),
127 )
128 .await
129 }
130 #[cfg(test)]
131 LegacyActorKvBackend::InMemory(store) => {
132 let mut entries: Vec<_> = store
133 .store
134 .read()
135 .iter()
136 .filter(|(key, _)| key.starts_with(prefix))
137 .map(|(key, value)| (key.clone(), value.clone()))
138 .collect();
139 apply_list_opts(&mut entries, store.capped_opts(opts));
140 Ok(entries)
141 }
142 }
143 }
144
145 pub(crate) async fn list_range(
146 &self,
147 start: &[u8],
148 end: &[u8],
149 opts: ListOpts,
150 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
151 match &self.backend {
152 LegacyActorKvBackend::Envoy { handle, actor_id } => {
153 handle
154 .kv_list_range(
155 actor_id.clone(),
156 start.to_vec(),
157 end.to_vec(),
158 true,
159 Some(opts.reverse),
160 opts.limit.map(u64::from),
161 )
162 .await
163 }
164 #[cfg(test)]
165 LegacyActorKvBackend::InMemory(store) => {
166 let entries_guard = store.store.read();
167 let mut entries: Vec<_> = entries_guard
168 .range(start.to_vec()..end.to_vec())
169 .filter(|(key, _)| {
170 *store.range_start_inclusive.lock() || key.as_slice() > start
171 })
172 .map(|(key, value)| (key.clone(), value.clone()))
173 .collect();
174 apply_list_opts(&mut entries, store.capped_opts(opts));
175 Ok(entries)
176 }
177 }
178 }
179
180 #[cfg(test)]
181 pub(crate) async fn batch_delete(&self, keys: &[&[u8]]) -> Result<()> {
182 match &self.backend {
183 LegacyActorKvBackend::Envoy { handle, actor_id } => {
184 handle
185 .kv_delete(
186 actor_id.clone(),
187 keys.iter().map(|key| key.to_vec()).collect(),
188 )
189 .await
190 }
191 LegacyActorKvBackend::InMemory(store) => {
192 let mut store = store.store.write();
193 for key in keys {
194 store.remove(*key);
195 }
196 Ok(())
197 }
198 }
199 }
200
201 #[cfg(test)]
202 pub(crate) async fn delete_range(&self, start: &[u8], end: &[u8]) -> Result<()> {
203 match &self.backend {
204 LegacyActorKvBackend::Envoy { handle, actor_id } => {
205 handle
206 .kv_delete_range(actor_id.clone(), start.to_vec(), end.to_vec())
207 .await
208 }
209 LegacyActorKvBackend::InMemory(store) => {
210 let mut entries = store.store.write();
211 if let Some(hook) = store.delete_range_after_write_lock.lock().clone() {
212 hook();
213 }
214 entries.retain(|key, _| key.as_slice() < start || key.as_slice() >= end);
215 Ok(())
216 }
217 }
218 }
219
220 #[cfg(test)]
221 pub(crate) fn test_identity(&self) -> usize {
222 match &self.backend {
223 LegacyActorKvBackend::Envoy { handle, .. } => handle.get_envoy_key().as_ptr() as usize,
224 LegacyActorKvBackend::InMemory(store) => Arc::as_ptr(store) as usize,
225 }
226 }
227
228 #[cfg(test)]
229 pub(crate) fn test_set_delete_range_after_write_lock_hook(
230 &self,
231 hook: impl Fn() + Send + Sync + 'static,
232 ) {
233 if let LegacyActorKvBackend::InMemory(store) = &self.backend {
234 *store.delete_range_after_write_lock.lock() = Some(Arc::new(hook));
235 }
236 }
237
238 #[cfg(test)]
239 pub(crate) fn test_set_list_limit_cap(&self, cap: u32) {
240 if let LegacyActorKvBackend::InMemory(store) = &self.backend {
241 *store.list_limit_cap.lock() = Some(cap);
242 }
243 }
244
245 #[cfg(test)]
246 pub(crate) fn test_set_range_start_inclusive(&self, inclusive: bool) {
247 if let LegacyActorKvBackend::InMemory(store) = &self.backend {
248 *store.range_start_inclusive.lock() = inclusive;
249 }
250 }
251}
252
253#[cfg(test)]
254impl TestLegacyActorKv {
255 fn capped_opts(&self, opts: ListOpts) -> ListOpts {
256 let Some(cap) = *self.list_limit_cap.lock() else {
257 return opts;
258 };
259 ListOpts {
260 reverse: opts.reverse,
261 limit: Some(opts.limit.map_or(cap, |limit| limit.min(cap))),
262 }
263 }
264}
265
266#[cfg(test)]
267fn apply_list_opts(entries: &mut Vec<(Vec<u8>, Vec<u8>)>, opts: ListOpts) {
268 if opts.reverse {
269 entries.reverse();
270 }
271 if let Some(limit) = opts.limit {
272 entries.truncate(limit as usize);
273 }
274}
275
276#[cfg(test)]
277pub(crate) type Kv = LegacyActorKv;
278
279#[cfg(test)]
280#[path = "../../tests/kv.rs"]
281pub(crate) mod tests;