Home | History | Annotate | Download | only in cloud
      1 /* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
      2 
      3 Licensed under the Apache License, Version 2.0 (the "License");
      4 you may not use this file except in compliance with the License.
      5 You may obtain a copy of the License at
      6 
      7     http://www.apache.org/licenses/LICENSE-2.0
      8 
      9 Unless required by applicable law or agreed to in writing, software
     10 distributed under the License is distributed on an "AS IS" BASIS,
     11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 See the License for the specific language governing permissions and
     13 limitations under the License.
     14 ==============================================================================*/
     15 
     16 #ifndef TENSORFLOW_CORE_PLATFORM_CLOUD_EXPIRING_LRU_CACHE_H_
     17 #define TENSORFLOW_CORE_PLATFORM_CLOUD_EXPIRING_LRU_CACHE_H_
     18 
     19 #include <list>
     20 #include <map>
     21 #include <memory>
     22 #include <string>
     23 #include "tensorflow/core/platform/env.h"
     24 #include "tensorflow/core/platform/mutex.h"
     25 #include "tensorflow/core/platform/thread_annotations.h"
     26 #include "tensorflow/core/platform/types.h"
     27 
     28 namespace tensorflow {
     29 
     30 /// \brief An LRU cache of string keys and arbitrary values, with configurable
     31 /// max item age (in seconds) and max entries.
     32 ///
     33 /// This class is thread safe.
     34 template <typename T>
     35 class ExpiringLRUCache {
     36  public:
     37   /// A `max_age` of 0 means that nothing is cached. A `max_entries` of 0 means
     38   /// that there is no limit on the number of entries in the cache (however, if
     39   /// `max_age` is also 0, the cache will not be populated).
     40   ExpiringLRUCache(uint64 max_age, size_t max_entries,
     41                    Env* env = Env::Default())
     42       : max_age_(max_age), max_entries_(max_entries), env_(env) {}
     43 
     44   /// Insert `value` with key `key`. This will replace any previous entry with
     45   /// the same key.
     46   void Insert(const string& key, const T& value) {
     47     if (max_age_ == 0) {
     48       return;
     49     }
     50     mutex_lock lock(mu_);
     51     InsertLocked(key, value);
     52   }
     53 
     54   // Delete the entry with key `key`. Return true if the entry was found for
     55   // `key`, false if the entry was not found. In both cases, there is no entry
     56   // with key `key` existed after the call.
     57   bool Delete(const string& key) {
     58     mutex_lock lock(mu_);
     59     return DeleteLocked(key);
     60   }
     61 
     62   /// Look up the entry with key `key` and copy it to `value` if found. Returns
     63   /// true if an entry was found for `key`, and its timestamp is not more than
     64   /// max_age_ seconds in the past.
     65   bool Lookup(const string& key, T* value) {
     66     if (max_age_ == 0) {
     67       return false;
     68     }
     69     mutex_lock lock(mu_);
     70     return LookupLocked(key, value);
     71   }
     72 
     73   typedef std::function<Status(const string&, T*)> ComputeFunc;
     74 
     75   /// Look up the entry with key `key` and copy it to `value` if found. If not
     76   /// found, call `compute_func`. If `compute_func` returns successfully, store
     77   /// a copy of the output parameter in the cache, and another copy in `value`.
     78   Status LookupOrCompute(const string& key, T* value,
     79                          const ComputeFunc& compute_func) {
     80     if (max_age_ == 0) {
     81       return compute_func(key, value);
     82     }
     83 
     84     // Note: we hold onto mu_ for the rest of this function. In practice, this
     85     // is okay, as stat requests are typically fast, and concurrent requests are
     86     // often for the same file. Future work can split this up into one lock per
     87     // key if this proves to be a significant performance bottleneck.
     88     mutex_lock lock(mu_);
     89     if (LookupLocked(key, value)) {
     90       return Status::OK();
     91     }
     92     Status s = compute_func(key, value);
     93     if (s.ok()) {
     94       InsertLocked(key, *value);
     95     }
     96     return s;
     97   }
     98 
     99   /// Clear the cache.
    100   void Clear() {
    101     mutex_lock lock(mu_);
    102     cache_.clear();
    103     lru_list_.clear();
    104   }
    105 
    106   /// Accessors for cache parameters.
    107   uint64 max_age() const { return max_age_; }
    108   size_t max_entries() const { return max_entries_; }
    109 
    110  private:
    111   struct Entry {
    112     /// The timestamp (seconds) at which the entry was added to the cache.
    113     uint64 timestamp;
    114 
    115     /// The entry's value.
    116     T value;
    117 
    118     /// A list iterator pointing to the entry's position in the LRU list.
    119     std::list<string>::iterator lru_iterator;
    120   };
    121 
    122   bool LookupLocked(const string& key, T* value) EXCLUSIVE_LOCKS_REQUIRED(mu_) {
    123     auto it = cache_.find(key);
    124     if (it == cache_.end()) {
    125       return false;
    126     }
    127     lru_list_.erase(it->second.lru_iterator);
    128     if (env_->NowSeconds() - it->second.timestamp > max_age_) {
    129       cache_.erase(it);
    130       return false;
    131     }
    132     *value = it->second.value;
    133     lru_list_.push_front(it->first);
    134     it->second.lru_iterator = lru_list_.begin();
    135     return true;
    136   }
    137 
    138   void InsertLocked(const string& key, const T& value)
    139       EXCLUSIVE_LOCKS_REQUIRED(mu_) {
    140     lru_list_.push_front(key);
    141     Entry entry{env_->NowSeconds(), value, lru_list_.begin()};
    142     auto insert = cache_.insert(std::make_pair(key, entry));
    143     if (!insert.second) {
    144       lru_list_.erase(insert.first->second.lru_iterator);
    145       insert.first->second = entry;
    146     } else if (max_entries_ > 0 && cache_.size() > max_entries_) {
    147       cache_.erase(lru_list_.back());
    148       lru_list_.pop_back();
    149     }
    150   }
    151 
    152   bool DeleteLocked(const string& key) EXCLUSIVE_LOCKS_REQUIRED(mu_) {
    153     auto it = cache_.find(key);
    154     if (it == cache_.end()) {
    155       return false;
    156     }
    157     lru_list_.erase(it->second.lru_iterator);
    158     cache_.erase(it);
    159     return true;
    160   }
    161 
    162   /// The maximum age of entries in the cache, in seconds. A value of 0 means
    163   /// that no entry is ever placed in the cache.
    164   const uint64 max_age_;
    165 
    166   /// The maximum number of entries in the cache. A value of 0 means there is no
    167   /// limit on entry count.
    168   const size_t max_entries_;
    169 
    170   /// The Env from which we read timestamps.
    171   Env* const env_;  // not owned
    172 
    173   /// Guards access to the cache and the LRU list.
    174   mutex mu_;
    175 
    176   /// The cache (a map from string key to Entry).
    177   std::map<string, Entry> cache_ GUARDED_BY(mu_);
    178 
    179   /// The LRU list of entries. The front of the list identifies the most
    180   /// recently accessed entry.
    181   std::list<string> lru_list_ GUARDED_BY(mu_);
    182 };
    183 
    184 }  // namespace tensorflow
    185 
    186 #endif  // TENSORFLOW_CORE_PLATFORM_CLOUD_EXPIRING_LRU_CACHE_H_
    187