1use std::{sync::Arc, time::Duration};
4
5use arc_swap::ArcSwapAny;
6use async_trait::async_trait;
7use bytes::Bytes;
8
9use crate::cache_topology::CacheTopology;
10use crate::cacheservice::CacheNamespaceConf;
11use crate::service::Cacheable;
12use crate::value::Value;
13use crate::{Expiration, Protocol, Result};
14#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct CacheEntry {
21 pub data: Bytes,
23 pub flags: Option<u32>,
25}
26
27impl From<Value> for CacheEntry {
28 fn from(value: Value) -> Self {
29 let flags = value.flags();
30 Self {
31 data: value.into_bytes(),
32 flags: (flags != 0).then_some(flags),
33 }
34 }
35}
36
37#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
39pub struct SetOptions {
40 pub expiration: Option<Expiration>,
42 pub flags: Option<u32>,
44}
45
46impl SetOptions {
47 pub fn with_expiration(mut self, expiration: Expiration) -> Self {
49 self.expiration = Some(expiration);
50 self
51 }
52
53 pub fn with_flags(mut self, flags: u32) -> Self {
55 self.flags = Some(flags);
56 self
57 }
58}
59
60#[async_trait]
62pub trait Memcache: Send + Sync {
63 async fn get(&self, key: &str) -> Result<Option<CacheEntry>>;
65
66 async fn set(&self, key: &str, value: Bytes) -> Result<bool> {
69 self.set_with(key, value, SetOptions::default()).await
70 }
71
72 async fn set_with(&self, key: &str, value: Bytes, options: SetOptions) -> Result<bool>;
74}
75
76#[async_trait]
82pub trait CacheServiceConfigSource: Send + Sync {
83 async fn load(&self) -> Result<CacheNamespaceConf>;
85
86 async fn subscribe(
94 &self,
95 on_update: Arc<dyn Fn(CacheNamespaceConf) + Send + Sync>,
96 ) -> Result<SubscriptionHandle>;
97}
98
99pub struct SubscriptionHandle {
102 cancel: Option<Box<dyn FnOnce() + Send + Sync>>,
103}
104
105impl std::fmt::Debug for SubscriptionHandle {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 f.debug_struct("SubscriptionHandle").finish_non_exhaustive()
108 }
109}
110
111impl SubscriptionHandle {
112 pub fn new(cancel: impl FnOnce() + Send + Sync + 'static) -> Self {
114 Self {
115 cancel: Some(Box::new(cancel)),
116 }
117 }
118
119 pub fn empty() -> Self {
121 Self { cancel: None }
122 }
123}
124
125impl Drop for SubscriptionHandle {
126 fn drop(&mut self) {
127 if let Some(cancel) = self.cancel.take() {
128 cancel();
129 }
130 }
131}
132
133#[derive(Clone, Copy, Debug, PartialEq, Eq)]
135pub struct CacheServiceOptions {
136 pub default_expiration: Expiration,
138 pub protocol: Protocol,
141 pub master_timeout: Duration,
143 pub slave_timeout: Duration,
145 pub connect_timeout: Duration,
147 pub replica_quota: Duration,
149 pub failure_penalty: Duration,
151 pub update_master_l1: bool,
153}
154
155impl Default for CacheServiceOptions {
156 fn default() -> Self {
157 Self {
158 default_expiration: Expiration::default(),
159 protocol: Protocol::Text,
160 master_timeout: Duration::from_millis(100),
161 slave_timeout: Duration::from_millis(100),
162 connect_timeout: Duration::from_secs(2),
163 replica_quota: Duration::from_secs(2),
164 failure_penalty: Duration::from_millis(500),
165 update_master_l1: true,
166 }
167 }
168}
169
170impl CacheServiceOptions {
171 pub fn new(default_expiration: Expiration) -> Self {
173 Self {
174 default_expiration,
175 ..Self::default()
176 }
177 }
178
179 #[must_use]
181 pub fn with_protocol(mut self, protocol: Protocol) -> Self {
182 self.protocol = protocol;
183 self
184 }
185
186 #[must_use]
187 pub fn with_timeout(mut self, timeout: Duration) -> Self {
188 self.master_timeout = timeout;
189 self.slave_timeout = timeout;
190 self
191 }
192
193 #[must_use]
194 pub fn with_master_timeout(mut self, timeout: Duration) -> Self {
195 self.master_timeout = timeout;
196 self
197 }
198
199 #[must_use]
200 pub fn with_slave_timeout(mut self, timeout: Duration) -> Self {
201 self.slave_timeout = timeout;
202 self
203 }
204
205 #[must_use]
206 pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
207 self.connect_timeout = timeout;
208 self
209 }
210
211 #[must_use]
212 pub fn with_update_master_l1(mut self, enabled: bool) -> Self {
213 self.update_master_l1 = enabled;
214 self
215 }
216}
217
218#[cfg_attr(test, allow(clippy::large_enum_variant))]
222pub(crate) enum BackendSlot {
223 Net(CacheTopology),
224 #[cfg(test)]
225 Test(Arc<dyn Cacheable>),
226}
227
228impl BackendSlot {
229 async fn get(&self, key: &str) -> Result<Option<Value>> {
230 match self {
231 Self::Net(topology) => topology.get(key).await,
232 #[cfg(test)]
233 Self::Test(backend) => backend.get(key).await,
234 }
235 }
236
237 async fn set(&self, key: &str, value: Value, expiration: Expiration) -> Result<bool> {
238 match self {
239 Self::Net(topology) => topology.set(key, value, expiration).await,
240 #[cfg(test)]
241 Self::Test(backend) => backend.set(key, value, expiration).await,
242 }
243 }
244
245 pub(crate) fn topology(&self) -> Option<&CacheTopology> {
246 match self {
247 Self::Net(topology) => Some(topology),
248 #[cfg(test)]
249 Self::Test(_) => None,
250 }
251 }
252}
253
254#[derive(Clone)]
265pub struct CacheService {
266 backend: Arc<ArcSwapAny<Arc<BackendSlot>>>,
267 options: CacheServiceOptions,
268 _hold: Option<Arc<dyn std::fmt::Debug + Send + Sync>>,
272}
273
274impl CacheService {
275 pub async fn single(endpoint: impl Into<String>) -> Result<Self> {
280 Self::single_with_options(endpoint, CacheServiceOptions::default()).await
281 }
282
283 pub async fn single_with_options(
285 endpoint: impl Into<String>,
286 options: CacheServiceOptions,
287 ) -> Result<Self> {
288 Self::new(CacheNamespaceConf::single_master(endpoint.into()), options).await
289 }
290
291 pub async fn new(conf: CacheNamespaceConf, options: CacheServiceOptions) -> Result<Self> {
295 let backend = Self::build_backend(&conf, options, None)?;
296 Ok(Self {
297 backend: Arc::new(ArcSwapAny::new(Arc::new(backend))),
298 options,
299 _hold: None,
300 })
301 }
302
303 pub async fn new_live(
309 source: Arc<dyn CacheServiceConfigSource>,
310 options: CacheServiceOptions,
311 ) -> Result<Self> {
312 let initial = source.load().await?;
313 let backend = Self::build_backend(&initial, options, None)?;
314 let backend_slot = Arc::new(ArcSwapAny::new(Arc::new(backend)));
315
316 let inner =
317 crate::service::live::CacheServiceInner::new(initial, backend_slot.clone(), options);
318 let inner_for_cb = inner.clone();
319 let on_update: Arc<dyn Fn(CacheNamespaceConf) + Send + Sync> = Arc::new(move |conf| {
320 if let Err(error) = inner_for_cb.apply(conf) {
321 tracing::warn!(
322 %error,
323 "cache-service config update failed; retaining old backend"
324 );
325 }
326 });
327 let handle = source.subscribe(on_update).await?;
328
329 Ok(Self {
336 backend: backend_slot,
337 options,
338 _hold: Some(Arc::new(LiveHold {
339 _source: source,
340 _handle: handle,
341 _inner: inner,
342 })),
343 })
344 }
345
346 pub(crate) fn build_backend(
347 conf: &CacheNamespaceConf,
348 options: CacheServiceOptions,
349 previous: Option<&CacheTopology>,
350 ) -> Result<BackendSlot> {
351 Ok(BackendSlot::Net(CacheTopology::from_namespace_conf(
352 conf, options, previous,
353 )?))
354 }
355
356 fn backend(&self) -> Arc<BackendSlot> {
358 self.backend.load_full()
359 }
360
361 #[cfg(test)]
364 pub(crate) fn from_backend(backend: Arc<dyn Cacheable>, options: CacheServiceOptions) -> Self {
365 Self {
366 backend: Arc::new(ArcSwapAny::new(Arc::new(BackendSlot::Test(backend)))),
367 options,
368 _hold: None,
369 }
370 }
371}
372
373struct LiveHold {
376 _source: Arc<dyn CacheServiceConfigSource>,
377 _handle: SubscriptionHandle,
378 _inner: Arc<crate::service::live::CacheServiceInner>,
379}
380
381impl std::fmt::Debug for LiveHold {
382 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
383 f.debug_struct("LiveHold").finish_non_exhaustive()
384 }
385}
386
387#[async_trait]
388impl Memcache for CacheService {
389 async fn get(&self, key: &str) -> Result<Option<CacheEntry>> {
390 self.backend()
391 .get(key)
392 .await
393 .map(|value| value.map(CacheEntry::from))
394 }
395
396 async fn set_with(&self, key: &str, value: Bytes, options: SetOptions) -> Result<bool> {
397 let expiration = options
398 .expiration
399 .unwrap_or(self.options.default_expiration);
400 let value = Value::new(value, options.flags.unwrap_or_default());
401 self.backend().set(key, value, expiration).await
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use std::collections::HashMap;
408 use std::sync::Mutex;
409
410 use crate::value::CasValue;
411
412 use super::*;
413
414 #[test]
415 fn cache_service_options_default_to_text_protocol() {
416 assert_eq!(CacheServiceOptions::default().protocol, Protocol::Text);
417 }
418
419 #[test]
420 fn cache_service_options_can_select_binary_protocol() {
421 let options = CacheServiceOptions::default().with_protocol(Protocol::Binary);
422
423 assert_eq!(options.protocol, Protocol::Binary);
424 }
425
426 #[derive(Default)]
427 struct RecordingBackend {
428 get_result: Mutex<Option<Value>>,
429 set_call: Mutex<Option<(String, Value, Expiration)>>,
430 }
431
432 #[async_trait]
433 impl Cacheable for RecordingBackend {
434 async fn get(&self, _key: &str) -> Result<Option<Value>> {
435 Ok(self.get_result.lock().unwrap().clone())
436 }
437
438 async fn get_multi(&self, _keys: &[&str]) -> Result<HashMap<String, Value>> {
439 unreachable!("the public API does not expose get_multi")
440 }
441
442 async fn set(&self, key: &str, value: Value, expire: Expiration) -> Result<bool> {
443 *self.set_call.lock().unwrap() = Some((key.to_owned(), value, expire));
444 Ok(true)
445 }
446
447 async fn set_with_noreply(
448 &self,
449 _key: &str,
450 _value: Value,
451 _expire: Expiration,
452 ) -> Result<()> {
453 unreachable!("the public API does not expose set_with_noreply")
454 }
455
456 async fn add(&self, _key: &str, _value: Value, _expire: Expiration) -> Result<bool> {
457 unreachable!("the public API does not expose add")
458 }
459
460 async fn get_cas(&self, _key: &str) -> Result<Option<CasValue>> {
461 unreachable!("the public API does not expose get_cas")
462 }
463
464 async fn cas(&self, _key: &str, _value: &CasValue, _expire: Expiration) -> Result<bool> {
465 unreachable!("the public API does not expose cas")
466 }
467
468 async fn delete(&self, _key: &str) -> Result<bool> {
469 unreachable!("the public API does not expose delete")
470 }
471
472 async fn delete_with_noreply(&self, _key: &str) -> Result<()> {
473 unreachable!("the public API does not expose delete_with_noreply")
474 }
475 }
476
477 #[tokio::test]
478 async fn set_uses_instance_expiration_and_zero_flags_by_default() {
479 let backend = Arc::new(RecordingBackend::default());
480 let cache = CacheService::from_backend(
481 backend.clone(),
482 CacheServiceOptions::new(Expiration::Seconds(90)),
483 );
484
485 assert!(
486 cache
487 .set("user:1", Bytes::from_static(b"value"))
488 .await
489 .unwrap()
490 );
491
492 let call = backend.set_call.lock().unwrap().clone().unwrap();
493 assert_eq!(call.0, "user:1");
494 assert_eq!(call.1.as_bytes(), b"value");
495 assert_eq!(call.1.flags(), 0);
496 assert_eq!(call.2, Expiration::Seconds(90));
497 }
498
499 #[tokio::test]
500 async fn set_with_overrides_expiration_and_flags() {
501 let backend = Arc::new(RecordingBackend::default());
502 let cache = CacheService::from_backend(
503 backend.clone(),
504 CacheServiceOptions::new(Expiration::Seconds(90)),
505 );
506 let options = SetOptions::default()
507 .with_expiration(Expiration::Seconds(5))
508 .with_flags(32);
509
510 cache
511 .set_with("user:2", Bytes::from_static(b"value"), options)
512 .await
513 .unwrap();
514
515 let call = backend.set_call.lock().unwrap().clone().unwrap();
516 assert_eq!(call.1.flags(), 32);
517 assert_eq!(call.2, Expiration::Seconds(5));
518 }
519
520 #[tokio::test]
521 async fn get_maps_wire_zero_flag_to_none_without_copying_data() {
522 let backend = Arc::new(RecordingBackend::default());
523 *backend.get_result.lock().unwrap() = Some(Value::new(Bytes::from_static(b"value"), 0));
524 let cache = CacheService::from_backend(backend, CacheServiceOptions::default());
525
526 let entry = cache.get("user:1").await.unwrap().unwrap();
527
528 assert_eq!(entry.data, Bytes::from_static(b"value"));
529 assert_eq!(entry.flags, None);
530 }
531
532 #[tokio::test]
533 async fn public_contract_is_object_safe() {
534 let backend = Arc::new(RecordingBackend::default());
535 *backend.get_result.lock().unwrap() = Some(Value::new(Bytes::new(), 7));
536 let cache: Arc<dyn Memcache> = Arc::new(CacheService::from_backend(
537 backend,
538 CacheServiceOptions::default(),
539 ));
540
541 assert_eq!(cache.get("key").await.unwrap().unwrap().flags, Some(7));
542 }
543}