Skip to content

Commit

Permalink
Fix context pooling concurrency issue
Browse files Browse the repository at this point in the history
Fixes #26202

(cherry picked from commit 8e09d65)
  • Loading branch information
roji committed Oct 4, 2021
1 parent 897e63f commit 7f6147b
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 16 deletions.
18 changes: 10 additions & 8 deletions src/EFCore/DbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -822,11 +822,17 @@ public virtual void Dispose()
}
}

/// <summary>
/// Releases the allocated resources for this context.
/// </summary>
public virtual ValueTask DisposeAsync()
=> DisposeSync() ? _serviceScope.DisposeAsyncIfAvailable() : default;

private bool DisposeSync()
{
if (_lease.IsActive)
{
if (_lease.ContextDisposed())
if (_lease.IsStandalone)
{
_disposed = true;

Expand All @@ -835,7 +841,10 @@ private bool DisposeSync()
ClearEvents();
}

var lease = _lease;
_lease = DbContextLease.InactiveLease;

lease.ContextDisposed();
}
}
else if (!_disposed)
Expand Down Expand Up @@ -863,13 +872,6 @@ private bool DisposeSync()
return false;
}

/// <summary>
/// Releases the allocated resources for this context.
/// </summary>
public virtual ValueTask DisposeAsync()
=> DisposeSync() ? _serviceScope.DisposeAsyncIfAvailable() : default;


private static readonly bool _dontClearEvents
= AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue23108", out var isEnabled) && isEnabled;

Expand Down
19 changes: 11 additions & 8 deletions src/EFCore/Internal/DbContextLease.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ namespace Microsoft.EntityFrameworkCore.Internal
public struct DbContextLease
{
private IDbContextPool _contextPool;
private readonly bool _standalone;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public bool IsStandalone { get; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -34,7 +41,7 @@ public struct DbContextLease
public DbContextLease([NotNull] IDbContextPool contextPool, bool standalone)
{
_contextPool = contextPool;
_standalone = standalone;
IsStandalone = standalone;

var context = _contextPool.Rent();
Context = context;
Expand Down Expand Up @@ -65,16 +72,12 @@ public bool IsActive
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public bool ContextDisposed()
public void ContextDisposed()
{
if (_standalone)
if (IsStandalone)
{
Release();

return true;
}

return false;
}

/// <summary>
Expand Down
27 changes: 27 additions & 0 deletions test/EFCore.SqlServer.FunctionalTests/DbContextPoolingTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,33 @@ private async Task WriteResults()
}
}

[ConditionalTheory]
[InlineData(false)]
[InlineData(true)]
public async Task Concurrency_test2(bool async)
{
var factory = BuildServiceProviderWithFactory<PooledContext>()
.GetService<IDbContextFactory<PooledContext>>();

await Task.WhenAll(
Enumerable.Range(0, 10).Select(_ => Task.Run(async () =>
{
for (var j = 0; j < 1_000_000; j++)
{
var ctx = factory.CreateDbContext();
if (async)
{
await ctx.DisposeAsync();
}
else
{
ctx.Dispose();
}
}
})));
}
private async Task Dispose(IDisposable disposable, bool async)
{
if (async)
Expand Down

0 comments on commit 7f6147b

Please sign in to comment.