diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0f715832e..b56d4a9fe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,31 @@
Release Notes
====
+# 03-08-2024
+DotNext 5.2.0
+* Added `Number.IsPrime` static method that allows to check whether the specified number is a prime number
+* Fixed AOT compatibility issues
+
+DotNext.Metaprogramming 5.2.0
+* Updated dependencies
+
+DotNext.Unsafe 5.2.0
+* Updated dependencies
+
+DotNext.Threading 5.2.0
+* Added specialized `IndexPool` data type that can be useful for implementing fast object pools
+
+DotNext.IO 5.2.0
+* Updated dependencies
+
+DotNext.Net.Cluster 5.2.0
+* Fixed [226](https://github.com/dotnet/dotNext/issues/226)
+* Fixed [221](https://github.com/dotnet/dotNext/issues/221)
+
+DotNext.AspNetCore.Cluster 5.2.0
+* Fixed [226](https://github.com/dotnet/dotNext/issues/226)
+* Fixed [221](https://github.com/dotnet/dotNext/issues/221)
+
# 02-28-2024
DotNext 5.1.0
* Added `Span.Advance` extension method for spans
diff --git a/README.md b/README.md
index e06c40b66..6e06f5798 100644
--- a/README.md
+++ b/README.md
@@ -44,30 +44,31 @@ All these things are implemented in 100% managed code on top of existing .NET AP
* [NuGet Packages](https://www.nuget.org/profiles/rvsakno)
# What's new
-Release Date: 02-28-2024
+Release Date: 03-08-2024
-DotNext 5.1.0
-* Added `Span.Advance` extension method for spans
-* `CollectionType.GetItemType` now correctly recognizes enumerable pattern even if target type doesn't implement `IEnumerable`
+DotNext 5.2.0
+* Added `Number.IsPrime` static method that allows to check whether the specified number is a prime number
+* Fixed AOT compatibility issues
-DotNext.Metaprogramming 5.1.0
+DotNext.Metaprogramming 5.2.0
* Updated dependencies
-DotNext.Unsafe 5.1.0
-* Added `UnmanagedMemory.AsMemory` static method that allows to wrap unmanaged pointer into [Memory<T>](https://learn.microsoft.com/en-us/dotnet/api/system.memory-1)
-
-DotNext.Threading 5.1.0
+DotNext.Unsafe 5.2.0
* Updated dependencies
-DotNext.IO 5.1.0
-* Merged [225](https://github.com/dotnet/dotNext/pull/225)
-* Added `AsUnbufferedStream` extension method for [SafeFileHandle](https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.safehandles.safefilehandle) class
+DotNext.Threading 5.2.0
+* Added specialized `IndexPool` data type that can be useful for implementing fast object pools
-DotNext.Net.Cluster 5.1.0
+DotNext.IO 5.2.0
* Updated dependencies
-DotNext.AspNetCore.Cluster 5.1.0
-* Updated dependencies
+DotNext.Net.Cluster 5.2.0
+* Fixed [226](https://github.com/dotnet/dotNext/issues/226)
+* Fixed [221](https://github.com/dotnet/dotNext/issues/221)
+
+DotNext.AspNetCore.Cluster 5.2.0
+* Fixed [226](https://github.com/dotnet/dotNext/issues/226)
+* Fixed [221](https://github.com/dotnet/dotNext/issues/221)
Changelog for previous versions located [here](./CHANGELOG.md).
diff --git a/src/DotNext.IO/DotNext.IO.csproj b/src/DotNext.IO/DotNext.IO.csproj
index 3608bef3f..829ed2814 100644
--- a/src/DotNext.IO/DotNext.IO.csproj
+++ b/src/DotNext.IO/DotNext.IO.csproj
@@ -11,7 +11,7 @@
.NET Foundation and Contributors.NEXT Family of Libraries
- 5.1.0
+ 5.2.0DotNext.IOMIT
diff --git a/src/DotNext.Metaprogramming/DotNext.Metaprogramming.csproj b/src/DotNext.Metaprogramming/DotNext.Metaprogramming.csproj
index 9b8e2ea7e..c8b5b134a 100644
--- a/src/DotNext.Metaprogramming/DotNext.Metaprogramming.csproj
+++ b/src/DotNext.Metaprogramming/DotNext.Metaprogramming.csproj
@@ -8,7 +8,7 @@
truefalsenullablePublicOnly
- 5.1.0
+ 5.2.0.NET Foundation.NEXT Family of Libraries
diff --git a/src/DotNext.Tests/Collections/Concurrent/IndexPoolTests.cs b/src/DotNext.Tests/Collections/Concurrent/IndexPoolTests.cs
new file mode 100644
index 000000000..ddad312dc
--- /dev/null
+++ b/src/DotNext.Tests/Collections/Concurrent/IndexPoolTests.cs
@@ -0,0 +1,107 @@
+namespace DotNext.Collections.Concurrent;
+
+public sealed class IndexPoolTests : Test
+{
+ [Fact]
+ public static void EmptyPool()
+ {
+ var pool = default(IndexPool);
+ False(pool.TryPeek(out _));
+ False(pool.TryTake(out _));
+ DoesNotContain(10, pool);
+ Empty(pool);
+ }
+
+ [Fact]
+ public static void TakeAll()
+ {
+ var pool = new IndexPool();
+ NotEmpty(pool);
+
+ for (var i = 0; i <= IndexPool.MaxValue; i++)
+ {
+ Equal(i, pool.Take());
+ }
+
+ Throws(() => pool.Take());
+ }
+
+ [Fact]
+ public static void ContainsAll()
+ {
+ var pool = new IndexPool();
+ for (var i = 0; i <= IndexPool.MaxValue; i++)
+ {
+ True(pool.Contains(i));
+ }
+
+ for (var i = 0; i <= IndexPool.MaxValue; i++)
+ {
+ Equal(i, pool.Take());
+ }
+
+ for (var i = 0; i <= IndexPool.MaxValue; i++)
+ {
+ False(pool.Contains(i));
+ }
+ }
+
+ [Fact]
+ public static void Enumerator()
+ {
+ var pool = new IndexPool();
+ var expected = new int[pool.Count];
+ Span.ForEach(expected, static (ref int value, int index) => value = index);
+
+ Equal(expected, pool.ToArray());
+
+ while (pool.TryTake(out _))
+ {
+ // take all indicies
+ }
+
+ Equal(Array.Empty(), pool.ToArray());
+ }
+
+ [Fact]
+ public static void CustomMaxValue()
+ {
+ var pool = new IndexPool(maxValue: 2);
+ Equal(3, pool.Count);
+
+ Equal(0, pool.Take());
+ Equal(1, pool.Take());
+ Equal(2, pool.Take());
+
+ False(pool.TryTake(out _));
+ Empty(pool);
+ }
+
+ [Fact]
+ public static void Consistency()
+ {
+ var pool = new IndexPool();
+ Equal(0, pool.Take());
+
+ Equal(1, pool.Take());
+ pool.Return(1);
+
+ Equal(1, pool.Take());
+ pool.Return(1);
+
+ pool.Return(0);
+ }
+
+ [Fact]
+ public static void TakeReturnMany()
+ {
+ var pool = new IndexPool();
+ Span indicies = stackalloc int[IndexPool.Capacity];
+
+ Equal(IndexPool.Capacity, pool.Take(indicies));
+ Empty(pool);
+
+ pool.Return(indicies);
+ NotEmpty(pool);
+ }
+}
\ No newline at end of file
diff --git a/src/DotNext.Tests/Net/Cluster/Consensus/Raft/LeaderStateContextTests.cs b/src/DotNext.Tests/Net/Cluster/Consensus/Raft/LeaderStateContextTests.cs
index f93f2f1bd..6fe3cfd6c 100644
--- a/src/DotNext.Tests/Net/Cluster/Consensus/Raft/LeaderStateContextTests.cs
+++ b/src/DotNext.Tests/Net/Cluster/Consensus/Raft/LeaderStateContextTests.cs
@@ -96,4 +96,19 @@ public static void Resize()
private static LeaderState.Replicator CreateReplicator(DummyRaftClusterMember member)
=> new(member, NullLogger.Instance);
+
+ [Fact]
+ public static void RegressionIssue221()
+ {
+ const int length = 16;
+ using var context = new LeaderState.Context(length);
+
+ var keys = new DummyRaftClusterMember[length];
+ Span.Initialize(keys);
+
+ for (var i = 0; i < length; i++)
+ {
+ var ctx = context.GetOrCreate(keys[i], CreateReplicator);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/DotNext.Tests/Numerics/NumberTests.cs b/src/DotNext.Tests/Numerics/NumberTests.cs
index 7a9deb575..22431799a 100644
--- a/src/DotNext.Tests/Numerics/NumberTests.cs
+++ b/src/DotNext.Tests/Numerics/NumberTests.cs
@@ -63,4 +63,15 @@ public static void BinarySize()
Equal(sizeof(int), Number.GetMaxByteCount());
Equal(sizeof(long), Number.GetMaxByteCount());
}
+
+ [Fact]
+ public static void IsPrime()
+ {
+ False(Number.IsPrime(1L));
+ True(Number.IsPrime(2L));
+ True(Number.IsPrime(3));
+ False(Number.IsPrime(4));
+
+ True(Number.IsPrime(1669));
+ }
}
\ No newline at end of file
diff --git a/src/DotNext.Threading/Collections/Concurrent/IndexPool.cs b/src/DotNext.Threading/Collections/Concurrent/IndexPool.cs
new file mode 100644
index 000000000..b8e3e5bcc
--- /dev/null
+++ b/src/DotNext.Threading/Collections/Concurrent/IndexPool.cs
@@ -0,0 +1,252 @@
+using System.Collections;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Numerics;
+using System.Runtime.InteropServices;
+
+namespace DotNext.Collections.Concurrent;
+
+///
+/// Represents a pool of integer values.
+///
+///
+/// This type is thread-safe.
+///
+[EditorBrowsable(EditorBrowsableState.Advanced)]
+[StructLayout(LayoutKind.Auto)]
+public struct IndexPool : ISupplier, IConsumer, IReadOnlyCollection
+{
+ private readonly int maxValue;
+ private ulong bitmask;
+
+ ///
+ /// Initializes a new pool that can return an integer value within the range [0..].
+ ///
+ public IndexPool()
+ {
+ bitmask = ulong.MaxValue;
+ maxValue = MaxValue;
+ }
+
+ ///
+ /// Initializes a new pool that can return an integer within the range [0..].
+ ///
+ /// The maximum possible value to return, inclusive.
+ ///
+ /// is less than zero;
+ /// or greater than .
+ ///
+ public IndexPool(int maxValue)
+ {
+ if (maxValue < 0 || maxValue > MaxValue)
+ throw new ArgumentOutOfRangeException(nameof(maxValue));
+
+ bitmask = ulong.MaxValue;
+ this.maxValue = maxValue;
+ }
+
+ ///
+ /// Gets the maximum number that can be returned by the pool.
+ ///
+ /// Always returns 63.
+ public static int MaxValue => Capacity - 1;
+
+ ///
+ /// Gets the maximum capacity of the pool.
+ ///
+ public static int Capacity => sizeof(ulong) * 8;
+
+ ///
+ /// Tries to peek the next available index from the pool, without acquiring it.
+ ///
+ /// The index which is greater than or equal to zero.
+ /// if the index is available for rent; otherwise, .
+ public readonly bool TryPeek(out int result)
+ => (result = BitOperations.TrailingZeroCount(Volatile.Read(in bitmask))) <= maxValue;
+
+ ///
+ /// Returns the available index from the pool.
+ ///
+ /// The index which is greater than or equal to zero.
+ /// if the index is successfully rented from the pool; otherwise, .
+ ///
+ public bool TryTake(out int result)
+ {
+ ulong current, newValue = Volatile.Read(in bitmask);
+ do
+ {
+ result = BitOperations.TrailingZeroCount(current = newValue);
+
+ if (result > maxValue)
+ return false;
+
+ newValue = current ^ (1UL << result);
+ }
+ while ((newValue = Interlocked.CompareExchange(ref bitmask, newValue, current)) != current);
+
+ return true;
+ }
+
+ ///
+ /// Returns the available index from the pool.
+ ///
+ /// The index which is greater than or equal to zero.
+ /// There is no available index to return.
+ ///
+ public int Take()
+ {
+ if (!TryTake(out var result))
+ ThrowOverflowException();
+
+ return result;
+
+ [DoesNotReturn]
+ [StackTraceHidden]
+ static void ThrowOverflowException() => throw new OverflowException();
+ }
+
+ ///
+ /// Takes all available indicies, atomically.
+ ///
+ ///
+ /// The buffer to be modified with the indicies taken from the pool.
+ /// The size of the buffer should not be less than .
+ ///
+ /// The number of indicies written to the buffer.
+ /// is too small to place indicies.
+ ///
+ public int Take(Span indicies)
+ {
+ if (indicies.Length < Capacity)
+ throw new ArgumentOutOfRangeException(nameof(indicies));
+
+ var oldValue = Interlocked.Exchange(ref bitmask, 0UL);
+ var bufferOffset = 0;
+
+ for (int bitPosition = 0; bitPosition < Capacity; bitPosition++)
+ {
+ if (Contains(oldValue, bitPosition))
+ {
+ indicies[bufferOffset++] = bitPosition;
+ }
+ }
+
+ return bufferOffset;
+ }
+
+ ///
+ int ISupplier.Invoke() => Take();
+
+ ///
+ /// Returns an index previously obtained using back to the pool.
+ ///
+ ///
+ /// is less than zero or greater than the maximum
+ /// value specified for this pool.
+ public void Return(int value)
+ {
+ if (value < 0 || value > maxValue)
+ ThrowArgumentOutOfRangeException();
+
+ Interlocked.Or(ref bitmask, 1UL << value);
+
+ [DoesNotReturn]
+ [StackTraceHidden]
+ static void ThrowArgumentOutOfRangeException()
+ => throw new ArgumentOutOfRangeException(nameof(value));
+ }
+
+ ///
+ /// Returns multiple indicies, atomically.
+ ///
+ /// The buffer of indicies to return back to the pool.
+ public void Return(ReadOnlySpan indicies)
+ {
+ var newValue = 0UL;
+
+ foreach (var index in indicies)
+ {
+ newValue |= 1UL << index;
+ }
+
+ Interlocked.Or(ref bitmask, newValue);
+ }
+
+ ///
+ void IConsumer.Invoke(int value) => Return(value);
+
+ ///
+ /// Determines whether the specified index is available for rent.
+ ///
+ /// The value to check.
+ /// if is available for rent; otherwise, .
+ public readonly bool Contains(int value)
+ => value >= 0 && value <= maxValue && Contains(Volatile.Read(in bitmask), value);
+
+ private static bool Contains(ulong bitmask, int index)
+ => (bitmask & (1UL << index)) is not 0UL;
+
+ ///
+ /// Gets the number of available indicies.
+ ///
+ public readonly int Count => Math.Min(BitOperations.PopCount(bitmask), maxValue + 1);
+
+ ///
+ /// Gets an enumerator over available indicies in the pool.
+ ///
+ /// The enumerator over available indicies in this pool.
+ public readonly Enumerator GetEnumerator() => new(Volatile.Read(in bitmask), maxValue);
+
+ ///
+ readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator().AsClassicEnumerator();
+
+ ///
+ readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator().AsClassicEnumerator();
+
+ ///
+ /// Represents an enumerator over available indicies in the pool.
+ ///
+ [StructLayout(LayoutKind.Auto)]
+ public struct Enumerator
+ {
+ private readonly ulong bitmask;
+ private readonly int maxValue;
+ private int current;
+
+ internal Enumerator(ulong bitmask, int maxValue)
+ {
+ this.bitmask = bitmask;
+ this.maxValue = maxValue;
+ current = -1;
+ }
+
+ ///
+ /// Gets the current index.
+ ///
+ public readonly int Current => current;
+
+ ///
+ /// Advances to the next available index.
+ ///
+ /// if enumerator advanced successfully; otherwise, .
+ public bool MoveNext()
+ {
+ while (++current <= maxValue)
+ {
+ if (Contains(bitmask, current))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ internal IEnumerator AsClassicEnumerator()
+ {
+ while (MoveNext())
+ yield return Current;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DotNext.Threading/DotNext.Threading.csproj b/src/DotNext.Threading/DotNext.Threading.csproj
index 04e4f78ad..45872754c 100644
--- a/src/DotNext.Threading/DotNext.Threading.csproj
+++ b/src/DotNext.Threading/DotNext.Threading.csproj
@@ -7,7 +7,7 @@
truetruenullablePublicOnly
- 5.1.0
+ 5.2.0.NET Foundation and Contributors.NEXT Family of Libraries
diff --git a/src/DotNext.Unsafe/DotNext.Unsafe.csproj b/src/DotNext.Unsafe/DotNext.Unsafe.csproj
index af05c5a80..abad20ae6 100644
--- a/src/DotNext.Unsafe/DotNext.Unsafe.csproj
+++ b/src/DotNext.Unsafe/DotNext.Unsafe.csproj
@@ -7,7 +7,7 @@
enabletruetrue
- 5.1.0
+ 5.2.0nullablePublicOnly.NET Foundation and Contributors
diff --git a/src/DotNext/DotNext.csproj b/src/DotNext/DotNext.csproj
index 2ae09a6a1..7f376f80a 100644
--- a/src/DotNext/DotNext.csproj
+++ b/src/DotNext/DotNext.csproj
@@ -11,7 +11,7 @@
.NET Foundation and Contributors.NEXT Family of Libraries
- 5.1.0
+ 5.2.0DotNextMIT
diff --git a/src/DotNext/Func.cs b/src/DotNext/Func.cs
index ec5448d0b..4ea604ecb 100644
--- a/src/DotNext/Func.cs
+++ b/src/DotNext/Func.cs
@@ -92,21 +92,16 @@ public static Func Constant(T obj)
if (typeof(T) == typeof(bool))
return Unsafe.As>(Constant(Unsafe.As(ref obj)));
- // cache nulls
- if (obj is null)
- return Default!;
-
// slow path - allocates a new delegate
- unsafe
- {
- return DelegateHelpers.CreateDelegate