mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-10 11:04:29 +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>
|
||||
public interface IIocContainer : IContextAware
|
||||
public interface IIocContainer : IContextAware, IDisposable
|
||||
{
|
||||
#region Register Methods
|
||||
|
||||
|
||||
@ -22,6 +22,18 @@ public class IocContainerLifetimeTests
|
||||
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]
|
||||
public void RegisterSingleton_Should_Return_Same_Instance()
|
||||
{
|
||||
@ -207,4 +219,31 @@ public class IocContainerLifetimeTests
|
||||
scope2.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[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
|
||||
|
||||
/// <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>
|
||||
/// 检查容器是否已冻结,如果已冻结则抛出异常
|
||||
/// 用于保护注册操作的安全性
|
||||
@ -57,6 +70,11 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// </summary>
|
||||
private volatile bool _frozen;
|
||||
|
||||
/// <summary>
|
||||
/// 容器释放状态标志,true 表示容器已释放,不允许继续访问。
|
||||
/// </summary>
|
||||
private volatile bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// 读写锁,确保多线程环境下的线程安全操作
|
||||
/// </summary>
|
||||
@ -85,6 +103,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// <exception cref="InvalidOperationException">当容器已冻结或类型已被注册时抛出</exception>
|
||||
public void RegisterSingleton<T>(T instance)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var type = typeof(T);
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
@ -119,6 +138,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
where TImpl : class, TService
|
||||
where TService : class
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
@ -142,6 +162,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
where TImpl : class, TService
|
||||
where TService : class
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
@ -165,6 +186,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
where TImpl : class, TService
|
||||
where TService : class
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
@ -187,6 +209,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// <exception cref="InvalidOperationException">当容器已冻结时抛出</exception>
|
||||
public void RegisterPlurality(object instance)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var concreteType = instance.GetType();
|
||||
var interfaces = concreteType.GetInterfaces();
|
||||
|
||||
@ -219,6 +242,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// </summary>
|
||||
public void RegisterPlurality<T>() where T : class
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
@ -262,6 +286,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// <exception cref="InvalidOperationException">当容器已冻结时抛出</exception>
|
||||
public void Register<T>(T instance)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
@ -284,6 +309,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// <exception cref="InvalidOperationException">当容器已冻结时抛出</exception>
|
||||
public void Register(Type type, object instance)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
@ -307,6 +333,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
public void RegisterFactory<TService>(
|
||||
Func<IServiceProvider, TService> factory) where TService : class
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
@ -328,6 +355,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
|
||||
public void RegisterCqrsPipelineBehavior<TBehavior>() where TBehavior : class
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
@ -392,6 +420,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
public void RegisterCqrsHandlersFromAssemblies(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(assemblies);
|
||||
ThrowIfDisposed();
|
||||
var assemblyArray = assemblies.ToArray();
|
||||
foreach (var assembly in assemblyArray)
|
||||
{
|
||||
@ -419,6 +448,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// <param name="configurator">服务配置委托</param>
|
||||
public void ExecuteServicesHook(Action<IServiceCollection>? configurator = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
@ -464,6 +494,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// <returns>服务实例或null</returns>
|
||||
public T? Get<T>() where T : class
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
@ -503,6 +534,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// <returns>服务实例或null</returns>
|
||||
public object? Get(Type type)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
@ -593,6 +625,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// <returns>只读的服务实例列表</returns>
|
||||
public IReadOnlyList<T> GetAll<T>() where T : class
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
@ -620,6 +653,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
public IReadOnlyList<object> GetAll(Type type)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(type);
|
||||
ThrowIfDisposed();
|
||||
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
@ -750,6 +784,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// <returns>排序后的只读服务实例列表</returns>
|
||||
public IReadOnlyList<T> GetAllSorted<T>(Comparison<T> comparison) where T : class
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var list = GetAll<T>().ToList();
|
||||
list.Sort(comparison);
|
||||
return list;
|
||||
@ -816,6 +851,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// <returns>true表示包含该类型实例,false表示不包含</returns>
|
||||
public bool Contains<T>() where T : class
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
@ -838,6 +874,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// <returns>true表示包含该实例,false表示不包含</returns>
|
||||
public bool ContainsInstance(object instance)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
@ -855,6 +892,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
@ -867,7 +905,9 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
|
||||
GetServicesUnsafe.Clear();
|
||||
_registeredInstances.Clear();
|
||||
(_provider as IDisposable)?.Dispose();
|
||||
_provider = null;
|
||||
_frozen = false;
|
||||
_logger.Info("Container cleared");
|
||||
}
|
||||
finally
|
||||
@ -882,6 +922,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// </summary>
|
||||
public void Freeze()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
@ -917,6 +958,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// <exception cref="InvalidOperationException">当容器未冻结时抛出</exception>
|
||||
public IServiceScope CreateScope()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_lock.EnterReadLock();
|
||||
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
|
||||
}
|
||||
|
||||
@ -83,6 +83,7 @@ public class NotificationBenchmarks
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_container.Dispose();
|
||||
_serviceProvider.Dispose();
|
||||
}
|
||||
|
||||
|
||||
@ -85,6 +85,7 @@ public class RequestBenchmarks
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_container.Dispose();
|
||||
_serviceProvider.Dispose();
|
||||
}
|
||||
|
||||
|
||||
@ -103,6 +103,8 @@ public class RequestInvokerBenchmarks
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_reflectionContainer.Dispose();
|
||||
_generatedContainer.Dispose();
|
||||
_serviceProvider.Dispose();
|
||||
BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
|
||||
}
|
||||
|
||||
@ -101,6 +101,7 @@ public class RequestPipelineBenchmarks
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_container.Dispose();
|
||||
_serviceProvider.Dispose();
|
||||
BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ public class RequestStartupBenchmarks
|
||||
private static readonly ILogger RuntimeLogger = CreateLogger(nameof(RequestStartupBenchmarks));
|
||||
private static readonly BenchmarkRequest Request = new(Guid.NewGuid());
|
||||
|
||||
private MicrosoftDiContainer _container = null!;
|
||||
private ServiceProvider _serviceProvider = null!;
|
||||
private IMediator _mediatr = null!;
|
||||
private ICqrsRuntime _runtime = null!;
|
||||
@ -62,7 +63,8 @@ public class RequestStartupBenchmarks
|
||||
|
||||
_serviceProvider = CreateMediatRServiceProvider();
|
||||
_mediatr = _serviceProvider.GetRequiredService<IMediator>();
|
||||
_runtime = CreateGFrameworkRuntime();
|
||||
_container = CreateGFrameworkContainer();
|
||||
_runtime = CreateGFrameworkRuntime(_container);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -84,6 +86,7 @@ public class RequestStartupBenchmarks
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_container.Dispose();
|
||||
_serviceProvider.Dispose();
|
||||
}
|
||||
|
||||
@ -124,10 +127,11 @@ public class RequestStartupBenchmarks
|
||||
/// </summary>
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("ColdStart")]
|
||||
public ValueTask<BenchmarkResponse> ColdStart_GFrameworkCqrs()
|
||||
public async ValueTask<BenchmarkResponse> ColdStart_GFrameworkCqrs()
|
||||
{
|
||||
var runtime = CreateGFrameworkRuntime();
|
||||
return runtime.SendAsync(BenchmarkContext.Instance, Request, CancellationToken.None);
|
||||
using var container = CreateGFrameworkContainer();
|
||||
var runtime = CreateGFrameworkRuntime(container);
|
||||
return await runtime.SendAsync(BenchmarkContext.Instance, Request, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -137,12 +141,21 @@ public class RequestStartupBenchmarks
|
||||
/// 该 benchmark 故意保持与 MediatR 对照组同样的“单 handler 最小宿主”模型,
|
||||
/// 因此这里继续使用单点手工注册,而不引入依赖完整 CQRS 注册协调器的程序集扫描路径。
|
||||
/// </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>();
|
||||
});
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
|
||||
@ -103,6 +103,8 @@ public class StreamInvokerBenchmarks
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_reflectionContainer.Dispose();
|
||||
_generatedContainer.Dispose();
|
||||
_serviceProvider.Dispose();
|
||||
BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
|
||||
}
|
||||
|
||||
@ -86,6 +86,7 @@ public class StreamingBenchmarks
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_container.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