Skip to content

Commit 590e5f8

Browse files
committed
Ensure that ILoggerFactory is re-captured for each context Instance
Otherwise the wrong (possibly disposed) logger factory can be used. Fixes part of ASP.NET build break.
1 parent 6f1cad4 commit 590e5f8

File tree

10 files changed

+204
-68
lines changed

10 files changed

+204
-68
lines changed

src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations)
639639
}
640640

641641
var dummyLogger = new DiagnosticsLogger<DbLoggerCategory.Model>(
642-
new LoggerFactory(),
642+
new ScopedLoggerFactory(new LoggerFactory(), dispose: true),
643643
new LoggingOptions(),
644644
new DiagnosticListener(""));
645645

src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ protected virtual KeyBuilder VisitPrimaryKey([NotNull] EntityTypeBuilder builder
487487
if (property != null)
488488
{
489489
var dummyLogger = new DiagnosticsLogger<DbLoggerCategory.Model>(
490-
new LoggerFactory(),
490+
new ScopedLoggerFactory(new LoggerFactory(), dispose: true),
491491
new LoggingOptions(),
492492
new DiagnosticListener(""));
493493

src/EFCore.Relational/Metadata/Conventions/Internal/RelationalConventionSetBuilderDependencies.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ public RelationalConventionSetBuilderDependencies(
6969

7070
TypeMappingSource = typeMappingSource;
7171
Logger = logger
72-
?? new DiagnosticsLogger<DbLoggerCategory.Model>(new LoggerFactory(), new LoggingOptions(), new DiagnosticListener(""));
72+
?? new DiagnosticsLogger<DbLoggerCategory.Model>(
73+
new ScopedLoggerFactory(new LoggerFactory(), dispose: true),
74+
new LoggingOptions(),
75+
new DiagnosticListener(""));
7376
Context = currentContext;
7477
SetFinder = setFinder;
7578
}

src/EFCore/Infrastructure/CoreOptionsExtension.cs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -345,12 +345,6 @@ public virtual CoreOptionsExtension WithCacheServiceProvider(bool cacheServicePr
345345
/// <returns> False since no database provider is registered. </returns>
346346
public virtual bool ApplyServices(IServiceCollection services)
347347
{
348-
var loggerFactory = GetLoggerFactory();
349-
if (loggerFactory != null)
350-
{
351-
services.AddScoped<ILoggerFactory>(_ => new ExternalLoggerFactory(loggerFactory));
352-
}
353-
354348
var memoryCache = GetMemoryCache();
355349
if (memoryCache != null)
356350
{
@@ -363,9 +357,6 @@ public virtual bool ApplyServices(IServiceCollection services)
363357
private IMemoryCache GetMemoryCache()
364358
=> MemoryCache;
365359

366-
private ILoggerFactory GetLoggerFactory()
367-
=> LoggerFactory ?? ApplicationServiceProvider?.GetService<ILoggerFactory>();
368-
369360
/// <summary>
370361
/// Returns a hash code created from any options that would cause a new <see cref="IServiceProvider" />
371362
/// to be needed.

src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices()
216216
TryAdd<ICoreConventionSetBuilder, CoreConventionSetBuilder>();
217217
TryAdd<IModelCustomizer, ModelCustomizer>();
218218
TryAdd<IModelCacheKeyFactory, ModelCacheKeyFactory>();
219-
TryAdd<ILoggerFactory, LoggerFactory>();
219+
TryAdd<ILoggerFactory>(p => ScopedLoggerFactory.Create(p, null));
220220
TryAdd<IModelSource, ModelSource>();
221221
TryAdd<IInternalEntityEntryFactory, InternalEntityEntryFactory>();
222222
TryAdd<IInternalEntityEntrySubscriber, InternalEntityEntrySubscriber>();

src/EFCore/Internal/ExternalLoggerFactory.cs

Lines changed: 0 additions & 50 deletions
This file was deleted.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using JetBrains.Annotations;
6+
using Microsoft.EntityFrameworkCore.Infrastructure;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.Logging;
9+
10+
namespace Microsoft.EntityFrameworkCore.Internal
11+
{
12+
public class ScopedLoggerFactory : ILoggerFactory
13+
{
14+
private readonly ILoggerFactory _underlyingFactory;
15+
private readonly bool _dispose;
16+
17+
/// <summary>
18+
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
19+
/// directly from your code. This API may change or be removed in future releases.
20+
/// </summary>
21+
public ScopedLoggerFactory(
22+
[NotNull] ILoggerFactory loggerFactory,
23+
bool dispose)
24+
{
25+
_underlyingFactory = loggerFactory;
26+
_dispose = dispose;
27+
}
28+
29+
/// <summary>
30+
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
31+
/// directly from your code. This API may change or be removed in future releases.
32+
/// </summary>
33+
public static ScopedLoggerFactory Create(
34+
[NotNull] IServiceProvider internalServiceProvider,
35+
[CanBeNull] IDbContextOptions contextOptions)
36+
{
37+
var coreOptions
38+
= (contextOptions ??
39+
internalServiceProvider.GetService<IDbContextOptions>())
40+
?.FindExtension<CoreOptionsExtension>();
41+
42+
if (coreOptions != null)
43+
{
44+
if (coreOptions.LoggerFactory != null)
45+
{
46+
return new ScopedLoggerFactory(coreOptions.LoggerFactory, dispose: false);
47+
}
48+
49+
var applicationServiceProvider = coreOptions.ApplicationServiceProvider;
50+
if (applicationServiceProvider != null
51+
&& applicationServiceProvider != internalServiceProvider)
52+
{
53+
return new ScopedLoggerFactory(applicationServiceProvider.GetService<ILoggerFactory>(), dispose: false);
54+
}
55+
}
56+
57+
return new ScopedLoggerFactory(new LoggerFactory(), dispose: true);
58+
}
59+
60+
/// <summary>
61+
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
62+
/// directly from your code. This API may change or be removed in future releases.
63+
/// </summary>
64+
public virtual void Dispose()
65+
{
66+
if (_dispose)
67+
{
68+
_underlyingFactory.Dispose();
69+
}
70+
}
71+
72+
/// <summary>
73+
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
74+
/// directly from your code. This API may change or be removed in future releases.
75+
/// </summary>
76+
public virtual ILogger CreateLogger(string categoryName)
77+
=> _underlyingFactory.CreateLogger(categoryName);
78+
79+
/// <summary>
80+
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
81+
/// directly from your code. This API may change or be removed in future releases.
82+
/// </summary>
83+
public virtual void AddProvider(ILoggerProvider provider)
84+
=> _underlyingFactory.AddProvider(provider);
85+
}
86+
}

src/EFCore/Internal/ServiceProviderCache.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Concurrent;
66
using System.Collections.Generic;
7+
using System.Diagnostics;
78
using System.Linq;
89
using JetBrains.Annotations;
910
using Microsoft.EntityFrameworkCore.Diagnostics;
@@ -100,8 +101,13 @@ public virtual IServiceProvider GetOrAdd([NotNull] IDbContextOptions options, bo
100101

101102
using (var scope = serviceProvider.CreateScope())
102103
{
103-
var logger =
104-
scope.ServiceProvider.GetRequiredService<IDiagnosticsLogger<DbLoggerCategory.Infrastructure>>();
104+
var scopedProvider = scope.ServiceProvider;
105+
106+
// Because IDbContextOptions cannot yet be resolved from the internal provider
107+
var logger = new DiagnosticsLogger<DbLoggerCategory.Infrastructure>(
108+
ScopedLoggerFactory.Create(scopedProvider, options),
109+
scopedProvider.GetService<ILoggingOptions>(),
110+
scopedProvider.GetService<DiagnosticSource>());
105111

106112
if (_configurations.Count == 0)
107113
{

test/EFCore.Specification.Tests/TestUtilities/ListLoggerFactory.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ namespace Microsoft.EntityFrameworkCore.TestUtilities
1313
public class ListLoggerFactory : ILoggerFactory
1414
{
1515
private readonly Func<string, bool> _shouldLogCategory;
16+
private bool _disposed;
1617

1718
public ListLoggerFactory()
1819
: this(_ => true)
@@ -37,16 +38,30 @@ public void SetTestOutputHelper(ITestOutputHelper testOutputHelper)
3738
}
3839

3940
public virtual ILogger CreateLogger(string name)
40-
=> !_shouldLogCategory(name)
41+
{
42+
CheckDisposed();
43+
44+
return !_shouldLogCategory(name)
4145
? (ILogger)NullLogger.Instance
4246
: Logger;
47+
}
48+
49+
private void CheckDisposed()
50+
{
51+
if (_disposed)
52+
{
53+
throw new ObjectDisposedException(nameof(ListLoggerFactory));
54+
}
55+
}
4356

4457
public void AddProvider(ILoggerProvider provider)
4558
{
59+
CheckDisposed();
4660
}
4761

4862
public void Dispose()
4963
{
64+
_disposed = true;
5065
}
5166

5267
protected class ListLogger : ILogger

test/EFCore.Tests/DbContextServicesTest.cs

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,21 @@ protected internal override void OnConfiguring(DbContextOptionsBuilder optionsBu
119119

120120
private class MyLoggerProvider : ILoggerProvider
121121
{
122-
public ILogger CreateLogger(string categoryName) => new MyListLogger(Log);
122+
private bool _disposed;
123+
124+
public ILogger CreateLogger(string categoryName)
125+
{
126+
if (_disposed)
127+
{
128+
throw new ObjectDisposedException(nameof(MyLoggerProvider));
129+
}
130+
131+
return new MyListLogger(Log);
132+
}
123133

124134
public void Dispose()
125135
{
136+
_disposed = true;
126137
}
127138

128139
private class MyListLogger : ILogger
@@ -164,6 +175,58 @@ public void Log<TState>(
164175
}
165176
}
166177

178+
[Theory]
179+
[InlineData(ServiceLifetime.Scoped)]
180+
[InlineData(ServiceLifetime.Singleton)]
181+
public void Logger_factory_registered_on_application_service_provider_is_not_disposed(ServiceLifetime optionsLifetime)
182+
{
183+
for (var i = 0; i < 2; i++)
184+
{
185+
ILoggerFactory loggerFactory;
186+
187+
using (var appServiceProvider
188+
= new ServiceCollection()
189+
.AddScoped<Random>()
190+
.AddDbContext<ConstructorTestContext1A>(
191+
b => b.UseInMemoryDatabase("Scratch"), optionsLifetime: optionsLifetime)
192+
.BuildServiceProvider())
193+
{
194+
loggerFactory = appServiceProvider.GetService<ILoggerFactory>();
195+
Random scopedExternalService;
196+
197+
using (var scope = appServiceProvider.CreateScope())
198+
{
199+
var context = scope.ServiceProvider.GetRequiredService<ConstructorTestContext1A>();
200+
201+
// Should not throw
202+
var _ = context.Model;
203+
204+
Assert.Same(loggerFactory, scope.ServiceProvider.GetService<ILoggerFactory>());
205+
206+
scopedExternalService = scope.ServiceProvider.GetService<Random>();
207+
Assert.NotNull(scopedExternalService);
208+
}
209+
210+
using (var scope = appServiceProvider.CreateScope())
211+
{
212+
var context = scope.ServiceProvider.GetRequiredService<ConstructorTestContext1A>();
213+
214+
// Should not throw
215+
var _ = context.Model;
216+
217+
Assert.Same(loggerFactory, scope.ServiceProvider.GetService<ILoggerFactory>());
218+
219+
Assert.NotSame(scopedExternalService, scope.ServiceProvider.GetService<Random>());
220+
}
221+
222+
// Should not throw
223+
loggerFactory.CreateLogger("MyLogger");
224+
}
225+
226+
Assert.Throws<ObjectDisposedException>(() => loggerFactory.CreateLogger("MyLogger"));
227+
}
228+
}
229+
167230
[Fact]
168231
public void Can_use_GetInfrastructure_with_inferred_generic_to_get_service_provider()
169232
{
@@ -175,6 +238,28 @@ public void Can_use_GetInfrastructure_with_inferred_generic_to_get_service_provi
175238
}
176239
}
177240

241+
[Fact]
242+
public void Logger_factory_registered_on_internal_service_provider_is_not_disposed()
243+
{
244+
var serviceProvider
245+
= new ServiceCollection()
246+
.AddEntityFrameworkInMemoryDatabase()
247+
.BuildServiceProvider();
248+
249+
var appServiceProvider
250+
= new ServiceCollection()
251+
.AddDbContext<ConstructorTestContext1A>(
252+
b => b.UseInMemoryDatabase("Scratch")
253+
.UseInternalServiceProvider(serviceProvider))
254+
.BuildServiceProvider();
255+
256+
var context = appServiceProvider.GetRequiredService<ConstructorTestContext1A>();
257+
var _ = context.Model;
258+
259+
context = appServiceProvider.GetRequiredService<ConstructorTestContext1A>();
260+
_ = context.Model;
261+
}
262+
178263
[Fact]
179264
public void Each_context_gets_new_scoped_services()
180265
{
@@ -306,7 +391,7 @@ public void Required_low_level_services_are_added_if_needed()
306391
new EntityFrameworkServicesBuilder(serviceCollection).TryAddCoreServices();
307392
var provider = serviceCollection.BuildServiceProvider();
308393

309-
Assert.IsType<LoggerFactory>(provider.GetRequiredService<ILoggerFactory>());
394+
Assert.IsType<ScopedLoggerFactory>(provider.GetRequiredService<ILoggerFactory>());
310395
}
311396

312397
[Fact]

0 commit comments

Comments
 (0)