diff --git a/GFramework.Core.Abstractions/Ioc/IIocContainer.cs b/GFramework.Core.Abstractions/Ioc/IIocContainer.cs
index 76a560f5..9db0bd3e 100644
--- a/GFramework.Core.Abstractions/Ioc/IIocContainer.cs
+++ b/GFramework.Core.Abstractions/Ioc/IIocContainer.cs
@@ -8,9 +8,15 @@ using GFramework.Core.Abstractions.Systems;
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..c36aba30 100644
--- a/GFramework.Core.Tests/Ioc/IocContainerLifetimeTests.cs
+++ b/GFramework.Core.Tests/Ioc/IocContainerLifetimeTests.cs
@@ -3,6 +3,9 @@
using GFramework.Core.Ioc;
using Microsoft.Extensions.DependencyInjection;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
namespace GFramework.Core.Tests.Ioc;
@@ -22,6 +25,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 +222,112 @@ 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);
+ }
+
+ [Test]
+ public void Dispose_Should_Be_Idempotent_When_Called_Concurrently()
+ {
+ var container = new MicrosoftDiContainer();
+ var containerLock = GetContainerLock(container);
+ var releasedGate = false;
+
+ containerLock.EnterWriteLock();
+ try
+ {
+ var firstDisposeTask = Task.Run(container.Dispose);
+ Thread.Sleep(50);
+ var secondDisposeTask = Task.Run(container.Dispose);
+ Thread.Sleep(50);
+
+ containerLock.ExitWriteLock();
+ releasedGate = true;
+
+ Assert.That(async () => await Task.WhenAll(firstDisposeTask, secondDisposeTask).ConfigureAwait(false), Throws.Nothing);
+ }
+ finally
+ {
+ if (!releasedGate)
+ {
+ containerLock.ExitWriteLock();
+ }
+ }
+ }
+
+ [Test]
+ public void Dispose_Should_Only_Attempt_Lock_Disposal_Once_When_Called_Concurrently()
+ {
+ var container = new MicrosoftDiContainer();
+ var containerLock = GetContainerLock(container);
+ var releasedGate = false;
+
+ containerLock.EnterWriteLock();
+ try
+ {
+ var firstDisposeTask = Task.Run(container.Dispose);
+ Thread.Sleep(50);
+ var secondDisposeTask = Task.Run(container.Dispose);
+ Thread.Sleep(50);
+
+ containerLock.ExitWriteLock();
+ releasedGate = true;
+
+ Assert.That(async () => await Task.WhenAll(firstDisposeTask, secondDisposeTask).ConfigureAwait(false), Throws.Nothing);
+ Assert.That(GetLockDisposalStarted(container), Is.EqualTo(1));
+ }
+ finally
+ {
+ if (!releasedGate)
+ {
+ containerLock.ExitWriteLock();
+ }
+ }
+ }
+
+ ///
+ /// 通过反射获取容器内部锁,用于构造可重复的并发释放竞态回归。
+ ///
+ private static ReaderWriterLockSlim GetContainerLock(MicrosoftDiContainer container)
+ {
+ ArgumentNullException.ThrowIfNull(container);
+ var lockField = typeof(MicrosoftDiContainer).GetField("_lock", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.That(lockField, Is.Not.Null);
+ return (ReaderWriterLockSlim)lockField!.GetValue(container)!;
+ }
+
+ ///
+ /// 读取锁销毁启动标记,验证并发释放路径不会重复执行底层锁销毁。
+ ///
+ private static int GetLockDisposalStarted(MicrosoftDiContainer container)
+ {
+ ArgumentNullException.ThrowIfNull(container);
+ var flagField = typeof(MicrosoftDiContainer).GetField("_lockDisposalStarted", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.That(flagField, Is.Not.Null);
+ return (int)flagField!.GetValue(container)!;
+ }
+}
diff --git a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs
index 820cfa66..a2627a06 100644
--- a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs
+++ b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs
@@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
@@ -760,4 +762,73 @@ 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());
+ }
+
+ ///
+ /// 测试等待中的读取线程在容器释放后也会收到稳定的容器级释放异常,而不是底层锁异常。
+ ///
+ [Test]
+ public async Task Dispose_Should_Translate_Waiting_Readers_To_Container_ObjectDisposedException()
+ {
+ _container.RegisterSingleton(new TestService());
+ _container.Freeze();
+
+ var containerLock = GetContainerLock(_container);
+ var releasedGate = false;
+ using var queryStarted = new ManualResetEventSlim(false);
+
+ containerLock.EnterWriteLock();
+ try
+ {
+ var queryTask = Task.Run(() =>
+ {
+ queryStarted.Set();
+ return _container.Get();
+ });
+
+ Assert.That(queryStarted.Wait(TimeSpan.FromSeconds(1)), Is.True);
+
+ var disposeTask = Task.Run(_container.Dispose);
+ Thread.Sleep(50);
+
+ containerLock.ExitWriteLock();
+ releasedGate = true;
+
+ await disposeTask.ConfigureAwait(false);
+
+ var exception = Assert.ThrowsAsync(async () => await queryTask.ConfigureAwait(false));
+ Assert.That(exception, Is.Not.Null);
+ Assert.That(exception!.ObjectName, Is.EqualTo(nameof(MicrosoftDiContainer)));
+ }
+ finally
+ {
+ if (!releasedGate)
+ {
+ containerLock.ExitWriteLock();
+ }
+ }
+ }
+
+ ///
+ /// 通过反射获取容器内部锁,用于构造可重复的并发释放竞态回归。
+ ///
+ private static ReaderWriterLockSlim GetContainerLock(MicrosoftDiContainer container)
+ {
+ ArgumentNullException.ThrowIfNull(container);
+ var lockField = typeof(MicrosoftDiContainer).GetField("_lock", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.That(lockField, Is.Not.Null);
+ return (ReaderWriterLockSlim)lockField!.GetValue(container)!;
+ }
}
diff --git a/GFramework.Core/Ioc/MicrosoftDiContainer.cs b/GFramework.Core/Ioc/MicrosoftDiContainer.cs
index 5a6d728c..ad362b76 100644
--- a/GFramework.Core/Ioc/MicrosoftDiContainer.cs
+++ b/GFramework.Core/Ioc/MicrosoftDiContainer.cs
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
using System.Reflection;
+using System.Threading;
using GFramework.Core.Abstractions.Bases;
using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Abstractions.Logging;
@@ -17,11 +18,143 @@ namespace GFramework.Core.Ioc;
/// 将 Microsoft DI 包装为 IIocContainer 接口实现
/// 提供线程安全的依赖注入容器功能
///
+///
+/// 该适配器负责维护服务注册表、冻结后的根 以及并发访问控制。
+/// 容器释放后会阻止任何进一步访问,并统一抛出 ,
+/// 以避免 benchmark、测试宿主或短生命周期架构误用失效的 DI 状态。
+///
/// 可选的IServiceCollection实例,默认创建新的ServiceCollection
public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) : ContextAwareBase, IIocContainer
{
#region Helper Methods
+ ///
+ /// 抛出统一的容器释放异常,避免并发路径泄露底层锁类型的实现细节。
+ ///
+ /// 始终抛出,且对象名固定为当前容器类型。
+ private void ThrowDisposedException()
+ {
+ const string objectName = nameof(MicrosoftDiContainer);
+ _logger.Warn("Attempted to use a disposed MicrosoftDiContainer.");
+ throw new ObjectDisposedException(objectName);
+ }
+
+ ///
+ /// 检查容器是否已释放,避免访问已经失效的服务提供者与同步原语。
+ ///
+ /// 当容器已释放时抛出。
+ private void ThrowIfDisposed()
+ {
+ if (!_disposed) return;
+ ThrowDisposedException();
+ }
+
+ ///
+ /// 进入读锁,并在获取锁前后都复核释放状态,确保等待中的线程也能稳定得到容器级异常。
+ ///
+ /// 当容器已释放,或等待期间被其他线程释放时抛出。
+ private void EnterReadLockOrThrowDisposed()
+ {
+ var lockTaken = false;
+
+ try
+ {
+ _lock.EnterReadLock();
+ lockTaken = true;
+ }
+ catch (ObjectDisposedException) when (_disposed)
+ {
+ ThrowDisposedException();
+ }
+
+ if (!_disposed)
+ {
+ return;
+ }
+
+ if (lockTaken)
+ {
+ _lock.ExitReadLock();
+ }
+
+ ThrowDisposedException();
+ }
+
+ ///
+ /// 进入写锁,并在获取锁前后都复核释放状态,确保等待中的线程不会泄露底层锁异常。
+ ///
+ /// 当容器已释放,或等待期间被其他线程释放时抛出。
+ private void EnterWriteLockOrThrowDisposed()
+ {
+ var lockTaken = false;
+
+ try
+ {
+ _lock.EnterWriteLock();
+ lockTaken = true;
+ }
+ catch (ObjectDisposedException) when (_disposed)
+ {
+ ThrowDisposedException();
+ }
+
+ if (!_disposed)
+ {
+ return;
+ }
+
+ if (lockTaken)
+ {
+ _lock.ExitWriteLock();
+ }
+
+ ThrowDisposedException();
+ }
+
+ ///
+ /// 在释放标志已经对外可见后,等待遗留 waiter 退场,再尝试释放底层锁。
+ ///
+ ///
+ /// 容器会先把 置为 并退出写锁,
+ /// 这样所有已在等待队列中的线程都能醒来并通过统一路径抛出容器级
+ /// 。只有当这些线程退场后,底层锁才可安全释放。
+ /// 该步骤只允许一个释放调用者执行,避免并发 重复销毁同一个
+ /// 并破坏幂等契约。
+ ///
+ private void DisposeLockWhenQuiescent()
+ {
+ if (Interlocked.CompareExchange(ref _lockDisposalStarted, 1, 0) != 0)
+ {
+ return;
+ }
+
+ const int maxDisposeSpinAttempts = 512;
+ var spinWait = new SpinWait();
+
+ for (var attempt = 0; attempt < maxDisposeSpinAttempts; attempt++)
+ {
+ if (_lock.CurrentReadCount == 0 &&
+ _lock.WaitingReadCount == 0 &&
+ _lock.WaitingWriteCount == 0 &&
+ _lock.WaitingUpgradeCount == 0)
+ {
+ try
+ {
+ _lock.Dispose();
+ return;
+ }
+ catch (SynchronizationLockException)
+ {
+ // 等待中的线程刚好在本轮检查后切换状态;继续自旋直到锁真正静默。
+ }
+ }
+
+ spinWait.SpinOnce();
+ }
+
+ _logger.Warn("MicrosoftDiContainer lock disposal was skipped because waiters did not quiesce in time.");
+ }
+
///
/// 检查容器是否已冻结,如果已冻结则抛出异常
/// 用于保护注册操作的安全性
@@ -57,6 +190,16 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
///
private volatile bool _frozen;
+ ///
+ /// 容器释放状态标志,true 表示容器已释放,不允许继续访问。
+ ///
+ private volatile bool _disposed;
+
+ ///
+ /// 标记底层读写锁的销毁流程是否已经启动,确保并发释放时最多只有一个线程尝试销毁锁实例。
+ ///
+ private int _lockDisposalStarted;
+
///
/// 读写锁,确保多线程环境下的线程安全操作
///
@@ -85,8 +228,9 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 当容器已冻结或类型已被注册时抛出
public void RegisterSingleton(T instance)
{
+ ThrowIfDisposed();
var type = typeof(T);
- _lock.EnterWriteLock();
+ EnterWriteLockOrThrowDisposed();
try
{
ThrowIfFrozen();
@@ -119,7 +263,8 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
where TImpl : class, TService
where TService : class
{
- _lock.EnterWriteLock();
+ ThrowIfDisposed();
+ EnterWriteLockOrThrowDisposed();
try
{
ThrowIfFrozen();
@@ -142,7 +287,8 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
where TImpl : class, TService
where TService : class
{
- _lock.EnterWriteLock();
+ ThrowIfDisposed();
+ EnterWriteLockOrThrowDisposed();
try
{
ThrowIfFrozen();
@@ -165,7 +311,8 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
where TImpl : class, TService
where TService : class
{
- _lock.EnterWriteLock();
+ ThrowIfDisposed();
+ EnterWriteLockOrThrowDisposed();
try
{
ThrowIfFrozen();
@@ -187,10 +334,11 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 当容器已冻结时抛出
public void RegisterPlurality(object instance)
{
+ ThrowIfDisposed();
var concreteType = instance.GetType();
var interfaces = concreteType.GetInterfaces();
- _lock.EnterWriteLock();
+ EnterWriteLockOrThrowDisposed();
try
{
ThrowIfFrozen();
@@ -219,7 +367,8 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
///
public void RegisterPlurality() where T : class
{
- _lock.EnterWriteLock();
+ ThrowIfDisposed();
+ EnterWriteLockOrThrowDisposed();
try
{
ThrowIfFrozen();
@@ -262,7 +411,8 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 当容器已冻结时抛出
public void Register(T instance)
{
- _lock.EnterWriteLock();
+ ThrowIfDisposed();
+ EnterWriteLockOrThrowDisposed();
try
{
ThrowIfFrozen();
@@ -284,7 +434,8 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 当容器已冻结时抛出
public void Register(Type type, object instance)
{
- _lock.EnterWriteLock();
+ ThrowIfDisposed();
+ EnterWriteLockOrThrowDisposed();
try
{
ThrowIfFrozen();
@@ -307,7 +458,8 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
public void RegisterFactory(
Func factory) where TService : class
{
- _lock.EnterWriteLock();
+ ThrowIfDisposed();
+ EnterWriteLockOrThrowDisposed();
try
{
ThrowIfFrozen();
@@ -328,7 +480,8 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 行为类型,必须是引用类型
public void RegisterCqrsPipelineBehavior() where TBehavior : class
{
- _lock.EnterWriteLock();
+ ThrowIfDisposed();
+ EnterWriteLockOrThrowDisposed();
try
{
ThrowIfFrozen();
@@ -392,6 +545,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)
{
@@ -401,7 +555,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
}
}
- _lock.EnterWriteLock();
+ EnterWriteLockOrThrowDisposed();
try
{
ThrowIfFrozen();
@@ -419,7 +573,8 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 服务配置委托
public void ExecuteServicesHook(Action? configurator = null)
{
- _lock.EnterWriteLock();
+ ThrowIfDisposed();
+ EnterWriteLockOrThrowDisposed();
try
{
ThrowIfFrozen();
@@ -464,7 +619,8 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 服务实例或null
public T? Get() where T : class
{
- _lock.EnterReadLock();
+ ThrowIfDisposed();
+ EnterReadLockOrThrowDisposed();
try
{
if (_provider == null)
@@ -503,7 +659,8 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 服务实例或null
public object? Get(Type type)
{
- _lock.EnterReadLock();
+ ThrowIfDisposed();
+ EnterReadLockOrThrowDisposed();
try
{
if (_provider == null)
@@ -593,7 +750,8 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 只读的服务实例列表
public IReadOnlyList GetAll() where T : class
{
- _lock.EnterReadLock();
+ ThrowIfDisposed();
+ EnterReadLockOrThrowDisposed();
try
{
if (_provider == null)
@@ -620,8 +778,9 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
public IReadOnlyList