alastairtree / LazyCache

An easy to use thread safe in-memory caching service with a simple developer friendly API for c#
https://nuget.org/packages/LazyCache
MIT License
1.71k stars 159 forks source link

Using ExpirationMode.ImmediateEviction ignores sliding expiration value #156

Open ExLuzZziVo opened 3 years ago

ExLuzZziVo commented 3 years ago

Describe the bug Hi, I am trying to use ExpirationMode.ImmediateEviction with sliding expiration in my project and this makes the entry to update every time I access it, ignoring the sliding expiration value

To Reproduce

            for (var i = 1; i <= 5; i++)
            {
                await _cache.GetOrAddAsync("1", entry => Task.FromResult(new Random().Next(1, 10)),
                    new LazyCacheEntryOptions {ExpirationMode = ExpirationMode.ImmediateEviction}
                        .SetSlidingExpiration(TimeSpan.FromSeconds(5))
                        .RegisterPostEvictionCallback((key, value, reason, state) =>
                        {
                            Debug.WriteLine($"{key} {((Task<int>) value).Result}");
                        }));
            }

Expected behavior The sliding expiration time shouldn't be ignored

** Framework and Platform

alastairtree commented 3 years ago

I suspect the bug is because the immediate expiration stuff was never tested with sliding expiration. Looking at https://github.com/alastairtree/LazyCache/blob/master/LazyCache/Providers/MemoryCacheProvider.cs#L55 you can see it seems to cancel based on absolute expiration so some additional code would be needed detect sliding to make it work.

expiryTokenSource.CancelAfter(lazyPolicy.ImmediateAbsoluteExpirationRelativeToNow);

and note the related code in https://github.com/alastairtree/LazyCache/blob/master/LazyCache/MemoryCacheEntryOptionsExtensions.cs

alastairtree commented 3 years ago

Just released a new version, may well fix this. Let us know how you get on?

Install-Package LazyCache -Version 2.4.0

ExLuzZziVo commented 3 years ago

Just released a new version, may well fix this. Let us know how you get on?

Install-Package LazyCache -Version 2.4.0

Unfortunately this doesn't fix it

A workaround is to use MemoryCacheEntryOptions instead of LazyCacheEntryOptions

LukeRH commented 3 years ago

Hi @ExLuzZziVo, i am also seeing this issue please could you detail the workaround ?

alastairtree commented 3 years ago

A PR with a failing unit test would help track this down if you are willing?

ExLuzZziVo commented 3 years ago

Hi @ExLuzZziVo, i am also seeing this issue please could you detail the workaround ?

Just replace new LazyCacheEntryOptions {ExpirationMode = ExpirationMode.ImmediateEviction} with new MemoryCacheEntryOptions() in above example and it will work as expected

A PR with a failing unit test would help track this down if you are willing?

I will try to make a PR next week

ExLuzZziVo commented 3 years ago

@alastairtree There is a strange thing with the unit test for this issue. If I run the test method separately, it fails. If I run all the tests, it passes. I have tried to set the cache key manually but the result was the same. My test method:

        [Test]
        public void GetFromCacheTwiceAtSameTimeOnlyAddsOnceWithSliding()
        {
            var times = 0;

            var lazyCacheEntryOptions = new LazyCacheEntryOptions { ExpirationMode = ExpirationMode.ImmediateEviction }
                .SetSlidingExpiration(TimeSpan.FromSeconds(20));

            var t1 = Task.Factory.StartNew(() =>
            {
                sut.GetOrAdd(TestKey, () =>
                {
                    Interlocked.Increment(ref times);
                    return new DateTime(2001, 01, 01);
                }, lazyCacheEntryOptions);
            });

            var t2 = Task.Factory.StartNew(() =>
            {
                sut.GetOrAdd(TestKey, () =>
                {
                    Interlocked.Increment(ref times);
                    return new DateTime(2001, 01, 01);
                }, lazyCacheEntryOptions);
            });

            Task.WaitAll(t1, t2);

            Assert.AreEqual(1, times);
        }