Skip to content

Commit 060ecd6

Browse files
committed
Release 5.20.0
1 parent 62ac7bf commit 060ecd6

File tree

34 files changed

+1246
-107
lines changed

34 files changed

+1246
-107
lines changed

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
Release Notes
22
====
33

4+
# 03-30-2025
5+
<a href="https://www.nuget.org/packages/dotnext/5.20.0">DotNext 5.20.0</a>
6+
* Introduced `List.Repeat()` static method to construct read-only lists of repeatable items. Similar to [Enumerable.Repeat](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.repeat) but returns [IReadOnlyList&lt;T&gt;](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.ireadonlylist-1)
7+
* Added `Number.RoundUp` and `Number.RoundDown` generic extension methods to round the numbers to the multiple of the specified value
8+
9+
<a href="https://www.nuget.org/packages/dotnext.metaprogramming/5.20.0">DotNext.Metaprogramming 5.20.0</a>
10+
* Updated dependencies
11+
12+
<a href="https://www.nuget.org/packages/dotnext.unsafe/5.20.0">DotNext.Unsafe 5.20.0</a>
13+
* Added static methods to `UnmanagedMemory` for page-aligned memory allocation
14+
15+
<a href="https://www.nuget.org/packages/dotnext.threading/5.20.0">DotNext.Threading 5.20.0</a>
16+
* Improved debugging experience of `RandomAccessCache<TKey, TValue>` class
17+
* Added `AsyncCounter.TryIncrement` method that allows to specify the upper bound of the counter. As a result, `AsyncCounter` class can be used as a rate limiter
18+
19+
<a href="https://www.nuget.org/packages/dotnext.io/5.20.0">DotNext.IO 5.20.0</a>
20+
* Introduced `DiskSpacePool` class to assist with building caches when the cached data stored on the disk rather than in memory. The class can be combined with `RandomAccessCache<TKey, TValue>` to organize L1 application-specific caches (while L0 resides in memory and can be organized on top of `RandomAccessCache<TKey, TValue>` as well)
21+
22+
<a href="https://www.nuget.org/packages/dotnext.net.cluster/5.20.0">DotNext.Net.Cluster 5.20.0</a>
23+
* Updated dependencies
24+
25+
<a href="https://www.nuget.org/packages/dotnext.aspnetcore.cluster/5.20.0">DotNext.AspNetCore.Cluster 5.20.0</a>
26+
* Updated dependencies
27+
428
# 03-06-2025
529
<a href="https://www.nuget.org/packages/dotnext/5.19.1">DotNext 5.19.1</a>
630
* Smallish performance improvements of `SparseBufferWriter<T>`

