VahidN / EFSecondLevelCache.Core

Entity Framework Core Second Level Caching Library
Apache License 2.0
326 stars 50 forks source link

Thread safe caching #14

Closed ColinRaaijmakers closed 6 years ago

ColinRaaijmakers commented 6 years ago

Summary of the issue

In a multi threaded application i receive random the following error:

System.InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe. at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()

Environment

The in-use version: EFSecondLevelCache.Core 1.4.0
Operating system: Windows 10
IDE: Visual Studio 2017

Example code/Steps to reproduce:

public async Task<List<T>> GetAllAsync()
{
    var cacheKey = $"{typeof(T).Name}.{nameof(this.GetAllAsync)}";

    return await this.dbContext
        .Query<T>()
        .Cacheable(cacheKey)
        .ToListAsync();
}

Output:

InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider+ExceptionInterceptor+EnumeratorExceptionInterceptor.MoveNext()
System.Collections.Generic.LargeArrayBuilder.AddRange(IEnumerable<T> items)
System.Collections.Generic.EnumerableHelpers.ToArray<T>(IEnumerable<T> source)
System.Linq.Enumerable.ToArray<TSource>(IEnumerable<TSource> source)
EFSecondLevelCache.Core.EFCachedQueryProvider.Materialize(Expression expression, Func<object> materializer)
EFSecondLevelCache.Core.EFCachedQueryable.System.Collections.Generic.IEnumerable<TType>.GetEnumerator()
EFSecondLevelCache.Core.EFCachedQueryable.get_AsyncEnumerable()
Microsoft.EntityFrameworkCore.Extensions.Internal.QueryableExtensions.AsAsyncEnumerable<TSource>(IQueryable<TSource> source)
Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync<TSource>(IQueryable<TSource> source, CancellationToken cancellationToken)
Cre8ion.Database.EntityFramework.CachedRepository+<GetAllAsync>d__3.MoveNext()

Solution

I have downloaded the source code and added SyncLocks to EFCachedQueryProvider in the Materialize method.

This is working as expected.

How can I commit this change to your project on Github?

Best regards, Colin

VahidN commented 6 years ago

Thanks for the report. Added the syncLock via #https://github.com/VahidN/EFSecondLevelCache.Core/commit/8d5341e739674fdac1bf45f5168be40dda251cdc

ryazanov-dmitry commented 4 years ago

Hi guys. Still having issues with same error. Are you sure that we can use lock that easily within async context? I have multiple queries which some use Cacheable() and some not. When i call all this queries in async then i receive above error which comming from Materialize() method where lock resides. @VahidN

VahidN commented 4 years ago

This error basically means you are misusing the EF Core. More info

ryazanov-dmitry commented 4 years ago

But if I remove .cacheable() everything works

VahidN commented 4 years ago

I should be able to reproduce it, to debug it. Please provide a basic sample which shows this behavior.

ryazanov-dmitry commented 4 years ago

I can bake some code to reproduce latter, but for now you can check this article https://blog.cdemi.io/async-waiting-inside-c-sharp-locks/ Basically you can replace lock with semaphores and everything should work

lock[bot] commented 4 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related problems.