/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ using Amazon.Util; using System; using System.Collections.Generic; namespace Amazon.Runtime.Internal.Util { /// /// a size-limited cache of key value pairs /// /// Once the maximum size has been reached, the least recently /// used pairs will be evicted to make room for any new items. /// /// /// public class LruCache where TKey : class where TValue : class { private readonly object cacheLock = new object(); private Dictionary> cache; private LruList lruList; /// /// the maximum number of entries this LruCache will hold /// before items begin getting evicted /// public int MaxEntries { get; private set; } /// /// the number of items in the cache /// public int Count { get { lock (cacheLock) { return cache.Count; } } } /// /// Construct a new LruCache. /// /// maximum number of entries before items are evicted public LruCache(int maxEntries) { if (maxEntries < 1) { throw new ArgumentException("maxEntries must be greater than zero."); } MaxEntries = maxEntries; cache = new Dictionary>(); lruList = new LruList(); } /// /// Returns the least recently used item if it exists. /// /// The item that is least recently used or the default value of the LruListItem class if the LRU cache is empty. public LruListItem FindOldestItem() { lock (cacheLock) { var item = default(LruListItem); if (lruList.Tail != null) item = lruList.Tail; return item; } } /// /// Method to evict expired LRUListItems. /// /// Number of seconds the LRUListItems are valid for. public void EvictExpiredLRUListItems(int validityInSeconds) { lock (cacheLock) { while (Count != 0) { var item = FindOldestItem(); #pragma warning disable CS0618 // Type or member is obsolete var timeSpan = AWSSDKUtils.CorrectedUtcNow - item.LastTouchedTimestamp; #pragma warning restore CS0618 // Type or member is obsolete if (timeSpan.TotalSeconds > validityInSeconds) Evict(item.Key); else break; } } } /// /// Add the key/value pair to the cache, or update /// the value if the key already exists. /// /// If the cache is full, evicts the least recently used item. /// /// /// public void AddOrUpdate(TKey key, TValue value) { lock (cacheLock) { LruListItem existingLruListItem; if (cache.TryGetValue(key, out existingLruListItem)) { // update existingLruListItem.Value = value; lruList.Touch(existingLruListItem); } else { // add var newLruListItem = new LruListItem(key, value); while (cache.Count >= MaxEntries) { cache.Remove(lruList.EvictOldest()); } lruList.Add(newLruListItem); cache.Add(key, newLruListItem); } } } /// /// Evicts a specific key out of the cache if it exists /// /// the key to evict from the cache public void Evict(TKey key) { lock (cacheLock) { LruListItem existingLruListItem; if (cache.TryGetValue(key, out existingLruListItem)) { lruList.Remove(existingLruListItem); cache.Remove(key); } } } /// /// Try to get the value associated with the key. /// /// the key to look up /// the value, if the key exists /// true if there is a value associated with the key, or false if no value is associated with the key public bool TryGetValue(TKey key, out TValue value) { LruListItem existingListItem; lock (cacheLock) { if (cache.TryGetValue(key, out existingListItem)) { lruList.Touch(existingListItem); value = existingListItem.Value; return true; } else { value = null; return false; } } } /// /// Try to get the value associated with the key, if it doesn't exist, use the provided factory method to /// create a new value and tries adding it to the cache. /// /// the key to look up /// the factory method used in case the key is not present in the cache /// the looked up value or the value created by the factory public TValue GetOrAdd(TKey key, Func factory) { TValue value; if (TryGetValue(key, out value)) { return value; } value = factory(key); AddOrUpdate(key, value); return value; } /// /// Clear the LruCache of all entries. /// public void Clear() { lock (cacheLock) { cache.Clear(); lruList.Clear(); } } } /// /// Helper class to support LruCache. /// Does not implement the error checking and synchronization that /// would be necessary for it to stand alone. /// public class LruList { public LruListItem Head { get; private set; } public LruListItem Tail { get; private set; } public void Add(LruListItem item) { if (Head == null) { Head = item; Tail = item; item.Previous = null; item.Next = null; } else { Head.Previous = item; item.Next = Head; item.Previous = null; Head = item; } #pragma warning disable CS0618 // Type or member is obsolete item.LastTouchedTimestamp = AWSSDKUtils.CorrectedUtcNow; #pragma warning restore CS0618 // Type or member is obsolete } public void Remove(LruListItem item) { if (Head == item || Tail == item) { if (Head == item) { Head = item.Next; if (Head != null) { Head.Previous = null; } } if (Tail == item) { Tail = item.Previous; if (Tail != null) { Tail.Next = null; } } } else { item.Previous.Next = item.Next; item.Next.Previous = item.Previous; } item.Previous = null; item.Next = null; } public void Touch(LruListItem item) { Remove(item); Add(item); } public TKey EvictOldest() { TKey key = default(TKey); if (Tail != null) { key = Tail.Key; Remove(Tail); } return key; } internal void Clear() { Head = null; Tail = null; } } /// /// Item to be stored in the LruList /// linked list structure. /// public class LruListItem { public TValue Value { get; set; } public TKey Key { get; private set; } internal DateTime LastTouchedTimestamp { get; set; } public LruListItem Next { get; set; } public LruListItem Previous { get; set; } public LruListItem(TKey key, TValue value) { Key = key; Value = value; } } }