README.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,29 @@ All these things are implemented in 100% managed code on top of existing .NET AP
4444
* [NuGet Packages](https://www.nuget.org/profiles/rvsakno)
4545

4646
# What's new
47-
Release Date: 03-06-2025
47+
Release Date: 03-30-2025
4848

49-
<a href="https://www.nuget.org/packages/dotnext/5.19.1">DotNext 5.19.1</a>
50-
* Smallish performance improvements of `SparseBufferWriter<T>`
49+
<a href="https://www.nuget.org/packages/dotnext/5.20.0">DotNext 5.20.0</a>
50+
* Introduced `List.Repeat()` static method to construct read-only lists of repeatable items. Similar to [Enumerable.Repeat](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.repeat) but returns [IReadOnlyList&lt;T&gt;](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.ireadonlylist-1)
51+
* Added `Number.RoundUp` and `Number.RoundDown` generic extension methods to round the numbers to the multiple of the specified value
5152

52-
<a href="https://www.nuget.org/packages/dotnext.metaprogramming/5.19.1">DotNext.Metaprogramming 5.19.1</a>
53+
<a href="https://www.nuget.org/packages/dotnext.metaprogramming/5.20.0">DotNext.Metaprogramming 5.20.0</a>
5354
* Updated dependencies
5455

55-
<a href="https://www.nuget.org/packages/dotnext.unsafe/5.19.1">DotNext.Unsafe 5.19.1</a>
56-
* Updated dependencies
56+
<a href="https://www.nuget.org/packages/dotnext.unsafe/5.20.0">DotNext.Unsafe 5.20.0</a>
57+
* Added static methods to `UnmanagedMemory` for page-aligned memory allocation
5758

58-
<a href="https://www.nuget.org/packages/dotnext.threading/5.19.1">DotNext.Threading 5.19.1</a>
59-
* Fixed weight counting in `RandomAccessCache<TKey, TValue, TWeight>` class
59+
<a href="https://www.nuget.org/packages/dotnext.threading/5.20.0">DotNext.Threading 5.20.0</a>
60+
* Improved debugging experience of `RandomAccessCache<TKey, TValue>` class
61+
* Added `AsyncCounter.TryIncrement` method that allows to specify the upper bound of the counter. As a result, `AsyncCounter` class can be used as a rate limiter
6062

61-
<a href="https://www.nuget.org/packages/dotnext.io/5.19.1">DotNext.IO 5.19.1</a>
62-
* Updated dependencies
63+
<a href="https://www.nuget.org/packages/dotnext.io/5.20.0">DotNext.IO 5.20.0</a>
64+
* Introduced `DiskSpacePool` class to assist with building caches when the cached data stored on the disk rather than in memory. The class can be combined with `RandomAccessCache<TKey, TValue>` to organize L1 application-specific caches (while L0 resides in memory and can be organized on top of `RandomAccessCache<TKey, TValue>` as well)
6365

64-
<a href="https://www.nuget.org/packages/dotnext.net.cluster/5.19.1">DotNext.Net.Cluster 5.19.1</a>
66+
<a href="https://www.nuget.org/packages/dotnext.net.cluster/5.20.0">DotNext.Net.Cluster 5.20.0</a>
6567
* Updated dependencies
6668

67-
<a href="https://www.nuget.org/packages/dotnext.aspnetcore.cluster/5.19.1">DotNext.AspNetCore.Cluster 5.19.1</a>
69+
<a href="https://www.nuget.org/packages/dotnext.aspnetcore.cluster/5.20.0">DotNext.AspNetCore.Cluster 5.20.0</a>
6870
* Updated dependencies
6971

7072
Changelog for previous versions located [here](./CHANGELOG.md).

src/DotNext.Benchmarks/DotNext.Benchmarks.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<RootNamespace>DotNext</RootNamespace>
99
<StartupObject>DotNext.Program</StartupObject>
1010
<IsPackable>false</IsPackable>
11-
<Version>5.19.0</Version>
11+
<Version>5.20.0</Version>
1212
<Authors>.NET Foundation and Contributors</Authors>
1313
<Product>.NEXT Family of Libraries</Product>
1414
<Description>Various benchmarks demonstrating performance aspects of .NEXT extensions</Description>

src/DotNext.IO/DotNext.IO.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<Authors>.NET Foundation and Contributors</Authors>
1212
<Company />
1313
<Product>.NEXT Family of Libraries</Product>
14-
<VersionPrefix>5.19.1</VersionPrefix>
14+
<VersionPrefix>5.20.0</VersionPrefix>
1515
<VersionSuffix></VersionSuffix>
1616
<AssemblyName>DotNext.IO</AssemblyName>
1717
<PackageLicenseExpression>MIT</PackageLicenseExpression>

src/DotNext.IO/IO/MemoryMappedFiles/ReadOnlySequenceAccessor.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ internal MemoryManager(IMemorySegmentProvider cursor, in Segment segment)
3131
Segment = segment;
3232
}
3333

34-
public override unsafe Span<byte> GetSpan()
34+
public override Span<byte> GetSpan()
3535
=> Cursor.GetSpan(in Segment);
3636

3737
public override Memory<byte> Memory => CreateMemory(Segment.Length);
3838

39-
public override unsafe MemoryHandle Pin(int index)
39+
public override MemoryHandle Pin(int index)
4040
=> Cursor.Pin(in Segment, index);
4141

