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, Debug, PartialEq, Eq)]
135pub struct CacheServiceOptions {
136 pub resource_name: Option<Arc<str>>,
138 pub default_expiration: Expiration,
140 pub protocol: Protocol,
143 pub master_timeout: Duration,
145 pub slave_timeout: Duration,
147 pub connect_timeout: Duration,
149 pub replica_quota: Duration,
151 pub failure_penalty: Duration,
153 pub update_master_l1: bool,
155}
156
157impl Default for CacheServiceOptions {
158 fn default() -> Self {
159 Self {
160 resource_name: None,
161 default_expiration: Expiration::default(),
162 protocol: Protocol::Text,
163 master_timeout: Duration::from_millis(100),
164 slave_timeout: Duration::from_millis(100),
165 connect_timeout: Duration::from_secs(2),
166 replica_quota: Duration::from_secs(2),
167 failure_penalty: Duration::from_millis(500),
168 update_master_l1: true,
169 }
170 }
171}
172
173impl CacheServiceOptions {
174 #[must_use]
175 pub fn with_resource_name(mut self, name: impl Into<Arc<str>>) -> Self {
176 self.resource_name = Some(name.into());
177 self
178 }
179
180 pub fn new(default_expiration: Expiration) -> Self {
182 Self {
183 default_expiration,
184 ..Self::default()
185 }
186 }
187
188 #[must_use]
190 pub fn with_protocol(mut self, protocol: Protocol) -> Self {
191 self.protocol = protocol;
192 self
193 }
194
195 #[must_use]
196 pub fn with_timeout(mut self, timeout: Duration) -> Self {
197 self.master_timeout = timeout;
198 self.slave_timeout = timeout;
199 self
200 }
201
202 #[must_use]
203 pub fn with_master_timeout(mut self, timeout: Duration) -> Self {
204 self.master_timeout = timeout;
205 self
206 }
207
208 #[must_use]
209 pub fn with_slave_timeout(mut self, timeout: Duration) -> Self {
210 self.slave_timeout = timeout;
211 self
212 }
213
214 #[must_use]
215 pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
216 self.connect_timeout = timeout;
217 self
218 }
219
220 #[must_use]
221 pub fn with_update_master_l1(mut self, enabled: bool) -> Self {
222 self.update_master_l1 = enabled;
223 self
224 }
225}
226
227#[cfg_attr(test, allow(clippy::large_enum_variant))]
231pub(crate) enum BackendSlot {
232 Net(CacheTopology),
233 #[cfg(test)]
234 Test(Arc<dyn Cacheable>),
235}
236
237impl BackendSlot {
238 async fn get(&self, key: &str) -> Result<Option<Value>> {
239 match self {
240 Self::Net(topology) => topology.get(key).await,
241 #[cfg(test)]
242 Self::Test(backend) => backend.get(key).await,
243 }
244 }
245
246 async fn set(&self, key: &str, value: Value, expiration: Expiration) -> Result<bool> {
247 match self {
248 Self::Net(topology) => topology.set(key, value, expiration).await,
249 #[cfg(test)]
250 Self::Test(backend) => backend.set(key, value, expiration).await,
251 }
252 }
253
254 pub(crate) fn topology(&self) -> Option<&CacheTopology> {
255 match self {
256 Self::Net(topology) => Some(topology),
257 #[cfg(test)]
258 Self::Test(_) => None,
259 }
260 }
261}
262
263#[derive(Clone)]
274pub struct CacheService {
275 backend: Arc<ArcSwapAny<Arc<BackendSlot>>>,
276 options: CacheServiceOptions,
277 _hold: Option<Arc<dyn std::fmt::Debug + Send + Sync>>,
281}
282
283impl CacheService {
284 pub async fn single(endpoint: impl Into<String>) -> Result<Self> {
289 Self::single_with_options(endpoint, CacheServiceOptions::default()).await
290 }
291
292 pub async fn single_with_options(
294 endpoint: impl Into<String>,
295 options: CacheServiceOptions,
296 ) -> Result<Self> {
297 Self::new(CacheNamespaceConf::single_master(endpoint.into()), options).await
298 }
299
300 pub async fn new(conf: CacheNamespaceConf, options: CacheServiceOptions) -> Result<Self> {
304 let backend = Self::build_backend(&conf, &options, None)?;
305 Ok(Self {
306 backend: Arc::new(ArcSwapAny::new(Arc::new(backend))),
307 options,
308 _hold: None,
309 })
310 }
311
312 pub async fn new_live(
318 source: Arc<dyn CacheServiceConfigSource>,
319 options: CacheServiceOptions,
320 ) -> Result<Self> {
321 let initial = source.load().await?;
322 let backend = Self::build_backend(&initial, &options, None)?;
323 let backend_slot = Arc::new(ArcSwapAny::new(Arc::new(backend)));
324
325 let inner = crate::service::live::CacheServiceInner::new(
326 initial,
327 backend_slot.clone(),
328 options.clone(),
329 );
330 let inner_for_cb = inner.clone();
331 let on_update: Arc<dyn Fn(CacheNamespaceConf) + Send + Sync> = Arc::new(move |conf| {
332 if let Err(error) = inner_for_cb.apply(conf) {
333 tracing::warn!(
334 %error,
335 "cache-service config update failed; retaining old backend"
336 );
337 }
338 });
339 let handle = source.subscribe(on_update).await?;
340
341 Ok(Self {
348 backend: backend_slot,
349 options,
350 _hold: Some(Arc::new(LiveHold {
351 _source: source,
352 _handle: handle,
353 _inner: inner,
354 })),
355 })
356 }
357
358 pub(crate) fn build_backend(
359 conf: &CacheNamespaceConf,
360 options: &CacheServiceOptions,
361 previous: Option<&CacheTopology>,
362 ) -> Result<BackendSlot> {
363 Ok(BackendSlot::Net(CacheTopology::from_namespace_conf(
364 conf, options, previous,
365 )?))
366 }
367
368 fn backend(&self) -> Arc<BackendSlot> {
370 self.backend.load_full()
371 }
372
373 #[cfg(test)]
376 pub(crate) fn from_backend(backend: Arc<dyn Cacheable>, options: CacheServiceOptions) -> Self {
377 Self {
378 backend: Arc::new(ArcSwapAny::new(Arc::new(BackendSlot::Test(backend)))),
379 options,
380 _hold: None,
381 }
382 }
383}
384
385struct LiveHold {
388 _source: Arc<dyn CacheServiceConfigSource>,
389 _handle: SubscriptionHandle,
390 _inner: Arc<crate::service::live::CacheServiceInner>,
391}
392
393impl std::fmt::Debug for LiveHold {
394 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395 f.debug_struct("LiveHold").finish_non_exhaustive()
396 }
397}
398
399#[async_trait]
400impl Memcache for CacheService {
401 async fn get(&self, key: &str) -> Result<Option<CacheEntry>> {
402 self.backend()
403 .get(key)
404 .await
405 .map(|value| value.map(CacheEntry::from))
406 }
407
408 async fn set_with(&self, key: &str, value: Bytes, options: SetOptions) -> Result<bool> {
409 let expiration = options
410 .expiration
411 .unwrap_or(self.options.default_expiration);
412 let value = Value::new(value, options.flags.unwrap_or_default());
413 self.backend().set(key, value, expiration).await
414 }
415}
416
417#[cfg(test)]
418mod tests {
419 use std::collections::HashMap;
420 use std::sync::Mutex;
421
422 use crate::value::CasValue;
423
424 use super::*;
425
426 #[test]
427 fn cache_service_options_default_to_text_protocol() {
428 assert_eq!(CacheServiceOptions::default().protocol, Protocol::Text);
429 }
430
431 #[test]
432 fn cache_service_options_can_select_binary_protocol() {
433 let options = CacheServiceOptions::default().with_protocol(Protocol::Binary);
434
435 assert_eq!(options.protocol, Protocol::Binary);
436 }
437
438 #[derive(Default)]
439 struct RecordingBackend {
440 get_result: Mutex<Option<Value>>,
441 set_call: Mutex<Option<(String, Value, Expiration)>>,
442 }
443
444 #[async_trait]
445 impl Cacheable for RecordingBackend {
446 async fn get(&self, _key: &str) -> Result<Option<Value>> {
447 Ok(self.get_result.lock().unwrap().clone())
448 }
449
450 async fn get_multi(&self, _keys: &[&str]) -> Result<HashMap<String, Value>> {
451 unreachable!("the public API does not expose get_multi")
452 }
453
454 async fn set(&self, key: &str, value: Value, expire: Expiration) -> Result<bool> {
455 *self.set_call.lock().unwrap() = Some((key.to_owned(), value, expire));
456 Ok(true)
457 }
458
459 async fn set_with_noreply(
460 &self,
461 _key: &str,
462 _value: Value,
463 _expire: Expiration,
464 ) -> Result<()> {
465 unreachable!("the public API does not expose set_with_noreply")
466 }
467
468 async fn add(&self, _key: &str, _value: Value, _expire: Expiration) -> Result<bool> {
469 unreachable!("the public API does not expose add")
470 }
471
472 async fn get_cas(&self, _key: &str) -> Result<Option<CasValue>> {
473 unreachable!("the public API does not expose get_cas")
474 }
475
476 async fn cas(&self, _key: &str, _value: &CasValue, _expire: Expiration) -> Result<bool> {
477 unreachable!("the public API does not expose cas")
478 }
479
480 async fn delete(&self, _key: &str) -> Result<bool> {
481 unreachable!("the public API does not expose delete")
482 }
483
484 async fn delete_with_noreply(&self, _key: &str) -> Result<()> {
485 unreachable!("the public API does not expose delete_with_noreply")
486 }
487 }
488
489 #[tokio::test]
490 async fn set_uses_instance_expiration_and_zero_flags_by_default() {
491 let backend = Arc::new(RecordingBackend::default());
492 let cache = CacheService::from_backend(
493 backend.clone(),
494 CacheServiceOptions::new(Expiration::Seconds(90)),
495 );
496
497 assert!(
498 cache
499 .set("user:1", Bytes::from_static(b"value"))
500 .await
501 .unwrap()
502 );
503
504 let call = backend.set_call.lock().unwrap().clone().unwrap();
505 assert_eq!(call.0, "user:1");
506 assert_eq!(call.1.as_bytes(), b"value");
507 assert_eq!(call.1.flags(), 0);
508 assert_eq!(call.2, Expiration::Seconds(90));
509 }
510
511 #[tokio::test]
512 async fn set_with_overrides_expiration_and_flags() {
513 let backend = Arc::new(RecordingBackend::default());
514 let cache = CacheService::from_backend(
515 backend.clone(),
516 CacheServiceOptions::new(Expiration::Seconds(90)),
517 );
518 let options = SetOptions::default()
519 .with_expiration(Expiration::Seconds(5))
520 .with_flags(32);
521
522 cache
523 .set_with("user:2", Bytes::from_static(b"value"), options)
524 .await
525 .unwrap();
526
527 let call = backend.set_call.lock().unwrap().clone().unwrap();
528 assert_eq!(call.1.flags(), 32);
529 assert_eq!(call.2, Expiration::Seconds(5));
530 }
531
532 #[tokio::test]
533 async fn get_maps_wire_zero_flag_to_none_without_copying_data() {
534 let backend = Arc::new(RecordingBackend::default());
535 *backend.get_result.lock().unwrap() = Some(Value::new(Bytes::from_static(b"value"), 0));
536 let cache = CacheService::from_backend(backend, CacheServiceOptions::default());
537
538 let entry = cache.get("user:1").await.unwrap().unwrap();
539
540 assert_eq!(entry.data, Bytes::from_static(b"value"));
541 assert_eq!(entry.flags, None);
542 }
543
544 #[tokio::test]
545 async fn public_contract_is_object_safe() {
546 let backend = Arc::new(RecordingBackend::default());
547 *backend.get_result.lock().unwrap() = Some(Value::new(Bytes::new(), 7));
548 let cache: Arc<dyn Memcache> = Arc::new(CacheService::from_backend(
549 backend,
550 CacheServiceOptions::default(),
551 ));
552
553 assert_eq!(cache.get("key").await.unwrap().unwrap().flags, Some(7));
554 }
555}