use std::time::Duration;
use rainier_drivers::memcached::{check_key, expiry_seconds, Stored};
use rainier_drivers::MemcachedConnector;
use rainier_support::{BoxFuture, Result};
use crate::cache::Cache;
pub struct MemcachedCache {
connector: MemcachedConnector,
}
impl MemcachedCache {
pub fn new(connector: MemcachedConnector) -> Self {
Self { connector }
}
pub fn connector(&self) -> &MemcachedConnector {
&self.connector
}
pub async fn add_atomic(&self, key: &str, value: &[u8], ttl: Option<Duration>) -> Result<bool> {
check_key(key)?;
let mut guard = self.connector.acquire().await?;
match guard.connection().add(key, value, expiry_seconds(ttl)).await {
Ok(stored) => Ok(stored == Stored::Stored),
Err(e) => {
guard.discard();
Err(e)
}
}
}
}
impl Cache for MemcachedCache {
fn name(&self) -> &str {
"memcached"
}
fn add<'a>(
&'a self,
key: &'a str,
value: &'a [u8],
ttl: Option<Duration>,
) -> BoxFuture<'a, Result<bool>> {
Box::pin(async move { self.add_atomic(key, value, ttl).await })
}
fn forget_if<'a>(&'a self, key: &'a str, expected: &'a [u8]) -> BoxFuture<'a, Result<bool>> {
Box::pin(async move {
check_key(key)?;
let mut guard = self.connector.acquire().await?;
match guard.connection().delete_if(key, expected).await {
Ok(removed) => Ok(removed),
Err(e) => {
guard.discard();
Err(e)
}
}
})
}
fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<Option<Vec<u8>>>> {
Box::pin(async move {
check_key(key)?;
let mut guard = self.connector.acquire().await?;
match guard.connection().get(key).await {
Ok(value) => Ok(value),
Err(e) => {
guard.discard();
Err(e)
}
}
})
}
fn put<'a>(
&'a self,
key: &'a str,
value: &'a [u8],
ttl: Option<Duration>,
) -> BoxFuture<'a, Result<()>> {
Box::pin(async move {
check_key(key)?;
let mut guard = self.connector.acquire().await?;
match guard.connection().set(key, value, expiry_seconds(ttl)).await {
Ok(()) => Ok(()),
Err(e) => {
guard.discard();
Err(e)
}
}
})
}
fn forget<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<bool>> {
Box::pin(async move {
check_key(key)?;
let mut guard = self.connector.acquire().await?;
match guard.connection().delete(key).await {
Ok(removed) => Ok(removed),
Err(e) => {
guard.discard();
Err(e)
}
}
})
}
fn flush(&self) -> BoxFuture<'_, Result<()>> {
Box::pin(async move {
let mut guard = self.connector.acquire().await?;
match guard.connection().flush_all().await {
Ok(()) => Ok(()),
Err(e) => {
guard.discard();
Err(e)
}
}
})
}
fn increment<'a>(&'a self, key: &'a str, by: i64) -> BoxFuture<'a, Result<i64>> {
Box::pin(async move {
check_key(key)?;
let mut guard = self.connector.acquire().await?;
let existing = match guard.connection().increment(key, by.unsigned_abs(), by >= 0).await
{
Ok(value) => value,
Err(e) => {
guard.discard();
return Err(e);
}
};
if let Some(value) = existing {
return Ok(value as i64);
}
let seed = by.max(0);
match guard.connection().add(key, seed.to_string().as_bytes(), 0).await {
Ok(Stored::Stored) => Ok(seed),
Ok(Stored::NotStored) => {
match guard.connection().increment(key, by.unsigned_abs(), by >= 0).await {
Ok(Some(value)) => Ok(value as i64),
Ok(None) => Ok(seed),
Err(e) => {
guard.discard();
Err(e)
}
}
}
Err(e) => {
guard.discard();
Err(e)
}
}
})
}
}
impl std::fmt::Debug for MemcachedCache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MemcachedCache").finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cache::CacheExt;
fn cache() -> MemcachedCache {
MemcachedCache::new(MemcachedConnector::open("127.0.0.1:11211"))
}
#[test]
fn the_driver_is_named() {
assert_eq!(cache().name(), "memcached");
}
#[tokio::test]
async fn an_invalid_key_is_refused_before_connecting() {
let cache = MemcachedCache::new(MemcachedConnector::open("127.0.0.1:1"));
let err = cache.get("has space").await.unwrap_err();
assert!(err.message().contains("spaces"), "{}", err.message());
}
#[tokio::test]
async fn an_over_long_key_is_refused_before_connecting() {
let cache = MemcachedCache::new(MemcachedConnector::open("127.0.0.1:1"));
assert!(cache.put(&"k".repeat(251), b"v", None).await.is_err());
}
#[tokio::test]
#[ignore = "needs a live Memcached"]
async fn a_value_round_trips() {
let cache = cache();
cache.put("k", b"v", None).await.unwrap();
assert_eq!(cache.get("k").await.unwrap(), Some(b"v".to_vec()));
assert!(cache.forget("k").await.unwrap());
assert_eq!(cache.get("k").await.unwrap(), None);
}
#[tokio::test]
#[ignore = "needs a live Memcached"]
async fn forgetting_an_absent_key_is_false_not_an_error() {
assert!(!cache().forget("definitely-absent").await.unwrap());
}
#[tokio::test]
#[ignore = "needs a live Memcached"]
async fn increment_creates_then_accumulates() {
let cache = cache();
cache.forget("hits").await.unwrap();
assert_eq!(cache.increment("hits", 1).await.unwrap(), 1);
assert_eq!(cache.increment("hits", 4).await.unwrap(), 5);
assert_eq!(cache.decrement("hits", 2).await.unwrap(), 3);
}
#[tokio::test]
#[ignore = "needs a live Memcached"]
async fn a_counter_does_not_go_negative() {
let cache = cache();
cache.forget("balance").await.unwrap();
cache.increment("balance", 1).await.unwrap();
assert_eq!(cache.decrement("balance", 10).await.unwrap(), 0);
}
#[tokio::test]
#[ignore = "needs a live Memcached"]
async fn add_atomic_lets_exactly_one_caller_win() {
let cache = cache();
cache.forget("lock").await.unwrap();
assert!(cache.add_atomic("lock", b"mine", Some(Duration::from_secs(5))).await.unwrap());
assert!(!cache.add_atomic("lock", b"theirs", Some(Duration::from_secs(5))).await.unwrap());
}
}