mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
fix(pool): 修复对象池系统中的双重释放和线程安全问题
- 修复 ActiveCount 在双重释放时可能变为负数的问题 - 添加对错误 key 释放的防护和警告日志 - 优化 StringBuilderPool 使用 ConcurrentBag 实现线程安全 - 改进池容量限制逻辑,超过最大容量的对象将被销毁 - 添加完整的单元测试验证双重释放、错误释放和线程安全场景
This commit is contained in:
parent
eb763a9bc4
commit
e2cfa7bffa
@ -46,7 +46,7 @@ public interface IObjectPoolSystem<in TKey, TObject>
|
||||
/// 设置指定池的最大容量
|
||||
/// </summary>
|
||||
/// <param name="key">对象池的键</param>
|
||||
/// <param name="maxCapacity">最大容量,超过此容量的对象将被销毁而不是放回池中</param>
|
||||
/// <param name="maxCapacity">池中保留的最大对象数量。超过此数量时,释放的对象将被销毁而不是放回池中。设置为 0 表示无限制。</param>
|
||||
void SetMaxCapacity(TKey key, int maxCapacity);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -250,6 +250,40 @@ public class ObjectPoolTests
|
||||
Assert.That(stats.TotalReleased, Is.EqualTo(0));
|
||||
Assert.That(stats.TotalDestroyed, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证双重释放不会导致 ActiveCount 变为负数
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Release_Should_Not_Make_ActiveCount_Negative_On_Double_Release()
|
||||
{
|
||||
// Arrange
|
||||
var obj = _pool.Acquire("test");
|
||||
_pool.Release("test", obj);
|
||||
|
||||
// Act - 双重释放
|
||||
_pool.Release("test", obj);
|
||||
|
||||
// Assert
|
||||
Assert.That(_pool.GetActiveCount("test"), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证使用错误的 key 释放不会影响原 key 的 ActiveCount
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Release_With_Wrong_Key_Should_Not_Affect_Original_Key_ActiveCount()
|
||||
{
|
||||
// Arrange
|
||||
var obj = _pool.Acquire("key1");
|
||||
|
||||
// Act - 使用错误的 key 释放
|
||||
_pool.Release("key2", obj);
|
||||
|
||||
// Assert
|
||||
Assert.That(_pool.GetActiveCount("key1"), Is.EqualTo(1));
|
||||
Assert.That(_pool.GetActiveCount("key2"), Is.EqualTo(0));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -113,4 +113,85 @@ public class StringBuilderPoolTests
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo("Hello World"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 Rent 方法会复用已归还的 StringBuilder 实例
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Rent_Should_Reuse_Returned_StringBuilder()
|
||||
{
|
||||
// Arrange
|
||||
var sb1 = StringBuilderPool.Rent();
|
||||
var originalInstance = sb1;
|
||||
StringBuilderPool.Return(sb1);
|
||||
|
||||
// Act
|
||||
var sb2 = StringBuilderPool.Rent();
|
||||
|
||||
// Assert
|
||||
Assert.That(sb2, Is.SameAs(originalInstance), "应该复用同一个实例");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 Rent 方法会确保返回的 StringBuilder 满足最小容量要求
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Rent_Should_Ensure_Minimum_Capacity()
|
||||
{
|
||||
// Arrange
|
||||
var sb1 = StringBuilderPool.Rent(100);
|
||||
StringBuilderPool.Return(sb1);
|
||||
|
||||
// Act - 请求更大容量
|
||||
var sb2 = StringBuilderPool.Rent(500);
|
||||
|
||||
// Assert
|
||||
Assert.That(sb2.Capacity, Is.GreaterThanOrEqualTo(500));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证超过最大保留容量的 StringBuilder 不会被池化
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Return_Should_Not_Pool_Large_Capacity_StringBuilder()
|
||||
{
|
||||
// Arrange
|
||||
var sb1 = StringBuilderPool.Rent(10000);
|
||||
StringBuilderPool.Return(sb1);
|
||||
|
||||
// Act - 租用新的小容量 StringBuilder
|
||||
var sb2 = StringBuilderPool.Rent(100);
|
||||
|
||||
// Assert
|
||||
Assert.That(sb2, Is.Not.SameAs(sb1), "大容量实例不应被池化");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证对象池在多线程环境下的线程安全性
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void Pool_Should_Be_Thread_Safe()
|
||||
{
|
||||
// Arrange
|
||||
const int threadCount = 10;
|
||||
const int operationsPerThread = 100;
|
||||
var tasks = new Task[threadCount];
|
||||
|
||||
// Act
|
||||
for (int i = 0; i < threadCount; i++)
|
||||
{
|
||||
tasks[i] = Task.Run(() =>
|
||||
{
|
||||
for (int j = 0; j < operationsPerThread; j++)
|
||||
{
|
||||
var sb = StringBuilderPool.Rent();
|
||||
sb.Append("Test");
|
||||
StringBuilderPool.Return(sb);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.DoesNotThrow(() => Task.WaitAll(tasks));
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using GFramework.Core.Abstractions.pool;
|
||||
using System.Diagnostics;
|
||||
using GFramework.Core.Abstractions.pool;
|
||||
using GFramework.Core.system;
|
||||
|
||||
namespace GFramework.Core.pool;
|
||||
@ -11,49 +12,6 @@ namespace GFramework.Core.pool;
|
||||
public abstract class AbstractObjectPoolSystem<TKey, TObject>
|
||||
: AbstractSystem, IObjectPoolSystem<TKey, TObject> where TObject : IPoolableObject where TKey : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// 池信息类,用于管理对象池的核心数据结构和统计信息。
|
||||
/// 包含对象栈、容量限制以及各类操作的统计计数。
|
||||
/// </summary>
|
||||
protected class PoolInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 对象栈,用于存储可复用的对象实例。
|
||||
/// </summary>
|
||||
public Stack<TObject> Stack { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 池的最大容量限制,超过此数量时将不再创建新对象。
|
||||
/// </summary>
|
||||
public int MaxCapacity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总共创建的对象数量统计。
|
||||
/// </summary>
|
||||
public int TotalCreated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总共从池中获取的对象数量统计。
|
||||
/// </summary>
|
||||
public int TotalAcquired { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总共归还到池中的对象数量统计。
|
||||
/// </summary>
|
||||
public int TotalReleased { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总共销毁的对象数量统计。
|
||||
/// </summary>
|
||||
public int TotalDestroyed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前活跃(正在使用)的对象数量统计。
|
||||
/// </summary>
|
||||
public int ActiveCount { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 存储对象池的字典,键为池标识,值为池信息
|
||||
/// </summary>
|
||||
@ -105,7 +63,19 @@ public abstract class AbstractObjectPoolSystem<TKey, TObject>
|
||||
}
|
||||
|
||||
poolInfo.TotalReleased++;
|
||||
poolInfo.ActiveCount--;
|
||||
|
||||
// 防止 ActiveCount 变为负数
|
||||
if (poolInfo.ActiveCount > 0)
|
||||
{
|
||||
poolInfo.ActiveCount--;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 记录警告:检测到可能的双重释放或错误释放
|
||||
Debug.WriteLine(
|
||||
$"[ObjectPool] Warning: Attempting to release object for key '{key}' " +
|
||||
$"but ActiveCount is already 0. Possible double-release or incorrect key.");
|
||||
}
|
||||
|
||||
// 检查容量限制
|
||||
if (poolInfo.MaxCapacity > 0 && poolInfo.Stack.Count >= poolInfo.MaxCapacity)
|
||||
@ -161,7 +131,7 @@ public abstract class AbstractObjectPoolSystem<TKey, TObject>
|
||||
/// 设置指定池的最大容量
|
||||
/// </summary>
|
||||
/// <param name="key">对象池的键</param>
|
||||
/// <param name="maxCapacity">最大容量,超过此容量的对象将被销毁而不是放回池中</param>
|
||||
/// <param name="maxCapacity">池中保留的最大对象数量。超过此数量时,释放的对象将被销毁而不是放回池中。设置为 0 表示无限制。</param>
|
||||
public void SetMaxCapacity(TKey key, int maxCapacity)
|
||||
{
|
||||
if (!Pools.TryGetValue(key, out var poolInfo))
|
||||
@ -242,4 +212,47 @@ public abstract class AbstractObjectPoolSystem<TKey, TObject>
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 池信息类,用于管理对象池的核心数据结构和统计信息。
|
||||
/// 包含对象栈、容量限制以及各类操作的统计计数。
|
||||
/// </summary>
|
||||
protected class PoolInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 对象栈,用于存储可复用的对象实例。
|
||||
/// </summary>
|
||||
public Stack<TObject> Stack { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 池中保留的最大对象数量。当释放对象时,如果池中对象数已达到此限制,
|
||||
/// 对象将被销毁而不是放回池中。设置为 0 表示无限制。
|
||||
/// </summary>
|
||||
public int MaxCapacity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总共创建的对象数量统计。
|
||||
/// </summary>
|
||||
public int TotalCreated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总共从池中获取的对象数量统计。
|
||||
/// </summary>
|
||||
public int TotalAcquired { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总共归还到池中的对象数量统计。
|
||||
/// </summary>
|
||||
public int TotalReleased { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总共销毁的对象数量统计。
|
||||
/// </summary>
|
||||
public int TotalDestroyed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前活跃(正在使用)的对象数量统计。
|
||||
/// </summary>
|
||||
public int ActiveCount { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
|
||||
namespace GFramework.Core.pool;
|
||||
@ -10,11 +11,18 @@ public static class StringBuilderPool
|
||||
private const int DefaultCapacity = 256;
|
||||
private const int MaxRetainedCapacity = 4096;
|
||||
|
||||
// 使用 ConcurrentBag 实现线程安全的对象池
|
||||
private static readonly ConcurrentBag<StringBuilder> Pool = new();
|
||||
|
||||
/// <summary>
|
||||
/// 从池中租用一个 StringBuilder
|
||||
/// </summary>
|
||||
/// <param name="capacity">初始容量,默认为 256</param>
|
||||
/// <returns>StringBuilder 实例</returns>
|
||||
/// <remarks>
|
||||
/// 优先从池中获取可复用的实例,如果池为空则创建新实例。
|
||||
/// 使用完毕后应调用 <see cref="Return"/> 方法归还到池中以便复用。
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var sb = StringBuilderPool.Rent();
|
||||
@ -32,14 +40,29 @@ public static class StringBuilderPool
|
||||
/// </example>
|
||||
public static StringBuilder Rent(int capacity = DefaultCapacity)
|
||||
{
|
||||
var sb = new StringBuilder(capacity);
|
||||
return sb;
|
||||
if (Pool.TryTake(out var sb))
|
||||
{
|
||||
// 从池中获取到实例,确保容量满足需求
|
||||
if (sb.Capacity < capacity)
|
||||
{
|
||||
sb.Capacity = capacity;
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
// 池为空,创建新实例
|
||||
return new StringBuilder(capacity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 StringBuilder 归还到池中
|
||||
/// </summary>
|
||||
/// <param name="builder">要归还的 StringBuilder</param>
|
||||
/// <remarks>
|
||||
/// 如果 StringBuilder 的容量超过 <see cref="MaxRetainedCapacity"/>,
|
||||
/// 则不会放回池中,而是直接丢弃以避免保留过大的对象。
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var sb = StringBuilderPool.Rent();
|
||||
@ -64,7 +87,9 @@ public static class StringBuilderPool
|
||||
return;
|
||||
}
|
||||
|
||||
// 清空内容并放回池中
|
||||
builder.Clear();
|
||||
Pool.Add(builder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -108,4 +133,4 @@ public static class StringBuilderPool
|
||||
Return(Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user