mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-13 06:04:30 +08:00
fix(core): 修复容器释放与基准资源泄漏
- 修复 MicrosoftDiContainer 的 IDisposable 释放逻辑、根 ServiceProvider 清理与释放后访问保护 - 更新 CQRS benchmarks 的容器 cleanup,并补齐 RequestStartupBenchmarks 的冷启动容器释放路径 - 补充 Core 容器生命周期回归测试并归档 issue 327 的 ai-plan topic
This commit is contained in:
parent
588800bb7b
commit
0ec8aa076b
@ -10,7 +10,7 @@ namespace GFramework.Core.Abstractions.Ioc;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 依赖注入容器接口,定义了服务注册、解析和管理的基本操作
|
/// 依赖注入容器接口,定义了服务注册、解析和管理的基本操作
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IIocContainer : IContextAware
|
public interface IIocContainer : IContextAware, IDisposable
|
||||||
{
|
{
|
||||||
#region Register Methods
|
#region Register Methods
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,18 @@ public class IocContainerLifetimeTests
|
|||||||
public Guid Id { get; } = Guid.NewGuid();
|
public Guid Id { get; } = Guid.NewGuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class DisposableTestService : ITestService, IDisposable
|
||||||
|
{
|
||||||
|
public Guid Id { get; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
IsDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void RegisterSingleton_Should_Return_Same_Instance()
|
public void RegisterSingleton_Should_Return_Same_Instance()
|
||||||
{
|
{
|
||||||
@ -207,4 +219,31 @@ public class IocContainerLifetimeTests
|
|||||||
scope2.Dispose();
|
scope2.Dispose();
|
||||||
scope3.Dispose();
|
scope3.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Dispose_Should_Dispose_Resolved_Singleton_And_Block_Further_Use()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var container = new MicrosoftDiContainer();
|
||||||
|
container.RegisterSingleton<DisposableTestService, DisposableTestService>();
|
||||||
|
container.Freeze();
|
||||||
|
var service = container.GetRequired<DisposableTestService>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
container.Dispose();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(service.IsDisposed, Is.True);
|
||||||
|
Assert.Throws<ObjectDisposedException>(() => container.Get<DisposableTestService>());
|
||||||
|
Assert.Throws<ObjectDisposedException>(() => container.CreateScope());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Dispose_Should_Be_Idempotent()
|
||||||
|
{
|
||||||
|
var container = new MicrosoftDiContainer();
|
||||||
|
|
||||||
|
Assert.DoesNotThrow(container.Dispose);
|
||||||
|
Assert.DoesNotThrow(container.Dispose);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -760,4 +760,17 @@ public class MicrosoftDiContainerTests
|
|||||||
Assert.That(((IPrioritizedService)services[0]).Priority, Is.EqualTo(10));
|
Assert.That(((IPrioritizedService)services[0]).Priority, Is.EqualTo(10));
|
||||||
Assert.That(((IPrioritizedService)services[1]).Priority, Is.EqualTo(30));
|
Assert.That(((IPrioritizedService)services[1]).Priority, Is.EqualTo(30));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试容器释放后会阻止后续注册与解析,避免 benchmark 或短生命周期宿主继续使用已回收状态。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void Dispose_Should_Block_Subsequent_Registration_And_Query_Operations()
|
||||||
|
{
|
||||||
|
_container.Dispose();
|
||||||
|
|
||||||
|
Assert.Throws<ObjectDisposedException>(() => _container.Register(new TestService()));
|
||||||
|
Assert.Throws<ObjectDisposedException>(() => _container.Contains<TestService>());
|
||||||
|
Assert.Throws<ObjectDisposedException>(() => _container.GetAll<TestService>());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,19 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
{
|
{
|
||||||
#region Helper Methods
|
#region Helper Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查容器是否已释放,避免访问已经失效的服务提供者与同步原语。
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException">当容器已释放时抛出。</exception>
|
||||||
|
private void ThrowIfDisposed()
|
||||||
|
{
|
||||||
|
if (!_disposed) return;
|
||||||
|
|
||||||
|
const string objectName = nameof(MicrosoftDiContainer);
|
||||||
|
_logger.Warn("Attempted to use a disposed MicrosoftDiContainer.");
|
||||||
|
throw new ObjectDisposedException(objectName);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 检查容器是否已冻结,如果已冻结则抛出异常
|
/// 检查容器是否已冻结,如果已冻结则抛出异常
|
||||||
/// 用于保护注册操作的安全性
|
/// 用于保护注册操作的安全性
|
||||||
@ -57,6 +70,11 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private volatile bool _frozen;
|
private volatile bool _frozen;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 容器释放状态标志,true 表示容器已释放,不允许继续访问。
|
||||||
|
/// </summary>
|
||||||
|
private volatile bool _disposed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 读写锁,确保多线程环境下的线程安全操作
|
/// 读写锁,确保多线程环境下的线程安全操作
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -85,6 +103,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
/// <exception cref="InvalidOperationException">当容器已冻结或类型已被注册时抛出</exception>
|
/// <exception cref="InvalidOperationException">当容器已冻结或类型已被注册时抛出</exception>
|
||||||
public void RegisterSingleton<T>(T instance)
|
public void RegisterSingleton<T>(T instance)
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
var type = typeof(T);
|
var type = typeof(T);
|
||||||
_lock.EnterWriteLock();
|
_lock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
@ -119,6 +138,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
where TImpl : class, TService
|
where TImpl : class, TService
|
||||||
where TService : class
|
where TService : class
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_lock.EnterWriteLock();
|
_lock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -142,6 +162,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
where TImpl : class, TService
|
where TImpl : class, TService
|
||||||
where TService : class
|
where TService : class
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_lock.EnterWriteLock();
|
_lock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -165,6 +186,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
where TImpl : class, TService
|
where TImpl : class, TService
|
||||||
where TService : class
|
where TService : class
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_lock.EnterWriteLock();
|
_lock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -187,6 +209,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
/// <exception cref="InvalidOperationException">当容器已冻结时抛出</exception>
|
/// <exception cref="InvalidOperationException">当容器已冻结时抛出</exception>
|
||||||
public void RegisterPlurality(object instance)
|
public void RegisterPlurality(object instance)
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
var concreteType = instance.GetType();
|
var concreteType = instance.GetType();
|
||||||
var interfaces = concreteType.GetInterfaces();
|
var interfaces = concreteType.GetInterfaces();
|
||||||
|
|
||||||
@ -219,6 +242,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void RegisterPlurality<T>() where T : class
|
public void RegisterPlurality<T>() where T : class
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_lock.EnterWriteLock();
|
_lock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -262,6 +286,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
/// <exception cref="InvalidOperationException">当容器已冻结时抛出</exception>
|
/// <exception cref="InvalidOperationException">当容器已冻结时抛出</exception>
|
||||||
public void Register<T>(T instance)
|
public void Register<T>(T instance)
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_lock.EnterWriteLock();
|
_lock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -284,6 +309,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
/// <exception cref="InvalidOperationException">当容器已冻结时抛出</exception>
|
/// <exception cref="InvalidOperationException">当容器已冻结时抛出</exception>
|
||||||
public void Register(Type type, object instance)
|
public void Register(Type type, object instance)
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_lock.EnterWriteLock();
|
_lock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -307,6 +333,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
public void RegisterFactory<TService>(
|
public void RegisterFactory<TService>(
|
||||||
Func<IServiceProvider, TService> factory) where TService : class
|
Func<IServiceProvider, TService> factory) where TService : class
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_lock.EnterWriteLock();
|
_lock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -328,6 +355,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
||||||
public void RegisterCqrsPipelineBehavior<TBehavior>() where TBehavior : class
|
public void RegisterCqrsPipelineBehavior<TBehavior>() where TBehavior : class
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_lock.EnterWriteLock();
|
_lock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -392,6 +420,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
|
public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(assemblies);
|
ArgumentNullException.ThrowIfNull(assemblies);
|
||||||
|
ThrowIfDisposed();
|
||||||
var assemblyArray = assemblies.ToArray();
|
var assemblyArray = assemblies.ToArray();
|
||||||
foreach (var assembly in assemblyArray)
|
foreach (var assembly in assemblyArray)
|
||||||
{
|
{
|
||||||
@ -419,6 +448,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
/// <param name="configurator">服务配置委托</param>
|
/// <param name="configurator">服务配置委托</param>
|
||||||
public void ExecuteServicesHook(Action<IServiceCollection>? configurator = null)
|
public void ExecuteServicesHook(Action<IServiceCollection>? configurator = null)
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_lock.EnterWriteLock();
|
_lock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -464,6 +494,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
/// <returns>服务实例或null</returns>
|
/// <returns>服务实例或null</returns>
|
||||||
public T? Get<T>() where T : class
|
public T? Get<T>() where T : class
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_lock.EnterReadLock();
|
_lock.EnterReadLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -503,6 +534,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
/// <returns>服务实例或null</returns>
|
/// <returns>服务实例或null</returns>
|
||||||
public object? Get(Type type)
|
public object? Get(Type type)
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_lock.EnterReadLock();
|
_lock.EnterReadLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -593,6 +625,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
/// <returns>只读的服务实例列表</returns>
|
/// <returns>只读的服务实例列表</returns>
|
||||||
public IReadOnlyList<T> GetAll<T>() where T : class
|
public IReadOnlyList<T> GetAll<T>() where T : class
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_lock.EnterReadLock();
|
_lock.EnterReadLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -620,6 +653,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
public IReadOnlyList<object> GetAll(Type type)
|
public IReadOnlyList<object> GetAll(Type type)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(type);
|
ArgumentNullException.ThrowIfNull(type);
|
||||||
|
ThrowIfDisposed();
|
||||||
|
|
||||||
_lock.EnterReadLock();
|
_lock.EnterReadLock();
|
||||||
try
|
try
|
||||||
@ -750,6 +784,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
/// <returns>排序后的只读服务实例列表</returns>
|
/// <returns>排序后的只读服务实例列表</returns>
|
||||||
public IReadOnlyList<T> GetAllSorted<T>(Comparison<T> comparison) where T : class
|
public IReadOnlyList<T> GetAllSorted<T>(Comparison<T> comparison) where T : class
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
var list = GetAll<T>().ToList();
|
var list = GetAll<T>().ToList();
|
||||||
list.Sort(comparison);
|
list.Sort(comparison);
|
||||||
return list;
|
return list;
|
||||||
@ -816,6 +851,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
/// <returns>true表示包含该类型实例,false表示不包含</returns>
|
/// <returns>true表示包含该类型实例,false表示不包含</returns>
|
||||||
public bool Contains<T>() where T : class
|
public bool Contains<T>() where T : class
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_lock.EnterReadLock();
|
_lock.EnterReadLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -838,6 +874,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
/// <returns>true表示包含该实例,false表示不包含</returns>
|
/// <returns>true表示包含该实例,false表示不包含</returns>
|
||||||
public bool ContainsInstance(object instance)
|
public bool ContainsInstance(object instance)
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_lock.EnterReadLock();
|
_lock.EnterReadLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -855,6 +892,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_lock.EnterWriteLock();
|
_lock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -867,7 +905,9 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
|
|
||||||
GetServicesUnsafe.Clear();
|
GetServicesUnsafe.Clear();
|
||||||
_registeredInstances.Clear();
|
_registeredInstances.Clear();
|
||||||
|
(_provider as IDisposable)?.Dispose();
|
||||||
_provider = null;
|
_provider = null;
|
||||||
|
_frozen = false;
|
||||||
_logger.Info("Container cleared");
|
_logger.Info("Container cleared");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@ -882,6 +922,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Freeze()
|
public void Freeze()
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_lock.EnterWriteLock();
|
_lock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -917,6 +958,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
/// <exception cref="InvalidOperationException">当容器未冻结时抛出</exception>
|
/// <exception cref="InvalidOperationException">当容器未冻结时抛出</exception>
|
||||||
public IServiceScope CreateScope()
|
public IServiceScope CreateScope()
|
||||||
{
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
_lock.EnterReadLock();
|
_lock.EnterReadLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -938,5 +980,44 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 释放容器持有的服务提供者、注册状态和同步原语。
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 冻结后的根 <see cref="IServiceProvider" /> 会拥有 DI 创建的单例与作用域根缓存,因此 benchmark、
|
||||||
|
/// 测试宿主或短生命周期架构在结束时需要显式释放容器,避免这些对象与内部
|
||||||
|
/// <see cref="ReaderWriterLockSlim" /> 一起滞留。
|
||||||
|
/// 释放是幂等的;首次释放后所有后续访问都会抛出 <see cref="ObjectDisposedException" />。
|
||||||
|
/// </remarks>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lock.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
(_provider as IDisposable)?.Dispose();
|
||||||
|
_provider = null;
|
||||||
|
GetServicesUnsafe.Clear();
|
||||||
|
_registeredInstances.Clear();
|
||||||
|
_frozen = false;
|
||||||
|
_logger.Info("IOC Container disposed");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
_lock.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,6 +83,7 @@ public class NotificationBenchmarks
|
|||||||
[GlobalCleanup]
|
[GlobalCleanup]
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
|
_container.Dispose();
|
||||||
_serviceProvider.Dispose();
|
_serviceProvider.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -85,6 +85,7 @@ public class RequestBenchmarks
|
|||||||
[GlobalCleanup]
|
[GlobalCleanup]
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
|
_container.Dispose();
|
||||||
_serviceProvider.Dispose();
|
_serviceProvider.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -103,6 +103,8 @@ public class RequestInvokerBenchmarks
|
|||||||
[GlobalCleanup]
|
[GlobalCleanup]
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
|
_reflectionContainer.Dispose();
|
||||||
|
_generatedContainer.Dispose();
|
||||||
_serviceProvider.Dispose();
|
_serviceProvider.Dispose();
|
||||||
BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
|
BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,6 +101,7 @@ public class RequestPipelineBenchmarks
|
|||||||
[GlobalCleanup]
|
[GlobalCleanup]
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
|
_container.Dispose();
|
||||||
_serviceProvider.Dispose();
|
_serviceProvider.Dispose();
|
||||||
BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
|
BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ public class RequestStartupBenchmarks
|
|||||||
private static readonly ILogger RuntimeLogger = CreateLogger(nameof(RequestStartupBenchmarks));
|
private static readonly ILogger RuntimeLogger = CreateLogger(nameof(RequestStartupBenchmarks));
|
||||||
private static readonly BenchmarkRequest Request = new(Guid.NewGuid());
|
private static readonly BenchmarkRequest Request = new(Guid.NewGuid());
|
||||||
|
|
||||||
|
private MicrosoftDiContainer _container = null!;
|
||||||
private ServiceProvider _serviceProvider = null!;
|
private ServiceProvider _serviceProvider = null!;
|
||||||
private IMediator _mediatr = null!;
|
private IMediator _mediatr = null!;
|
||||||
private ICqrsRuntime _runtime = null!;
|
private ICqrsRuntime _runtime = null!;
|
||||||
@ -62,7 +63,8 @@ public class RequestStartupBenchmarks
|
|||||||
|
|
||||||
_serviceProvider = CreateMediatRServiceProvider();
|
_serviceProvider = CreateMediatRServiceProvider();
|
||||||
_mediatr = _serviceProvider.GetRequiredService<IMediator>();
|
_mediatr = _serviceProvider.GetRequiredService<IMediator>();
|
||||||
_runtime = CreateGFrameworkRuntime();
|
_container = CreateGFrameworkContainer();
|
||||||
|
_runtime = CreateGFrameworkRuntime(_container);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -84,6 +86,7 @@ public class RequestStartupBenchmarks
|
|||||||
[GlobalCleanup]
|
[GlobalCleanup]
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
|
_container.Dispose();
|
||||||
_serviceProvider.Dispose();
|
_serviceProvider.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,10 +127,11 @@ public class RequestStartupBenchmarks
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
[BenchmarkCategory("ColdStart")]
|
[BenchmarkCategory("ColdStart")]
|
||||||
public ValueTask<BenchmarkResponse> ColdStart_GFrameworkCqrs()
|
public async ValueTask<BenchmarkResponse> ColdStart_GFrameworkCqrs()
|
||||||
{
|
{
|
||||||
var runtime = CreateGFrameworkRuntime();
|
using var container = CreateGFrameworkContainer();
|
||||||
return runtime.SendAsync(BenchmarkContext.Instance, Request, CancellationToken.None);
|
var runtime = CreateGFrameworkRuntime(container);
|
||||||
|
return await runtime.SendAsync(BenchmarkContext.Instance, Request, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -137,12 +141,21 @@ public class RequestStartupBenchmarks
|
|||||||
/// 该 benchmark 故意保持与 MediatR 对照组同样的“单 handler 最小宿主”模型,
|
/// 该 benchmark 故意保持与 MediatR 对照组同样的“单 handler 最小宿主”模型,
|
||||||
/// 因此这里继续使用单点手工注册,而不引入依赖完整 CQRS 注册协调器的程序集扫描路径。
|
/// 因此这里继续使用单点手工注册,而不引入依赖完整 CQRS 注册协调器的程序集扫描路径。
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private static ICqrsRuntime CreateGFrameworkRuntime()
|
private static MicrosoftDiContainer CreateGFrameworkContainer()
|
||||||
{
|
{
|
||||||
var container = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(static currentContainer =>
|
return BenchmarkHostFactory.CreateFrozenGFrameworkContainer(static currentContainer =>
|
||||||
{
|
{
|
||||||
currentContainer.RegisterTransient<GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<BenchmarkRequest, BenchmarkResponse>, BenchmarkRequestHandler>();
|
currentContainer.RegisterTransient<GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<BenchmarkRequest, BenchmarkResponse>, BenchmarkRequestHandler>();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 基于已冻结的 benchmark 容器构建最小 GFramework.CQRS runtime。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="container">当前 benchmark 拥有并负责释放的容器。</param>
|
||||||
|
private static ICqrsRuntime CreateGFrameworkRuntime(MicrosoftDiContainer container)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(container);
|
||||||
return GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(container, RuntimeLogger);
|
return GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(container, RuntimeLogger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -103,6 +103,8 @@ public class StreamInvokerBenchmarks
|
|||||||
[GlobalCleanup]
|
[GlobalCleanup]
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
|
_reflectionContainer.Dispose();
|
||||||
|
_generatedContainer.Dispose();
|
||||||
_serviceProvider.Dispose();
|
_serviceProvider.Dispose();
|
||||||
BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
|
BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -86,6 +86,7 @@ public class StreamingBenchmarks
|
|||||||
[GlobalCleanup]
|
[GlobalCleanup]
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
|
_container.Dispose();
|
||||||
_serviceProvider.Dispose();
|
_serviceProvider.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,48 @@
|
|||||||
|
# MicrosoftDiContainer Disposal Tracking
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Fix issue `#327` by making `MicrosoftDiContainer` explicitly disposable, ensuring frozen service providers and lock
|
||||||
|
state are released deterministically, and updating CQRS benchmarks so every owned container is disposed in cleanup or
|
||||||
|
cold-start paths.
|
||||||
|
|
||||||
|
## Current Recovery Point
|
||||||
|
|
||||||
|
- Recovery point: `MDC-DISPOSE-RP-001`
|
||||||
|
- Phase: completed and ready to archive
|
||||||
|
- Focus:
|
||||||
|
- keep the final validated implementation and archive handoff concise
|
||||||
|
|
||||||
|
## Active Risks
|
||||||
|
|
||||||
|
- No active implementation blockers remain after validation.
|
||||||
|
|
||||||
|
## Completed In This Stage
|
||||||
|
|
||||||
|
- Extended `IIocContainer` to inherit `IDisposable` so callers holding the abstraction can release container-owned
|
||||||
|
resources explicitly.
|
||||||
|
- Implemented `MicrosoftDiContainer.Dispose()` with idempotent root-provider release, lock cleanup, state clearing, and
|
||||||
|
`ObjectDisposedException` guards for post-disposal access.
|
||||||
|
- Updated `Clear()` to dispose the currently built root provider before resetting container state.
|
||||||
|
- Added Core regression tests that verify resolved DI-owned singletons are disposed and that disposed containers reject
|
||||||
|
further registration, lookup, and scope creation.
|
||||||
|
- Fixed CQRS benchmark cleanup so every benchmark-owned `MicrosoftDiContainer` is disposed, including the temporary
|
||||||
|
cold-start container path in `RequestStartupBenchmarks`.
|
||||||
|
|
||||||
|
## Validation Target
|
||||||
|
|
||||||
|
- `python3 scripts/license-header.py --check`
|
||||||
|
- `dotnet test GFramework.Core.Tests -c Release --filter "FullyQualifiedName~MicrosoftDiContainer|FullyQualifiedName~IocContainerLifetimeTests"`
|
||||||
|
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
|
||||||
|
- `dotnet build GFramework.sln -c Release`
|
||||||
|
|
||||||
|
## Latest Validation Result
|
||||||
|
|
||||||
|
- `python3 scripts/license-header.py --check` passed on 2026-05-06.
|
||||||
|
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainer|FullyQualifiedName~IocContainerLifetimeTests"` passed on 2026-05-06 with `55` tests passed.
|
||||||
|
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` passed on 2026-05-06 with `0 warnings` and `0 errors`.
|
||||||
|
- `dotnet build GFramework.sln -c Release` passed on 2026-05-06 with `0 warnings` and `0 errors`.
|
||||||
|
|
||||||
|
## Next Recommended Resume Step
|
||||||
|
|
||||||
|
Archive this topic under `ai-plan/public/archive/` and push the fix branch for review.
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
# MicrosoftDiContainer Disposal Trace
|
||||||
|
|
||||||
|
## 2026-05-06
|
||||||
|
|
||||||
|
### MDC-DISPOSE-RP-001 Issue #327 disposal repair
|
||||||
|
|
||||||
|
- Trigger:
|
||||||
|
- issue `#327` reports that `MicrosoftDiContainer` holds a `ReaderWriterLockSlim` and a frozen `IServiceProvider`
|
||||||
|
but never releases either resource explicitly
|
||||||
|
- CQRS benchmark types keep `MicrosoftDiContainer` fields alive across runs and currently only dispose the MediatR
|
||||||
|
`ServiceProvider` side
|
||||||
|
- `RequestStartupBenchmarks` also creates temporary GFramework runtimes whose backing containers are never surfaced
|
||||||
|
for cleanup
|
||||||
|
- Decisions:
|
||||||
|
- treat the fix as a container lifetime contract update, not only a benchmark workaround
|
||||||
|
- add the disposal contract at the `IIocContainer` abstraction so callers holding interface references can release the
|
||||||
|
container explicitly
|
||||||
|
- keep runtime ownership unchanged; benchmarks that create containers remain responsible for disposing them
|
||||||
|
- Implementation notes:
|
||||||
|
- `MicrosoftDiContainer` now releases its frozen root `IServiceProvider`, clears registration state, disposes the
|
||||||
|
internal `ReaderWriterLockSlim`, and rejects all later operations with `ObjectDisposedException`
|
||||||
|
- `RequestStartupBenchmarks` was rewritten so the steady-state runtime keeps an explicit container field and the
|
||||||
|
cold-start benchmark disposes its temporary container in the same measured invocation
|
||||||
|
- other benchmark classes that own `MicrosoftDiContainer` fields now dispose them during `GlobalCleanup`
|
||||||
|
- Validation milestone:
|
||||||
|
- `python3 scripts/license-header.py --check` passed
|
||||||
|
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainer|FullyQualifiedName~IocContainerLifetimeTests"` passed (`55/55`)
|
||||||
|
- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` passed with `0 warnings / 0 errors`
|
||||||
|
- `dotnet build GFramework.sln -c Release` passed with `0 warnings / 0 errors`
|
||||||
|
- Immediate next step:
|
||||||
|
- archive the topic and push the branch
|
||||||
Loading…
x
Reference in New Issue
Block a user