Skip to content

Commit cc4f434

Browse files
lookbusy1344claude
andcommitted
Change allocation IDs from int to uint
This doubles the range of allocation IDs from ~2.1B to ~4.3B before overflow. Updated UnmanagedStringPool.lastAllocationId, PooledString.AllocationId, EmptyStringAllocationId constant, and related method signatures. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 148f322 commit cc4f434

File tree

4 files changed

+14
-15
lines changed

4 files changed

+14
-15
lines changed

PooledString.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ PooledString implements IDisposable to allow freeing its memory back to the pool
2121
/// Value type representing a string allocated from an unmanaged pool. Just a reference and an allocation ID, 12 bytes total.
2222
/// </summary>
2323
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
24-
public readonly record struct PooledString(UnmanagedStringPool Pool, int AllocationId) : IDisposable
24+
public readonly record struct PooledString(UnmanagedStringPool Pool, uint AllocationId) : IDisposable
2525
{
2626
// NOTE this struct is technically immutable, but some methods mutate the underlying pool like SetAtPosition() and Free()
2727
// It also implements IDisposable to call Free() automatically

Tests/ClearMethodTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ public void Clear_PreservesIdCounter_NewAllocationsGetHigherIds()
171171
public void Clear_OldIdsNeverReused()
172172
{
173173
// Arrange
174-
var ids = new int[10];
174+
var ids = new uint[10];
175175
for (var i = 0; i < 5; i++) {
176176
ids[i] = pool.Allocate($"String{i}").AllocationId;
177177
}

Tests/UnmanagedStringPoolEdgeCaseTests.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -385,9 +385,8 @@ public void GetAllocationInfo_InvalidId_ThrowsArgumentException()
385385
using var pool = new UnmanagedStringPool(1024);
386386

387387
// Test various invalid IDs
388-
Assert.Throws<ArgumentException>(() => pool.GetAllocationInfo(-1));
389388
Assert.Throws<ArgumentException>(() => pool.GetAllocationInfo(999999));
390-
Assert.Throws<ArgumentException>(() => pool.GetAllocationInfo(int.MaxValue));
389+
Assert.Throws<ArgumentException>(() => pool.GetAllocationInfo(uint.MaxValue));
391390
}
392391

393392
[Fact]
@@ -396,9 +395,8 @@ public void FreeString_InvalidId_NoException()
396395
using var pool = new UnmanagedStringPool(1024);
397396

398397
// These should not throw - just be ignored
399-
pool.FreeString(-1);
400398
pool.FreeString(999999);
401-
pool.FreeString(int.MaxValue);
399+
pool.FreeString(uint.MaxValue);
402400
}
403401

404402
[Fact]
@@ -492,7 +490,7 @@ public void EmptyStringPool_GetAllocationInfo_InvalidId_ThrowsArgumentException(
492490
{
493491
using var pool = new UnmanagedStringPool(1024);
494492
Assert.Throws<ArgumentException>(() =>
495-
pool.GetAllocationInfo(int.MaxValue));
493+
pool.GetAllocationInfo(uint.MaxValue));
496494
}
497495

498496
[Fact]
@@ -502,7 +500,7 @@ public void EmptyStringPool_FreeString_AnyId_NoEffect()
502500
// These should not throw or cause issues
503501
pool.FreeString(0);
504502
pool.FreeString(1);
505-
pool.FreeString(-1);
503+
pool.FreeString(999999);
506504
}
507505

508506
#endregion

UnmanagedStringPool.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ heap allocation.
3535
/// </summary>
3636
public sealed class UnmanagedStringPool : IDisposable
3737
{
38-
public const int EmptyStringAllocationId = 0; // Reserved for empty strings
38+
public const uint EmptyStringAllocationId = 0; // Reserved for empty strings
3939
private const int DefaultCollectionSize = 16; // Default size for internal collections
4040
private const double FragmentationThreshold = 35.0; // Fragmentation threshold for triggering coalescing (percentage)
4141
private const int MinimumBlocksForCoalescing = 8; // Minimum number of free blocks before considering coalescing
@@ -45,7 +45,7 @@ public sealed class UnmanagedStringPool : IDisposable
4545
private IntPtr basePtr; // Base pointer to the unmanaged memory block
4646
private int capacityBytes; // Total capacity in bytes of the pool
4747
private int offsetFromBase; // Current offset from the base pointer, tracks how much space has been used
48-
private int lastAllocationId; // Last allocation ID used, starts at 0. Monotonically increasing
48+
private uint lastAllocationId; // Last allocation ID used, starts at 0. Monotonically increasing
4949
private int freeOperationsSinceLastCoalesce; // Track recent coalescing to avoid excessive operations
5050
private int totalFreeBlocks; // Total number of free blocks in the pool
5151
private int totalFreeBytes; // Running total of free bytes to avoid recalculation
@@ -54,7 +54,7 @@ public sealed class UnmanagedStringPool : IDisposable
5454
private readonly SortedList<int, List<FreeBlock>> freeBlocksBySize = new(DefaultCollectionSize);
5555

5656
// Central registry of allocated , keyed by allocation ID
57-
private readonly Dictionary<int, AllocationInfo> allocations = new(DefaultCollectionSize);
57+
private readonly Dictionary<uint, AllocationInfo> allocations = new(DefaultCollectionSize);
5858

5959
/// <summary>
6060
/// Information about an allocated string. OffsetBytes is a convenience field, for when the block is freed. 16 bytes total
@@ -322,7 +322,7 @@ internal PooledString Allocate(int lengthChars)
322322
/// Get allocation info for a valid allocation ID
323323
/// </summary>
324324
[MethodImpl(MethodImplOptions.AggressiveInlining)]
325-
internal AllocationInfo GetAllocationInfo(int id)
325+
internal AllocationInfo GetAllocationInfo(uint id)
326326
{
327327
if (id == EmptyStringAllocationId) {
328328
return new(IntPtr.Zero, 0, 0);
@@ -336,7 +336,7 @@ internal AllocationInfo GetAllocationInfo(int id)
336336
/// <summary>
337337
/// Mark a string's memory as free for reuse
338338
/// </summary>
339-
internal void FreeString(int id)
339+
internal void FreeString(uint id)
340340
{
341341
if (IsDisposed || id == EmptyStringAllocationId) {
342342
// Empty strings do not need to be freed, they are always available
@@ -387,8 +387,9 @@ private static int AlignSize(int sizeBytes)
387387
[MethodImpl(MethodImplOptions.AggressiveInlining)]
388388
private PooledString RegisterAllocation(IntPtr ptr, int lengthChars, int offset)
389389
{
390-
if (lastAllocationId == int.MaxValue) {
391-
// in the unlikely event we reach MaxValue (2 billion+ allocations) we cannot allocate more
390+
if (lastAllocationId == uint.MaxValue) {
391+
// in the unlikely event we reach MaxValue (4 billion+ allocations) we cannot allocate more
392+
// it is more likely if the pool is long-lived and cleared repeatedly
392393
throw new OutOfMemoryException("Allocation overflow");
393394
}
394395

0 commit comments

Comments
 (0)