LazyCache
LazyCache copied to clipboard
LongRunning shouldn't run more than once
Describe the bug My goal here is to cache some long running process data and then upon expiration, i will get those data that going to expired and append new delta changes from database The long running process shouldn't run more than one time
To Reproduce I will attach the console program that i wrote to reproduce the issue
When i set int parallelNumber = 100; // 100 or below => All the thing run as EXPECTED
When i set int parallelNumber = 300; // 300 or allow => Long process called more than once .. [NOT OK]
Expected behavior Long process should call ONE even the parallel count set to 300 above
** Framework and Platform
- OS: Windows 10
- Framework v4.6.2
- LazyCache Version 2.4
Console source code
using LazyCache; using Microsoft.Extensions.Caching.Memory; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks;
namespace ConsoleApp1 { class program { private static IAppCache _lazyCache; private static int _totalLongProcess; private static int _totalAppendProcess;
static List<int> LongProcess(string key)
{
_totalLongProcess++;
if (_totalLongProcess > 1)
{
throw new Exception("Not suppose to run more than one time");
}
Console.WriteLine("LONG PROCESS for key " + key);
Thread.Sleep(5 * 1000);
return Enumerable.Range(0, 100).ToList();
}
static List<int> AppendElement(string key, List<int> ori)
{
_totalAppendProcess++;
Console.WriteLine("Append Process for key " + key);
Thread.Sleep(2 * 1000);
var newList = new List<int>();
newList.Add(12345);
newList.Add(54321);
ori.AddRange(newList);
return ori;
}
static List<int> GetCacheByKey(string key, string threadId)
{
Console.WriteLine($"Request key {key}, threadId {threadId}");
var result = _lazyCache.GetOrAdd(key, () => LongProcess(key), GetOptions());
return result;
}
static MemoryCacheEntryOptions GetOptions()
{
//ensure the cache item expires exactly on 30s (and not lazily on the next access)
var options = new LazyCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromSeconds(10), ExpirationMode.ImmediateExpiration);
// as soon as it expires, re-add it to the cache
options.RegisterPostEvictionCallback((keyEvicted, value, reason, state) =>
{
// dont re-add if running out of memory or it was forcibly removed
if (reason == EvictionReason.Expired || reason == EvictionReason.TokenExpired)
{
var oriList = value as List<int>;
_lazyCache.GetOrAdd(keyEvicted.ToString(), _ => AppendElement(keyEvicted.ToString(), oriList), GetOptions()); //calls itself to get another set of options!
}
});
return options;
}
static void Main(string[] args)
{
_lazyCache = new CachingService();
int round = 1;
int i = 0;
while (i < round)
{
int parallelNumber = 300;
Parallel.For(0, parallelNumber, count =>
{
Thread.Sleep(1 * 1000);
var list = GetCacheByKey("1", Thread.CurrentThread.ManagedThreadId.ToString());
Console.WriteLine($"Got result for key {"1"}, threadId {Thread.CurrentThread.ManagedThreadId.ToString()}, count {list.Count}");
});
i++;
}
Console.WriteLine("Total long process run: " + _totalLongProcess);
Console.WriteLine("Total append process run: " + _totalAppendProcess);
Console.ReadLine();
}
}
}
How long does the 300 version take? Your code only has 10s (not 30s as commented) - could it be longer than 10?
Also your expiration code is wrong - it should be using DateTiemOffset
new LazyCacheEntryOptions().SetAbsoluteExpiration(DateTimeOffset.Now.AddSeconds(10))