4242
public override void Unpin()
@@ -201,7 +201,7 @@ private unsafe Span<byte> GetSpan(in Segment window)
201201
if (current != window)
202202
{
203203
segment?.ReleasePointerAndDispose();
204-
ptr = default;
204+
ptr = null;
205205
current = window;
206206
segment = mappedFile.CreateViewAccessor(window.Offset, window.Length, MemoryMappedFileAccess.Read);
207207
segment.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using System.Runtime.CompilerServices;
2+
3+
namespace DotNext.Runtime.Caching;
4+
5+
public partial class DiskSpacePool
6+
{
7+
private volatile SegmentHandle? freeList;
8+
private long cursor;
9+
10+
private long AllocateSegment() => Interlocked.Add(ref cursor, MaxSegmentSize);
11+
12+
private SegmentHandle RentOffset()
13+
{
14+
SegmentHandle? tmp;
15+
for (var headCopy = freeList;; headCopy = tmp)
16+
{
17+
if (headCopy is null)
18+
{
19+
tmp = new(this);
20+
break;
21+
}
22+
23+
tmp = headCopy.TryGetNext(out var next)
24+
? Interlocked.CompareExchange(ref freeList, next, headCopy)
25+
: freeList;
26+
27+
if (ReferenceEquals(tmp, headCopy))
28+
{
29+
tmp.MoveToCompletedState(this);
30+
break;
31+
}
32+
}
33+
34+
return tmp;
35+
}
36+
37+
private void ReturnOffset(long offset)
38+
{
39+
var node = new SegmentHandle(offset);
40+
for (SegmentHandle? headCopy = freeList, tmp;; headCopy = tmp)
41+
{
42+
node.SetNext(headCopy);
43+
44+
tmp = Interlocked.CompareExchange(ref freeList, node, headCopy);
45+
if (ReferenceEquals(tmp, headCopy))
46+
break;
47+
}
48+
}
49+
50+
internal sealed class SegmentHandle(long offset)
51+
{
52+
// Can be null, or Node, or DiskSpacePool, or Sentinel. Null treats as null Node.
53+
// Every type represents a state of the node.
54+
// Possible transitions:
55+
// null or Node => DiskSpacePool - taken from the free list
56+
// DiskSpacePool => Sentinel - disposed
57+
private object? ownerOrNext;
58+
59+
internal SegmentHandle(DiskSpacePool pool)
60+
: this(pool.AllocateSegment())
61+
=> ownerOrNext = pool;
62+
63+
internal bool TryGetNext(out SegmentHandle? next)
64+
{
65+
var ownerOrNextCopy = ownerOrNext;
66+
bool result;
67+
next = (result = ownerOrNextCopy is null or SegmentHandle)
68+
? Unsafe.As<SegmentHandle?>(ownerOrNextCopy)
69+
: null;
70+
71+
return result;
72+
}
73+
74+
internal void SetNext(SegmentHandle? next) => ownerOrNext = next;
75+
76+
internal void MoveToCompletedState(DiskSpacePool pool) => ownerOrNext = pool;
77+
78+
internal DiskSpacePool? TryGetOwner() => ReferenceEquals(ownerOrNext, Sentinel.Instance) ? null : Unsafe.As<DiskSpacePool?>(ownerOrNext);
79+
80+
internal void MoveToDisposedState() => ownerOrNext = Sentinel.Instance;
81+
82+
internal long Offset => offset;
83+
84+
public override string ToString() => offset.ToString();
85+
}
86+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using Microsoft.Win32.SafeHandles;
2+
3+
namespace DotNext.Runtime.Caching;
4+
5+
using Buffers;
6+
7+
public partial class DiskSpacePool
8+
{
9+
private readonly SafeFileHandle handle;
10+
private readonly IReadOnlyList<ReadOnlyMemory<byte>> zeroes;
11+
12+
private void EraseSegment(long offset)
13+
{
14+
switch (zeroes)
15+
{
16+
case []:
17+
break;
18+
case [var buffer]:
19+
RandomAccess.Write(handle, buffer.Span, offset);
20+
break;
21+
default:
22+
RandomAccess.Write(handle, zeroes, offset);
23+
break;
24+
}
25+
}
26+
27+
private void ReleaseSegment(long offset)
28+
{
29+
try
30+
{
31+
EraseSegment(offset);
32+
}
33+
finally
34+
{
35+
ReturnOffset(offset);
36+
}
37+
}
38+
39+
private ValueTask EraseSegmentAsync(long offset) => zeroes switch
40+
{
41+
[] => ValueTask.CompletedTask,
42+
[var buffer] => RandomAccess.WriteAsync(handle, buffer, offset),
43+
_ => RandomAccess.WriteAsync(handle, zeroes, offset),
44+
};
45+
46+
private void Write(long absoluteOffset, ReadOnlySpan<byte> buffer, int segmentOffset)
47+
=> RandomAccess.Write(handle, buffer, absoluteOffset + segmentOffset);
48+
49+
private ValueTask WriteAsync(long absoluteOffset, ReadOnlyMemory<byte> buffer, int segmentOffset, CancellationToken token)
50+
=> RandomAccess.WriteAsync(handle, buffer, absoluteOffset + segmentOffset, token);
51+
52+
private int Read(long absoluteOffset, Span<byte> buffer, int segmentOffset)
53+
=> Read(absoluteOffset, buffer, segmentOffset, MaxSegmentSize - segmentOffset);
54+
55+
private int Read(long absoluteOffset, Span<byte> buffer, int segmentOffset, int length)
56+
=> RandomAccess.Read(handle, buffer.TrimLength(length), absoluteOffset + segmentOffset);
57+
58+
private ValueTask<int> ReadAsync(long absoluteOffset, Memory<byte> buffer, int segmentOffset, CancellationToken token)
59+
=> ReadAsync(absoluteOffset, buffer, segmentOffset, MaxSegmentSize - segmentOffset, token);
60+
61+
private ValueTask<int> ReadAsync(long absoluteOffset, Memory<byte> buffer, int segmentOffset, int length, CancellationToken token)
62+
=> RandomAccess.ReadAsync(handle, buffer.TrimLength(length), absoluteOffset + segmentOffset, token);
63+
}

0 commit comments

Comments
 (0)