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 usesDapper.QueryAsyncmethod to getIEnumerable<Foo>and it can't return null. Anyway, I tried hardcoding null return - and goes the next step.ToDictionary()- it will throw onnull. And it does if I hardcode the null return. Also it has different stacktrace and points toGetOrCreateAsync, 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 returnsnull, 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?
.GetOrCreateAsyncis an extension method (code here).extensions-3.1.26/src/Caching/Memory/src.