From 0ec8aa076bd86547b9094b73f1437df1e65fde80 Mon Sep 17 00:00:00 2001
From: gewuyou <95328647+GeWuYou@users.noreply.github.com>
Date: Wed, 6 May 2026 19:08:48 +0800
Subject: [PATCH 1/3] =?UTF-8?q?fix(core):=20=E4=BF=AE=E5=A4=8D=E5=AE=B9?=
=?UTF-8?q?=E5=99=A8=E9=87=8A=E6=94=BE=E4=B8=8E=E5=9F=BA=E5=87=86=E8=B5=84?=
=?UTF-8?q?=E6=BA=90=E6=B3=84=E6=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 修复 MicrosoftDiContainer 的 IDisposable 释放逻辑、根 ServiceProvider 清理与释放后访问保护
- 更新 CQRS benchmarks 的容器 cleanup,并补齐 RequestStartupBenchmarks 的冷启动容器释放路径
- 补充 Core 容器生命周期回归测试并归档 issue 327 的 ai-plan topic
---
.../Ioc/IIocContainer.cs | 2 +-
.../Ioc/IocContainerLifetimeTests.cs | 41 +++++++++-
.../Ioc/MicrosoftDiContainerTests.cs | 13 +++
GFramework.Core/Ioc/MicrosoftDiContainer.cs | 81 +++++++++++++++++++
.../Messaging/NotificationBenchmarks.cs | 1 +
.../Messaging/RequestBenchmarks.cs | 1 +
.../Messaging/RequestInvokerBenchmarks.cs | 2 +
.../Messaging/RequestPipelineBenchmarks.cs | 1 +
.../Messaging/RequestStartupBenchmarks.cs | 25 ++++--
.../Messaging/StreamInvokerBenchmarks.cs | 2 +
.../Messaging/StreamingBenchmarks.cs | 1 +
...icrosoft-di-container-disposal-tracking.md | 48 +++++++++++
.../microsoft-di-container-disposal-trace.md | 31 +++++++
13 files changed, 241 insertions(+), 8 deletions(-)
create mode 100644 ai-plan/public/archive/microsoft-di-container-disposal/todos/microsoft-di-container-disposal-tracking.md
create mode 100644 ai-plan/public/archive/microsoft-di-container-disposal/traces/microsoft-di-container-disposal-trace.md
diff --git a/GFramework.Core.Abstractions/Ioc/IIocContainer.cs b/GFramework.Core.Abstractions/Ioc/IIocContainer.cs
index 76a560f5..03525b55 100644
--- a/GFramework.Core.Abstractions/Ioc/IIocContainer.cs
+++ b/GFramework.Core.Abstractions/Ioc/IIocContainer.cs
@@ -10,7 +10,7 @@ namespace GFramework.Core.Abstractions.Ioc;
///
/// 依赖注入容器接口,定义了服务注册、解析和管理的基本操作
///
-public interface IIocContainer : IContextAware
+public interface IIocContainer : IContextAware, IDisposable
{
#region Register Methods
diff --git a/GFramework.Core.Tests/Ioc/IocContainerLifetimeTests.cs b/GFramework.Core.Tests/Ioc/IocContainerLifetimeTests.cs
index d0dcdd08..a3325ff6 100644
--- a/GFramework.Core.Tests/Ioc/IocContainerLifetimeTests.cs
+++ b/GFramework.Core.Tests/Ioc/IocContainerLifetimeTests.cs
@@ -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();
}
-}
\ No newline at end of file
+
+ [Test]
+ public void Dispose_Should_Dispose_Resolved_Singleton_And_Block_Further_Use()
+ {
+ // Arrange
+ var container = new MicrosoftDiContainer();
+ container.RegisterSingleton();
+ container.Freeze();
+ var service = container.GetRequired();
+
+ // Act
+ container.Dispose();
+
+ // Assert
+ Assert.That(service.IsDisposed, Is.True);
+ Assert.Throws(() => container.Get());
+ Assert.Throws(() => container.CreateScope());
+ }
+
+ [Test]
+ public void Dispose_Should_Be_Idempotent()
+ {
+ var container = new MicrosoftDiContainer();
+
+ Assert.DoesNotThrow(container.Dispose);
+ Assert.DoesNotThrow(container.Dispose);
+ }
+}
diff --git a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs
index 820cfa66..bc16b6d2 100644
--- a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs
+++ b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs
@@ -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));
}
+
+ ///
+ /// 测试容器释放后会阻止后续注册与解析,避免 benchmark 或短生命周期宿主继续使用已回收状态。
+ ///
+ [Test]
+ public void Dispose_Should_Block_Subsequent_Registration_And_Query_Operations()
+ {
+ _container.Dispose();
+
+ Assert.Throws(() => _container.Register(new TestService()));
+ Assert.Throws(() => _container.Contains());
+ Assert.Throws(() => _container.GetAll());
+ }
}
diff --git a/GFramework.Core/Ioc/MicrosoftDiContainer.cs b/GFramework.Core/Ioc/MicrosoftDiContainer.cs
index 5a6d728c..4c5fe90b 100644
--- a/GFramework.Core/Ioc/MicrosoftDiContainer.cs
+++ b/GFramework.Core/Ioc/MicrosoftDiContainer.cs
@@ -22,6 +22,19 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
{
#region Helper Methods
+ ///
+ /// 检查容器是否已释放,避免访问已经失效的服务提供者与同步原语。
+ ///
+ /// 当容器已释放时抛出。
+ private void ThrowIfDisposed()
+ {
+ if (!_disposed) return;
+
+ const string objectName = nameof(MicrosoftDiContainer);
+ _logger.Warn("Attempted to use a disposed MicrosoftDiContainer.");
+ throw new ObjectDisposedException(objectName);
+ }
+
///
/// 检查容器是否已冻结,如果已冻结则抛出异常
/// 用于保护注册操作的安全性
@@ -57,6 +70,11 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
///
private volatile bool _frozen;
+ ///
+ /// 容器释放状态标志,true 表示容器已释放,不允许继续访问。
+ ///
+ private volatile bool _disposed;
+
///
/// 读写锁,确保多线程环境下的线程安全操作
///
@@ -85,6 +103,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 当容器已冻结或类型已被注册时抛出
public void RegisterSingleton(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)
/// 当容器已冻结时抛出
public void RegisterPlurality(object instance)
{
+ ThrowIfDisposed();
var concreteType = instance.GetType();
var interfaces = concreteType.GetInterfaces();
@@ -219,6 +242,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
///
public void RegisterPlurality() where T : class
{
+ ThrowIfDisposed();
_lock.EnterWriteLock();
try
{
@@ -262,6 +286,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 当容器已冻结时抛出
public void Register(T instance)
{
+ ThrowIfDisposed();
_lock.EnterWriteLock();
try
{
@@ -284,6 +309,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 当容器已冻结时抛出
public void Register(Type type, object instance)
{
+ ThrowIfDisposed();
_lock.EnterWriteLock();
try
{
@@ -307,6 +333,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
public void RegisterFactory(
Func factory) where TService : class
{
+ ThrowIfDisposed();
_lock.EnterWriteLock();
try
{
@@ -328,6 +355,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 行为类型,必须是引用类型
public void RegisterCqrsPipelineBehavior() where TBehavior : class
{
+ ThrowIfDisposed();
_lock.EnterWriteLock();
try
{
@@ -392,6 +420,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
public void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies)
{
ArgumentNullException.ThrowIfNull(assemblies);
+ ThrowIfDisposed();
var assemblyArray = assemblies.ToArray();
foreach (var assembly in assemblyArray)
{
@@ -419,6 +448,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 服务配置委托
public void ExecuteServicesHook(Action? configurator = null)
{
+ ThrowIfDisposed();
_lock.EnterWriteLock();
try
{
@@ -464,6 +494,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 服务实例或null
public T? Get() where T : class
{
+ ThrowIfDisposed();
_lock.EnterReadLock();
try
{
@@ -503,6 +534,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 服务实例或null
public object? Get(Type type)
{
+ ThrowIfDisposed();
_lock.EnterReadLock();
try
{
@@ -593,6 +625,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 只读的服务实例列表
public IReadOnlyList GetAll() where T : class
{
+ ThrowIfDisposed();
_lock.EnterReadLock();
try
{
@@ -620,6 +653,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
public IReadOnlyList