1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#![allow(dead_code)]
///! Implementation of cache functionality for common use of Writium APIs.
///!
///! To obey the rule, 'Do not communicate by sharing memory; instead, share
///! memory by communicating,' Writium cache doesn't allow memory borrowing.
use std::sync::RwLock;
use lru_cache::LruCache;

/// Cache for each Writium Api. Any Writium Api can be composited with this
/// struct for cache.
pub struct Cache<Src: 'static + CacheSource> {
    cache: RwLock<LruCache<String, Src::Value>>,
    src: Src,
}
impl<Src: 'static + CacheSource> Cache<Src> {
    pub fn new(capacity: usize, src: Src) -> Cache<Src> {
        Cache {
            cache: RwLock::new(LruCache::new(capacity)),
            src: src,
        }
    }

    // Try to get the object identified by given ID. `None` is returned directly
    // if the value is not cached.
    fn fetch(&self, id: &str) -> Option<Src::Value> {
        let guard = self.cache.read().unwrap();
        if let Some((_, val)) = guard.iter().find(|x| x.0 == id) {
            Some(val.clone())
        } else { None }
    }

    /// Get the object identified by given ID. If the object is not cached, try
    /// generating its cache with provided generation function. If there is no
    /// space for another object, the last recently accessed cache will be
    /// disposed.
    pub fn get(&self, id: &str) -> Option<Src::Value> {
        let cached = self.fetch(id);
        if cached.is_some() { return cached }
        if let Some(new) = self.src.generate(id) {
            let mut guard = self.cache.write().unwrap();
            guard.insert(id.to_string(), new);
        } else { return None }
        self.fetch(id)
    }

    /// Set the object identified by given ID with provided new one. If the
    /// object is not cached, try generating its cache with provided generation
    /// function. If there is no space for another object, the last recently
    /// accessed cache will be disposed and returned.
    pub fn insert(&self, id: &str, new_val: Src::Value) -> Option<Src::Value> {
        let mut guard = self.cache.write().unwrap();
        if let Some(mut old_val) = guard.insert(id.to_string(), new_val) {
            self.src.dispose(id, &mut old_val);
            Some(old_val)
        } else { None }
    }

    /// Remove the object identified by given ID. If the object is not cached,
    /// try generating its cache with provided generation function and then
    /// remove it. In case cache generation is needed. If there is no space for
    /// another object, the last recently accessed cache will be disposed. The
    /// value removed as cache is returned.
    pub fn remove(&self, id: &str) -> Option<Src::Value> {
        let mut guard = self.cache.write().unwrap();
        if let Some(mut old_val) = guard.remove(id) {
            self.src.remove(id, &mut old_val);
            Some(old_val)
        } else { None }
    }

    /// The maximum number of items can be cached at a same time.
    pub fn capacity(&self) -> usize {
        // Only if the thread is poisoned `cache` will be unavailable.
        self.cache.read().unwrap().capacity()
    }

    /// Get the number of items cached.
    pub fn len(&self) -> usize {
        // Only if the thread is poisoned `cache` will be unavailable.
        self.cache.read().unwrap().len()
    }
}

pub trait CacheSource: 'static + Send + Sync {
    type Value: Clone;
    /// Generate a new cached object. Return a value defined by default
    /// configurations. In the course of generation, no state should be created
    /// or written out of RAM, e.g., writing to files or calling to remote
    /// machines.
    fn generate(&self, id: &str) -> Option<Self::Value>;
    /// Dispose a cached object. Implementations should write the value into a
    /// recoverable form of storage, e.g., serializing data into JSON, if
    /// necessary. Cache disposal is an optional process.
    fn dispose(&self, _id: &str, _obj: &mut Self::Value) {}
    /// Remove a cached object. Implementations should remove any associated
    /// data from storage and invalidate any recoverability. Cache removal is an
    /// optional process.
    fn remove(&self, _id: &str, _obj: &mut Self::Value) {}
}

#[cfg(test)]
mod tests {
    // `bool` controls always fail.
    struct TestSource(bool);
    impl super::CacheSource for TestSource {
        type Value = &'static str;
        fn generate(&self, id: &str) -> Option<Self::Value> {
            if self.0 { None }
            else { Some(&["cache0", "cache1", "cache2", "cache3"][id.parse::<usize>().unwrap()]) }
        }
        fn dispose(&self, _id: &str, obj: &mut Self::Value) {
            *obj = "disposed"
        }
        fn remove(&self, _id: &str, obj: &mut Self::Value) {
            *obj = "removed"
        }
    }
    type TestCache = super::Cache<TestSource>;

    fn make_cache(fail: bool) -> TestCache {
        TestCache::new(3, TestSource(fail))
    }

    #[test]
    fn test_cache() {
        let cache = make_cache(false);
        assert_eq!(cache.get("0"), Some("cache0"));
        assert_eq!(cache.get("1"), Some("cache1"));
        assert_eq!(cache.get("2"), Some("cache2"));
    }
    #[test]
    fn test_cache_failure() {
        let cache = make_cache(true);
        assert_eq!(cache.get("0"), None);
        assert_eq!(cache.get("1"), None);
        assert_eq!(cache.get("2"), None);
    }
    #[test]
    fn test_max_cache() {
        let cache = make_cache(false);
        assert_eq!(cache.len(), 0);
        assert_eq!(cache.get("0"), Some("cache0"));
        assert_eq!(cache.len(), 1);
        assert_eq!(cache.get("1"), Some("cache1"));
        assert_eq!(cache.len(), 2);
        assert_eq!(cache.get("2"), Some("cache2"));
        assert_eq!(cache.len(), 3);
        assert_eq!(cache.get("3"), Some("cache3"));
        assert_eq!(cache.len(), 3);
    }
    #[test]
    fn test_max_cache_failure() {
        let cache = make_cache(true);
        assert_eq!(cache.len(), 0);
        assert_eq!(cache.get("0"), None);
        assert_eq!(cache.len(), 0);
        cache.get("1");
        assert_eq!(cache.len(), 0);
        cache.get("2");
        assert_eq!(cache.len(), 0);
    }
    #[test]
    fn test_remove() {
        let cache = make_cache(false);
        cache.get("0");
        assert_eq!(cache.len(), 1);
        assert_eq!(cache.remove("0"), Some("removed"));
        assert_eq!(cache.len(), 0);
    }
}