You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
EntityFrameworkCore.Jet/test/EFCore.Jet.FunctionalTests/DbContextPoolingTest.cs

418 lines
13 KiB
C#

using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using EntityFrameworkCore.Jet;
using Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.TestUtilities.Xunit;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using Xunit.Abstractions;
// ReSharper disable UnusedAutoPropertyAccessor.Local
// ReSharper disable UnusedMember.Local
// ReSharper disable ClassNeverInstantiated.Local
// ReSharper disable VirtualMemberCallInConstructor
namespace EntityFramework.Jet.FunctionalTests
{
public class DbContextPoolingTest
{
private static IServiceProvider BuildServiceProvider<TContext>(int poolSize = 32)
where TContext : DbContext
=> new ServiceCollection()
.AddEntityFrameworkJet()
.AddDbContextPool<TContext>(
ob => ob.UseJet(JetTestStore.NorthwindConnectionString),
poolSize)
.BuildServiceProvider();
private class PooledContext : DbContext
{
public static int DisposedCount;
public static int InstanceCount;
public static bool ModifyOptions;
public PooledContext(DbContextOptions options)
: base(options)
{
Interlocked.Increment(ref InstanceCount);
ChangeTracker.AutoDetectChangesEnabled = false;
Database.AutoTransactionsEnabled = false;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (ModifyOptions)
{
optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
}
public DbSet<Customer> Customers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.Entity<Customer>().ToTable("Customers");
public override void Dispose()
{
base.Dispose();
Interlocked.Increment(ref DisposedCount);
}
public class Customer
{
public string CustomerId { get; set; }
public string CompanyName { get; set; }
}
}
[Fact]
public void Invalid_pool_size()
{
Assert.Throws<ArgumentOutOfRangeException>(
() => BuildServiceProvider<PooledContext>(0));
Assert.Throws<ArgumentOutOfRangeException>(
() => BuildServiceProvider<PooledContext>(-1));
}
[Fact]
public void Options_modified_in_on_configuring()
{
var serviceProvider = BuildServiceProvider<PooledContext>();
var serviceScope1 = serviceProvider.CreateScope();
PooledContext.ModifyOptions = true;
try
{
Assert.Throws<InvalidOperationException>(
() => serviceScope1.ServiceProvider.GetService<PooledContext>());
}
finally
{
PooledContext.ModifyOptions = false;
}
}
private class BadCtorContext : DbContext
{
}
[Fact]
public void Throws_when_used_with_parameterless_constructor_context()
{
var serviceCollection = new ServiceCollection();
Assert.Equal(CoreStrings.DbContextMissingConstructor(nameof(BadCtorContext)),
Assert.Throws<ArgumentException>(
() => serviceCollection.AddDbContextPool<BadCtorContext>(
_ => { })).Message);
Assert.Equal(CoreStrings.DbContextMissingConstructor(nameof(BadCtorContext)),
Assert.Throws<ArgumentException>(
() => serviceCollection.AddDbContextPool<BadCtorContext>(
(_, __) => { })).Message);
}
[Fact]
public void Can_pool_non_derived_context()
{
var serviceProvider = BuildServiceProvider<DbContext>();
var serviceScope1 = serviceProvider.CreateScope();
var context1 = serviceScope1.ServiceProvider.GetService<DbContext>();
var serviceScope2 = serviceProvider.CreateScope();
var context2 = serviceScope2.ServiceProvider.GetService<DbContext>();
Assert.NotSame(context1, context2);
serviceScope1.Dispose();
serviceScope2.Dispose();
var serviceScope3 = serviceProvider.CreateScope();
var context3 = serviceScope3.ServiceProvider.GetService<DbContext>();
Assert.Same(context1, context3);
var serviceScope4 = serviceProvider.CreateScope();
var context4 = serviceScope4.ServiceProvider.GetService<DbContext>();
Assert.Same(context2, context4);
}
[Fact]
public void Contexts_are_pooled()
{
var serviceProvider = BuildServiceProvider<PooledContext>();
var serviceScope1 = serviceProvider.CreateScope();
var context1 = serviceScope1.ServiceProvider.GetService<PooledContext>();
var serviceScope2 = serviceProvider.CreateScope();
var context2 = serviceScope2.ServiceProvider.GetService<PooledContext>();
Assert.NotSame(context1, context2);
serviceScope1.Dispose();
serviceScope2.Dispose();
var serviceScope3 = serviceProvider.CreateScope();
var context3 = serviceScope3.ServiceProvider.GetService<PooledContext>();
Assert.Same(context1, context3);
var serviceScope4 = serviceProvider.CreateScope();
var context4 = serviceScope4.ServiceProvider.GetService<PooledContext>();
Assert.Same(context2, context4);
}
[Fact]
public void Context_configuration_is_reset()
{
var serviceProvider = BuildServiceProvider<PooledContext>();
var serviceScope = serviceProvider.CreateScope();
var context1 = serviceScope.ServiceProvider.GetService<PooledContext>();
context1.ChangeTracker.AutoDetectChangesEnabled = true;
context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
context1.Database.AutoTransactionsEnabled = true;
serviceScope.Dispose();
serviceScope = serviceProvider.CreateScope();
var context2 = serviceScope.ServiceProvider.GetService<PooledContext>();
Assert.Same(context1, context2);
Assert.False(context2.ChangeTracker.AutoDetectChangesEnabled);
Assert.Equal(QueryTrackingBehavior.TrackAll, context2.ChangeTracker.QueryTrackingBehavior);
Assert.False(context2.Database.AutoTransactionsEnabled);
}
[ConditionalFact]
[FrameworkSkipCondition(RuntimeFrameworks.CoreCLR, SkipReason = "Failing after netcoreapp2.0 upgrade")]
public void State_manager_is_reset()
{
var serviceProvider = BuildServiceProvider<PooledContext>();
var serviceScope = serviceProvider.CreateScope();
var context1 = serviceScope.ServiceProvider.GetService<PooledContext>();
var weakRef = new WeakReference(context1.Customers.First(c => c.CustomerId == "ALFKI"));
Assert.Equal(1, context1.ChangeTracker.Entries().Count());
serviceScope.Dispose();
serviceScope = serviceProvider.CreateScope();
var context2 = serviceScope.ServiceProvider.GetService<PooledContext>();
Assert.Same(context1, context2);
Assert.Empty(context2.ChangeTracker.Entries());
GC.Collect();
Assert.False(weakRef.IsAlive);
}
[Fact]
public void Pool_disposes_context_when_context_not_pooled()
{
var serviceProvider = BuildServiceProvider<PooledContext>(poolSize: 1);
var serviceScope1 = serviceProvider.CreateScope();
serviceScope1.ServiceProvider.GetService<PooledContext>();
var serviceScope2 = serviceProvider.CreateScope();
var context = serviceScope2.ServiceProvider.GetService<PooledContext>();
serviceScope1.Dispose();
serviceScope2.Dispose();
Assert.Throws<ObjectDisposedException>(() => context.Customers.ToList());
}
[Fact]
public void Pool_disposes_contexts_when_disposed()
{
var serviceProvider = BuildServiceProvider<PooledContext>();
var serviceScope = serviceProvider.CreateScope();
var context = serviceScope.ServiceProvider.GetService<PooledContext>();
serviceScope.Dispose();
((IDisposable)serviceProvider).Dispose();
Assert.Throws<ObjectDisposedException>(() => context.Customers.ToList());
}
[Fact]
public void Object_in_pool_is_disposed()
{
var serviceProvider = BuildServiceProvider<PooledContext>();
var serviceScope = serviceProvider.CreateScope();
var context = serviceScope.ServiceProvider.GetService<PooledContext>();
serviceScope.Dispose();
Assert.Throws<ObjectDisposedException>(() => context.Customers.ToList());
}
[Fact]
public void Provider_services_are_reset()
{
var serviceProvider = BuildServiceProvider<PooledContext>();
var serviceScope = serviceProvider.CreateScope();
var context1 = serviceScope.ServiceProvider.GetService<PooledContext>();
context1.Database.BeginTransaction();
Assert.NotNull(context1.Database.CurrentTransaction);
serviceScope.Dispose();
serviceScope = serviceProvider.CreateScope();
var context2 = serviceScope.ServiceProvider.GetService<PooledContext>();
Assert.Same(context1, context2);
Assert.Null(context2.Database.CurrentTransaction);
context2.Database.BeginTransaction();
Assert.NotNull(context2.Database.CurrentTransaction);
serviceScope.Dispose();
serviceScope = serviceProvider.CreateScope();
var context3 = serviceScope.ServiceProvider.GetService<PooledContext>();
Assert.Same(context2, context3);
Assert.Null(context3.Database.CurrentTransaction);
}
[ConditionalFact(Skip = "Investigate fail of last assert - 2.1")]
public async Task Concurrency_test()
{
PooledContext.InstanceCount = 0;
PooledContext.DisposedCount = 0;
var results = WriteResults();
var serviceProvider = BuildServiceProvider<PooledContext>();
async Task ProcessRequest()
{
while (_stopwatch.IsRunning)
{
using (var serviceScope = serviceProvider.CreateScope())
{
var context = serviceScope.ServiceProvider.GetService<PooledContext>();
await context.Customers.AsNoTracking().FirstAsync(c => c.CustomerId == "ALFKI");
Interlocked.Increment(ref _requests);
}
}
}
var tasks = new Task[32];
for (var i = 0; i < 32; i++)
{
tasks[i] = ProcessRequest();
}
await Task.WhenAll(tasks);
await results;
Assert.Equal(_requests, PooledContext.DisposedCount);
Assert.InRange(PooledContext.InstanceCount, 32, 64);
}
private readonly TimeSpan _duration = TimeSpan.FromSeconds(10);
private int _stopwatchStarted;
private readonly Stopwatch _stopwatch = new Stopwatch();
private long _requests;
private async Task WriteResults()
{
if (Interlocked.Exchange(ref _stopwatchStarted, 1) == 0)
{
_stopwatch.Start();
}
var lastRequests = (long)0;
var lastElapsed = TimeSpan.Zero;
while (_stopwatch.IsRunning)
{
await Task.Delay(TimeSpan.FromSeconds(1));
var currentRequests = _requests - lastRequests;
lastRequests = _requests;
var elapsed = _stopwatch.Elapsed;
var currentElapsed = elapsed - lastElapsed;
lastElapsed = elapsed;
_testOutputHelper?
.WriteLine(
$"[{DateTime.Now:HH:mm:ss.fff}] Requests: {_requests}, "
+ $"RPS: {Math.Round(currentRequests / currentElapsed.TotalSeconds)}");
if (elapsed > _duration)
{
_testOutputHelper?.WriteLine("");
_testOutputHelper?.WriteLine($"Average RPS: {Math.Round(_requests / elapsed.TotalSeconds)}");
_stopwatch.Stop();
}
}
}
private readonly ITestOutputHelper _testOutputHelper = null;
// ReSharper disable once UnusedParameter.Local
public DbContextPoolingTest(ITestOutputHelper testOutputHelper)
{
//_testOutputHelper = testOutputHelper;
}
}
}