LazyCache icon indicating copy to clipboard operation
LazyCache copied to clipboard

LazyCache.TryGetValue appears to return null value

Open thenerdynerd opened this issue 4 years ago • 20 comments

There appears to be a null value returned from the out parameter when using the trygetvalue method.

The code appears to find the value in the cache if this is defined but a try get does not return a value and thus unable to be used.

Is this a bug?

thenerdynerd avatar Sep 02 '21 22:09 thenerdynerd

Are you evaluating the return value of the TryGetValue? If the response is false, then the out parameter would be null.

gamblen avatar Sep 03 '21 08:09 gamblen

Screen Shot 2021-09-03 at 09 38 33

When then trying to evaluate the value object and or converting into the type needed it is null and fails. It does find it in the cache so it is in there and not false.

thenerdynerd avatar Sep 03 '21 08:09 thenerdynerd

This is exactly how I am using it and it works for me.

Would you be able to create a sample to reproduce the problem?

gamblen avatar Sep 03 '21 08:09 gamblen

OK will do - for now I have had to add something like this:

Screen Shot 2021-09-03 at 09 49 35

But I will try and get you something that I have been using. Do you need to see the exception generated?

thenerdynerd avatar Sep 03 '21 08:09 thenerdynerd

Can you try the latest package version? That should force value to be List<Account> rather than object.

I imagine the exception is NullReference?

gamblen avatar Sep 03 '21 08:09 gamblen

Yes that's right it's a null reference - just updating and will let you know. Thanks for the quick response.

thenerdynerd avatar Sep 03 '21 08:09 thenerdynerd

Hi now it appears with the update this is not being hit or evaluated via the trygetvalue - It's the same code just the update vs non update. Is there a reason that the behaviour would change like this?

thenerdynerd avatar Sep 03 '21 09:09 thenerdynerd

namespace LazyCache.UnitTests
{
    using System.Collections.Generic;
    using AutoFixture;
    using FluentAssertions;
    using NUnit.Framework;

    [TestFixture]
    public class Issue164Fixture
    {
        public class MyClass
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }

        [Test]
        public void Issue164()
        {
            var sut = new LazyCache.CachingService();

            var fixture = new AutoFixture.Fixture();

            var values = fixture.Create<List<MyClass>>();
            sut.Add("MyKey", values);


            if (sut.TryGetValue<List<MyClass>>("MyKey", out var cachedValues))
            {
                cachedValues.Should().BeEquivalentTo(values);
            }
            else
            {
                Assert.Fail("Not Found");
            }
        }

        [Test]
        public void Issue164_NotInCache()
        {
            var sut = new LazyCache.CachingService();

            var fixture = new AutoFixture.Fixture();

            var values = fixture.Create<List<MyClass>>();
            sut.Add("MyKey1", values);


            if (sut.TryGetValue<List<MyClass>>("MyKey2", out var cachedValues))
            {
                Assert.Fail("Found??");
            }
            else
            {
                cachedValues.Should().BeNull();
            }
        }
    }
}

How close is your implementation to this? This appears to work.

gamblen avatar Sep 03 '21 09:09 gamblen

Hi thanks for this.

My implementation is very close to this I will create a quick unit test to show.

Not sure if this is relevant but I am using lazy cache in an azure API service and this is being called multiple times and sometimes at the same time. Would the cache be visible through each call?

It appears when 2 calls are close together it adds to cache but it's not committed to the cache when the second call is being invoked and getting to the trycache.

So it will fail the evaluation. Have you seen this before? Or is there anything I can do to help this? Randomly pause execution to make sure the second thread/call has committed to cache?

thenerdynerd avatar Sep 03 '21 09:09 thenerdynerd

Taking a guess here, but it would depend on the hosting, are you running it in an Azure Function or something else? I believe the actual caching is done by Microsoft.Extensions.Caching. I believe it is all thread safe and as it is the in memory cache it should be once added it is in. You could be having some sort of race condition of course.

An option, might be a bit dirty, is to use a lock, mutex or a semaphore to make the area so that only one thread can access the Add or TryGetValue at once.

Maybe more of a question for @alastairtree

gamblen avatar Sep 03 '21 09:09 gamblen

Thanks for that - It's an Azure App service using .net Core. But I believe it is some sort of race condition. Thanks very much. If there's any tips around this would be great! Thanks again for your help.

thenerdynerd avatar Sep 03 '21 09:09 thenerdynerd

If you ever use the Scale out on the App Service, then the cache will be in each running VM, totally isolated. If that happens you would need to use something like Redis.

If it is all running in one process you can use lock. Inter-process would require mutex.

Sorry I have no experience running LazyCache, or any caching in a AppService.

gamblen avatar Sep 03 '21 09:09 gamblen

I am seeing the exact same issue on an Azure App Service using .NET (Core) 6. The cache is in memory on a single instance and TryGetValue<T> returns null whereas Get<T> DOES return the correct value.

If I pre-load the cache with values, the TryGetValue works fine, but after any GetOrAdd with a cache miss and reload via the delegate in GetOrAdd, the TryGetValue fails for that single item and works for the rest.

jjjulien avatar Sep 28 '22 22:09 jjjulien

I have the same issue in both Azure App Services and in a local environment (NET 6) when using Get (returns the proper value) and TryGetValue (false/null) on MemoryCache, which seems also to be used in the background by LazyCache. Thus, this is an issue of MemoryCache, not LazyCache itself. I am still searching for the right place to file an issue ...

bb-froggy avatar Oct 13 '22 08:10 bb-froggy

Oh, in my case, I used the Generic TryGetValue, but the non-generic Get. And I used the wrong type for TryGetValue, which results in the false/null, whereas the non-generic one returned the correct object. So, my fault. Maybe it is the same issue for you, @jjjulien?

bb-froggy avatar Oct 13 '22 09:10 bb-froggy

@bb-froggy I use generic Get and TryGetValue. The same type is being used for both. TryGetValue works w/out issue until the value is updated using the passed-in delegate, then it fails to return a value (but the Get<T> works).

jjjulien avatar Oct 13 '22 16:10 jjjulien

I'm seeing the same failure to return a value from TryGetValue with LazyCache v2.4.0. Here is a simple repro:

var cache = new CachingService();

string key1 = "mykey1";
var value1 = cache.GetOrAdd(key1, () => "my value1");  // <-- returns "my value1"
bool result1 = cache.TryGetValue(key1, out string value1a); // <-- returns false and null
var value1b = cache.Get<string>(key1); // <-- returns "my value1"

mattmpathic avatar Jun 07 '23 14:06 mattmpathic

I am also finding TryGetValue() gives me some internal object while Get() works. I think the issue is (from CachingService.cs):

 public virtual T Get<T>(string key)
        {
            ValidateKey(key);

            var item = CacheProvider.Get(key);

            return GetValueFromLazy<T>(item, out _);
        }
        public virtual bool TryGetValue<T>(string key, out T value)
        {
            ValidateKey(key);

            return CacheProvider.TryGetValue(key, out value);
        }

TryGetValue is just calling the CacheProvider' rather than GetValueFromLazy`. Is this a bug?

DavidThielen avatar Mar 20 '24 23:03 DavidThielen