// Copyright (c) 2020 Sergey Ivonchik // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE // OR OTHER DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.Linq; using Httx.Utils; namespace Httx.Caches.Memory { internal class Item { public Item(string key, T value, int maxAge, long createdAt) { Key = key; Value = value; MaxAge = maxAge; CreatedAt = createdAt; } public Item(string key, T value, int maxAge) : this(key, value, maxAge, Time.UnixTime()) { } public string Key { get; } public T Value { get; } public int MaxAge { get; } public long CreatedAt { get; } public bool Expired => Time.Since(CreatedAt) >= MaxAge; } public class MemoryCache { public const int PutCollectDisabled = -1; public const int CollectEveryPut = 0; private readonly object selfLock = new object(); private readonly int size; private readonly int maxAge; private readonly int collectFrequency; private readonly Action onEvictValueCallback; private int collectAfter; private Dictionary>> cacheImpl = new Dictionary>>(); private LinkedList> lruPolicy = new LinkedList>(); public MemoryCache(int size, int maxAge = int.MaxValue, int collectFrequency = PutCollectDisabled, Action onEvictValueCallback = null) { this.size = size; this.maxAge = maxAge; this.onEvictValueCallback = onEvictValueCallback; this.collectFrequency = collectFrequency; collectAfter = collectFrequency; } public void Put(string key, T value, int ttl) { lock (selfLock) { TryCollect(); if (cacheImpl.Count >= size) { RemoveFirst(); } var node = new LinkedListNode>(new Item(key, value, ttl)); lruPolicy.AddLast(node); cacheImpl[key] = node; } } public void Put(string key, T value) { Put(key, value, maxAge); } public T Get(string key, Func defaultFunc = null) { lock (selfLock) { if (cacheImpl.TryGetValue(key, out var node)) { var item = node.Value; if (!item.Expired) { lruPolicy.Remove(node); lruPolicy.AddLast(node); return item.Value; } } } return null != defaultFunc ? defaultFunc.Invoke() : default; } private void TryCollect() { if (PutCollectDisabled == collectAfter) { return; } if (0 == collectAfter) { var collectedCacheImpl = new Dictionary>>(); var collectedLruImpl = new LinkedList>(); foreach (var lruItem in lruPolicy.Where(lruItem => !lruItem.Expired)) { collectedCacheImpl[lruItem.Key] = collectedLruImpl.AddLast(lruItem); } foreach (var p in cacheImpl) { if (p.Value.Value.Expired) { onEvictValueCallback?.Invoke(p.Value.Value.Value, true); } } cacheImpl = collectedCacheImpl; lruPolicy = collectedLruImpl; collectAfter = collectFrequency; } collectAfter = Math.Max(collectAfter - 1, 0); } private void RemoveFirst() { var firstNode = lruPolicy.First; onEvictValueCallback?.Invoke(firstNode.Value.Value, false); lruPolicy.RemoveFirst(); cacheImpl.Remove(firstNode.Value.Key); } } public class MemoryCache : MemoryCache { public MemoryCache(int size, int maxAge = int.MaxValue, int collectFrequency = PutCollectDisabled, Action onEvictValueCallback = null) : base(size, maxAge, collectFrequency, onEvictValueCallback) { } } }