0

I have a lot of NullReferenceException exceptions on the line if (foosDictionary.TryGetValue(id, out var foo)) and I cannot in any way reproduce the issue.

Stack trace:

System.NullReferenceException: Object reference not set to an instance of an object.
   at MyProject.Service.Baz(Guid id) in CustomClass.cs:line 900
   at MyProject.Service.BazFeature(Guid id) in CustomClassCaller.cs:line 288

line 900 is the line in question. It is a direct reference to the dictionary: if (foosDictionary.TryGetValue(id, out var foo)).

Below is the code that leads to the problem line:

public Foo Baz(Guid id) 
{

Func<Task<Dictionary<Guid, Foo>>> createDictionary =
    async () => (await repository.GetFoos()).ToDictionary(sm => sm.Id);

var foosDictionary = await _cache.GetOrCreateAsync<Dictionary<Guid, Foo>>(
    "cacheKey",
    async cacheEntry =>
    {
        cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(15);
        return await createDictionary();
    });

if (foosDictionary.TryGetValue(id, out var foo))
{
    return foo;
}

}
// also there's the remaining code, but it shouldn't execute and it is lower than line 900. Anyway, here it is.

var newDictionary = await createDictionary();

if (newDictionary.TryGetValue(id, out var newFoo))
{
    fooDictionary = newDictionary;

    return newFoo;
}

throw new Exception("Error");

The Usual Suspects are:

  • await repository.GetFoos() - it uses Dapper.QueryAsync method to get IEnumerable<Foo> and it can't return null. Anyway, I tried hardcoding null return - and goes the next step
  • .ToDictionary() - it will throw on null. And it does if I hardcode the null return. Also it has different stacktrace and points to GetOrCreateAsync, not the line of code in question.
  • GetOrCreateAsync - all the other suspects will throw and give different stack trace so this must be it. This method returns null, but I can't figure out how and why.

Like, if there is null on the cache key, it should go and create the value. createDictionary can't return null so it's out of the question. So...how does that even happen? Why?

Memory cache is registered in Startup like this: services.AddMemoryCache(). I don't know whether it's Scoped or Singleton and the Microsoft docs don't address it in a straightforward way.

The app is ASP.NET Core 3.1, GetOrCreateAsync extension method is from v6.0.0, but the actual implementation (or so it seems) gets passed as v3.1.0.

How can I resolve the issue? Can GetOrCreateAsync really return null in some cases? If so, how to reproduce them?

PS. I've thought about thread 1 getting the entry and then thread 2 somehow nullifying it, but that shouldn't work since thread 1 already has the reference to the dictionary. Right?

19
  • Full stack trace, please. It seems possible that you're experiencing a threading issue accessing the dictionary itself. Commented Jul 8, 2022 at 8:35
  • I can't share the whole stack trace. I'll try to add as much as I can. Commented Jul 8, 2022 at 8:37
  • Sounds like an treading issue. Commented Jul 8, 2022 at 8:40
  • 1
    For diagnostics purposes, you could incorporate this code into your application and add some additional logging. In case you're wondering, .GetOrCreateAsync is an extension method (code here). Commented Jul 8, 2022 at 9:17
  • 1
    Ah, yes. I believe you can find the older version here. Once extracted, it seems to be under extensions-3.1.26/src/Caching/Memory/src. Commented Jul 8, 2022 at 9:49

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.