Skip to content

Commit 754bc9f

Browse files
committed
Release 5.16.0
1 parent c282cf4 commit 754bc9f

File tree

81 files changed

+1580
-463
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+1580
-463
lines changed

CHANGELOG.md

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,35 @@
11
Release Notes
22
====
33

4-
# 10-16-2023
4+
# 12-05-2024
5+
<a href="https://www.nuget.org/packages/dotnext/5.16.0">DotNext 5.16.0</a>
6+
* Added [LEB128](https://en.wikipedia.org/wiki/LEB128) encoder and decoder as a public API. See `DotNext.Buffers.Binary.Leb128<T>` type for more information
7+
* Added `SlideToEnd` method to `SpanWriter<T>` type
8+
* Added `IsBitSet` and `SetBit` generic methods to `Number` type
9+
* Added `DetachOrCopyBuffer` to `BufferWriterSlim<T>` type
10+
11+
<a href="https://www.nuget.org/packages/dotnext.metaprogramming/5.16.0">DotNext.Metaprogramming 5.16.0</a>
12+
* Updated dependencies
13+
14+
<a href="https://www.nuget.org/packages/dotnext.unsafe/5.16.0">DotNext.Unsafe 5.16.0</a>
15+
* Updated dependencies
16+
17+
<a href="https://www.nuget.org/packages/dotnext.threading/5.16.0">DotNext.Threading 5.16.0</a>
18+
* Async locks with synchronous acquisition methods now throw [LockRecursionException](https://learn.microsoft.com/en-us/dotnet/api/system.threading.lockrecursionexception) if the current thread tries to acquire the lock synchronously and recursively.
19+
* Added support of cancellation token to synchronous acquisition methods of `AsyncExclusiveLock` and `AsyncReaderWriterLock` classes
20+
* Introduced `LinkTo` method overload that supports multiple cancellation tokens
21+
22+
<a href="https://www.nuget.org/packages/dotnext.io/5.16.0">DotNext.IO 5.16.0</a>
23+
* Introduced `RandomAccessStream` class that represents [Stream](https://learn.microsoft.com/en-us/dotnet/api/system.io.stream) wrapper over the underlying data storage that supports random access pattern
24+
* Added extension method for `SpanWriter<byte>` that provides length-prefixed string encoding
25+
26+
<a href="https://www.nuget.org/packages/dotnext.net.cluster/5.16.0">DotNext.Net.Cluster 5.16.0</a>
27+
* Updated dependencies
28+
29+
<a href="https://www.nuget.org/packages/dotnext.aspnetcore.cluster/5.16.0">DotNext.AspNetCore.Cluster 5.16.0</a>
30+
* Updated dependencies
31+
32+
# 10-16-2024
533
<a href="https://www.nuget.org/packages/dotnext.threading/5.15.0">DotNext.Threading 5.15.0</a>
634
* Added support of synchronous lock acquisition to `AsyncExclusiveLock`, `AsyncReaderWriterLock`, `AsyncManualResetEvent`, `AsyncAutoResetEvent` so the users can easily migrate step-by-step from monitors and other synchronization primitives to async-friendly primitives
735
* Fixed random `InvalidOperationException` caused by `RandomAccessCache<TKey, TValue>`

README.md

+28-6
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,34 @@ 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: 10-16-2024
47+
Release Date: 12-05-2024
4848

49-
<a href="https://www.nuget.org/packages/dotnext.threading/5.15.0">DotNext.Threading 5.15.0</a>
50-
* Added support of synchronous lock acquisition to `AsyncExclusiveLock`, `AsyncReaderWriterLock`, `AsyncManualResetEvent`, `AsyncAutoResetEvent` so the users can easily migrate step-by-step from monitors and other synchronization primitives to async-friendly primitives
51-
* Fixed random `InvalidOperationException` caused by `RandomAccessCache<TKey, TValue>`
52-
* Added synchronous methods to `RandomAccessCache<TKey, TValue>` to support [251](https://github.com/dotnet/dotNext/issues/251) feature request
49+
<a href="https://www.nuget.org/packages/dotnext/5.16.0">DotNext 5.16.0</a>
50+
* Added [LEB128](https://en.wikipedia.org/wiki/LEB128) encoder and decoder as a public API. See `DotNext.Buffers.Binary.Leb128<T>` type for more information
51+
* Added `SlideToEnd` method to `SpanWriter<T>` type
52+
* Added `IsBitSet` and `SetBit` generic methods to `Number` type
53+
* Added `DetachOrCopyBuffer` to `BufferWriterSlim<T>` type
54+
55+
<a href="https://www.nuget.org/packages/dotnext.metaprogramming/5.16.0">DotNext.Metaprogramming 5.16.0</a>
56+
* Updated dependencies
57+
58+
<a href="https://www.nuget.org/packages/dotnext.unsafe/5.16.0">DotNext.Unsafe 5.16.0</a>
59+
* Updated dependencies
60+
61+
<a href="https://www.nuget.org/packages/dotnext.threading/5.16.0">DotNext.Threading 5.16.0</a>
62+
* Async locks with synchronous acquisition methods now throw [LockRecursionException](https://learn.microsoft.com/en-us/dotnet/api/system.threading.lockrecursionexception) if the current thread tries to acquire the lock synchronously and recursively.
63+
* Added support of cancellation token to synchronous acquisition methods of `AsyncExclusiveLock` and `AsyncReaderWriterLock` classes
64+
* Introduced `LinkTo` method overload that supports multiple cancellation tokens
65+
66+
<a href="https://www.nuget.org/packages/dotnext.io/5.16.0">DotNext.IO 5.16.0</a>
67+
* Introduced `RandomAccessStream` class that represents [Stream](https://learn.microsoft.com/en-us/dotnet/api/system.io.stream) wrapper over the underlying data storage that supports random access pattern
68+
* Added extension method for `SpanWriter<byte>` that provides length-prefixed string encoding
69+
70+
<a href="https://www.nuget.org/packages/dotnext.net.cluster/5.16.0">DotNext.Net.Cluster 5.16.0</a>
71+
* Updated dependencies
72+
73+
<a href="https://www.nuget.org/packages/dotnext.aspnetcore.cluster/5.16.0">DotNext.AspNetCore.Cluster 5.16.0</a>
74+
* Updated dependencies
5375

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

@@ -62,7 +84,7 @@ The libraries are versioned according to [Semantic Versioning 2.0](https://semve
6284
| 1.x | .NET Standard 2.0 | :x: |
6385
| 2.x | .NET Standard 2.1 | :x: |
6486
| 3.x | .NET Standard 2.1, .NET 5 | :x: |
65-
| 4.x | .NET 6 | :white_check_mark: |
87+
| 4.x | .NET 6 | :x: |
6688
| 5.x | .NET 8 | :heavy_check_mark: |
6789

6890
:x: - unsupported, :white_check_mark: - bug and security fixes only, :heavy_check_mark: - active development

src/Directory.Packages.props

+5-5
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,22 @@
1919
</ItemGroup>
2020
<ItemGroup>
2121
<!--Microsoft packages-->
22-
<PackageVersion Include="Microsoft.AspNetCore.Connections.Abstractions" Version="8.0.10" />
22+
<PackageVersion Include="Microsoft.AspNetCore.Connections.Abstractions" Version="8.0.11" />
2323
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
2424
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
2525
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
2626
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.1" />
2727
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
2828
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
2929
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
30-
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
30+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
3131
</ItemGroup>
3232
<ItemGroup>
3333
<!--Misc packages-->
34-
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
34+
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
3535
<PackageVersion Include="FastMember.Signed" Version="1.5.0" />
36-
<PackageVersion Include="xunit" Version="2.9.0" />
37-
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.0"/>
36+
<PackageVersion Include="xunit" Version="2.9.2" />
37+
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2"/>
3838
<PackageVersion Include="coverlet.collector" Version="6.0.2"/>
3939
</ItemGroup>
4040
</Project>

src/DotNext.Benchmarks/DotNext.Benchmarks.csproj

+1-1
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.11.0</Version>
11+
<Version>5.16.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>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.Numerics;
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
4+
5+
namespace DotNext.Buffers.Binary;
6+
7+
[StructLayout(LayoutKind.Auto)]
8+
internal struct Leb128Reader<T>() : IBufferReader, ISupplier<T>
9+
where T : struct, IBinaryInteger<T>
10+
{
11+
private Leb128<T> decoder;
12+
private bool incompleted = true;
13+
14+
readonly int IBufferReader.RemainingBytes => Unsafe.BitCast<bool, byte>(incompleted);
15+
16+
void IReadOnlySpanConsumer<byte>.Invoke(ReadOnlySpan<byte> source)
17+
=> incompleted = decoder.Append(MemoryMarshal.GetReference(source));
18+
19+
readonly T ISupplier<T>.Invoke() => decoder.Value;
20+
}

src/DotNext.IO/Buffers/BufferWriter.cs

+118-22
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace DotNext.Buffers;
77

88
using EncodingContext = DotNext.Text.EncodingContext;
99
using LengthFormat = IO.LengthFormat;
10+
using SevenBitEncodedInt = Binary.Leb128<uint>;
1011

1112
/// <summary>
1213
/// Represents extension methods for writing typed data into buffer.
@@ -47,28 +48,18 @@ internal static unsafe int WriteLength(this ref SpanWriter<byte> destination, in
4748
{
4849
LengthFormat.LittleEndian => &ByteBuffer.WriteLittleEndian,
4950
LengthFormat.BigEndian => &ByteBuffer.WriteBigEndian,
50-
LengthFormat.Compressed => &Write7BitEncodedInt,
51+
LengthFormat.Compressed => &ByteBuffer.WriteLeb128<int>,
5152
_ => throw new ArgumentOutOfRangeException(nameof(lengthFormat)),
5253
};
5354

5455
return writer(ref destination, value);
55-
56-
static int Write7BitEncodedInt(ref SpanWriter<byte> writer, int value)
57-
{
58-
foreach (var b in new SevenBitEncodedInt(value))
59-
{
60-
writer.Add() = b;
61-
}
62-
63-
return writer.WrittenCount;
64-
}
6556
}
6657

6758
internal static int WriteLength(this IBufferWriter<byte> buffer, int length, LengthFormat lengthFormat)
6859
{
69-
var writer = new SpanWriter<byte>(buffer.GetSpan(SevenBitEncodedInt.MaxSize));
70-
buffer.Advance(writer.WriteLength(length, lengthFormat));
71-
return writer.WrittenCount;
60+
var bytesWritten = WriteLength(buffer.GetSpan(SevenBitEncodedInt.MaxSizeInBytes), length, lengthFormat);
61+
buffer.Advance(bytesWritten);
62+
return bytesWritten;
7263
}
7364

7465
internal static int WriteLength(Span<byte> buffer, int length, LengthFormat lengthFormat)
@@ -77,26 +68,131 @@ internal static int WriteLength(Span<byte> buffer, int length, LengthFormat leng
7768
return writer.WriteLength(length, lengthFormat);
7869
}
7970

71+
private static int WriteLength(this ref BufferWriterSlim<byte> buffer, int length, LengthFormat lengthFormat)
72+
{
73+
var bytesWritten = WriteLength(buffer.GetSpan(SevenBitEncodedInt.MaxSizeInBytes), length, lengthFormat);
74+
buffer.Advance(bytesWritten);
75+
return bytesWritten;
76+
}
77+
8078
/// <summary>
8179
/// Encodes string using the specified encoding.
8280
/// </summary>
8381
/// <param name="writer">The buffer writer.</param>
84-
/// <param name="value">The sequence of characters.</param>
82+
/// <param name="chars">The sequence of characters.</param>
8583
/// <param name="context">The encoding context.</param>
8684
/// <param name="lengthFormat">String length encoding format; or <see langword="null"/> to prevent encoding of string length.</param>
8785
/// <returns>The number of written bytes.</returns>
88-
public static long Encode(this IBufferWriter<byte> writer, ReadOnlySpan<char> value, in EncodingContext context, LengthFormat? lengthFormat = null)
86+
public static long Encode(this IBufferWriter<byte> writer, ReadOnlySpan<char> chars, in EncodingContext context, LengthFormat? lengthFormat = null)
8987
{
90-
long result = lengthFormat.HasValue
91-
? writer.WriteLength(context.Encoding.GetByteCount(value), lengthFormat.GetValueOrDefault())
88+
var result = lengthFormat.HasValue
89+
? writer.WriteLength(context.Encoding.GetByteCount(chars), lengthFormat.GetValueOrDefault())
9290
: 0L;
9391

94-
context.GetEncoder().Convert(value, writer, true, out var bytesWritten, out _);
92+
context.GetEncoder().Convert(chars, writer, true, out var bytesWritten, out _);
93+
result += bytesWritten;
94+
95+
return result;
96+
}
97+
98+
/// <summary>
99+
/// Encodes string using the specified encoding.
100+
/// </summary>
101+
/// <param name="writer">The buffer writer.</param>
102+
/// <param name="chars">The sequence of characters.</param>
103+
/// <param name="context">The encoding context.</param>
104+
/// <param name="lengthFormat">String length encoding format; or <see langword="null"/> to prevent encoding of string length.</param>
105+
/// <returns>The number of written bytes.</returns>
106+
public static int Encode(this ref SpanWriter<byte> writer, scoped ReadOnlySpan<char> chars, in EncodingContext context, LengthFormat? lengthFormat = null)
107+
{
108+
var result = lengthFormat.HasValue
109+
? writer.WriteLength(context.Encoding.GetByteCount(chars), lengthFormat.GetValueOrDefault())
110+
: 0;
111+
112+
var bytesWritten = context.TryGetEncoder() is { } encoder
113+
? encoder.GetBytes(chars, writer.RemainingSpan, flush: true)
114+
: context.Encoding.GetBytes(chars, writer.RemainingSpan);
95115
result += bytesWritten;
116+
writer.Advance(bytesWritten);
96117

97118
return result;
98119
}
99120

121+
/// <summary>
122+
/// Writes a sequence of bytes prefixed with the length.
123+
/// </summary>
124+
/// <param name="writer">The buffer writer.</param>
125+
/// <param name="value">A sequence of bytes to be written.</param>
126+
/// <param name="lengthFormat">A format of the buffer length to be written.</param>
127+
/// <returns>A number of bytes written.</returns>
128+
public static int Write(this ref SpanWriter<byte> writer, scoped ReadOnlySpan<byte> value, LengthFormat lengthFormat)
129+
{
130+
var result = writer.WriteLength(value.Length, lengthFormat);
131+
result += writer.Write(value);
132+
133+
return result;
134+
}
135+
136+
/// <summary>
137+
/// Encodes string using the specified encoding.
138+
/// </summary>
139+
/// <param name="writer">The buffer writer.</param>
140+
/// <param name="chars">The sequence of characters.</param>
141+
/// <param name="context">The encoding context.</param>
142+
/// <param name="lengthFormat">String length encoding format; or <see langword="null"/> to prevent encoding of string length.</param>
143+
/// <returns>The number of written bytes.</returns>
144+
public static int Encode(this ref BufferWriterSlim<byte> writer, scoped ReadOnlySpan<char> chars, in EncodingContext context,
145+
LengthFormat? lengthFormat = null)
146+
{
147+
Span<byte> buffer;
148+
int byteCount, result;
149+
if (lengthFormat.HasValue)
150+
{
151+
byteCount = context.Encoding.GetByteCount(chars);
152+
result = writer.WriteLength(byteCount, lengthFormat.GetValueOrDefault());
153+
154+
buffer = writer.GetSpan(byteCount);
155+
byteCount = context.TryGetEncoder() is { } encoder
156+
? encoder.GetBytes(chars, buffer, flush: true)
157+
: context.Encoding.GetBytes(chars, buffer);
158+
159+
result += byteCount;
160+
writer.Advance(byteCount);
161+
}
162+
else
163+
{
164+
result = 0;
165+
var encoder = context.GetEncoder();
166+
byteCount = context.Encoding.GetMaxByteCount(1);
167+
for (int charsUsed, bytesWritten; !chars.IsEmpty; chars = chars.Slice(charsUsed), result += bytesWritten)
168+
{
169+
buffer = writer.GetSpan(byteCount);
170+
var maxChars = buffer.Length / byteCount;
171+
172+
encoder.Convert(chars, buffer, chars.Length <= maxChars, out charsUsed, out bytesWritten, out _);
173+
writer.Advance(bytesWritten);
174+
}
175+
}
176+
177+
return result;
178+
}
179+
180+
/// <summary>
181+
/// Writes a sequence of bytes prefixed with the length.
182+
/// </summary>
183+
/// <param name="writer">The buffer writer.</param>
184+
/// <param name="value">A sequence of bytes to be written.</param>
185+
/// <param name="lengthFormat">A format of the buffer length to be written.</param>
186+
/// <returns>A number of bytes written.</returns>
187+
public static int Write(this ref BufferWriterSlim<byte> writer, scoped ReadOnlySpan<byte> value, LengthFormat lengthFormat)
188+
{
189+
var result = writer.WriteLength(value.Length, lengthFormat);
190+
writer.Write(value);
191+
result += value.Length;
192+
193+
return result;
194+
}
195+
100196
private static bool TryFormat<T>(IBufferWriter<byte> writer, T value, Span<char> buffer, in EncodingContext context, LengthFormat? lengthFormat, ReadOnlySpan<char> format, IFormatProvider? provider, out long bytesWritten)
101197
where T : notnull, ISpanFormattable
102198
{
@@ -167,12 +263,12 @@ public static int Format<T>(this IBufferWriter<byte> writer, T value, LengthForm
167263
{
168264
null => 0,
169265
LengthFormat.BigEndian or LengthFormat.LittleEndian => sizeof(int),
170-
LengthFormat.Compressed => SevenBitEncodedInt.MaxSize,
266+
LengthFormat.Compressed => SevenBitEncodedInt.MaxSizeInBytes,
171267
_ => throw new ArgumentOutOfRangeException(nameof(lengthFormat)),
172268
};
173269

174270
int bytesWritten;
175-
for (int bufferSize = 0, actualLengthSize; ; bufferSize = bufferSize <= MaxBufferSize ? bufferSize << 1 : throw new InsufficientMemoryException())
271+
for (int bufferSize = 0; ; bufferSize = bufferSize <= MaxBufferSize ? bufferSize << 1 : throw new InsufficientMemoryException())
176272
{
177273
var buffer = writer.GetSpan(bufferSize);
178274

@@ -182,7 +278,7 @@ public static int Format<T>(this IBufferWriter<byte> writer, T value, LengthForm
182278
continue;
183279
}
184280

185-
actualLengthSize = lengthFormat.HasValue
281+
var actualLengthSize = lengthFormat.HasValue
186282
? WriteLength(buffer.Slice(0, expectedLengthSize), bytesWritten, lengthFormat.GetValueOrDefault())
187283
: 0;
188284

src/DotNext.IO/Buffers/IBufferReader.cs

+2-3
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,6 @@ TResult ISupplier<TResult>.Invoke()
252252
}
253253

254254
[StructLayout(LayoutKind.Auto)]
255-
256255
internal struct SkippingReader(long length) : IBufferReader
257256
{
258257
readonly int IBufferReader.RemainingBytes => int.CreateSaturating(length);
@@ -261,7 +260,7 @@ void IReadOnlySpanConsumer<byte>.Invoke(ReadOnlySpan<byte> source)
261260
=> length -= source.Length;
262261
}
263262

264-
internal struct BufferReader<TReader>(TReader reader) : IBufferReader, ISupplier<TReader>
263+
internal struct ProxyReader<TReader>(TReader reader) : IBufferReader, ISupplier<TReader>
265264
where TReader : struct, IBufferReader
266265
{
267266
int IBufferReader.RemainingBytes => reader.RemainingBytes;
@@ -273,5 +272,5 @@ void IReadOnlySpanConsumer<byte>.Invoke(ReadOnlySpan<byte> source)
273272

274273
readonly TReader ISupplier<TReader>.Invoke() => reader;
275274

276-
public static implicit operator BufferReader<TReader>(TReader reader) => new(reader);
275+
public static implicit operator ProxyReader<TReader>(TReader reader) => new(reader);
277276
}

src/DotNext.IO/Buffers/ReadOnlySpanFunc.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ namespace DotNext.Buffers;
1010
/// <param name="span">A read-only span of objects.</param>
1111
/// <param name="arg">A state object.</param>
1212
/// <returns>The value returned by the delegate.</returns>
13-
public delegate TResult ReadOnlySpanFunc<T, TArg, TResult>(ReadOnlySpan<T> span, TArg arg);
13+
public delegate TResult ReadOnlySpanFunc<T, in TArg, out TResult>(ReadOnlySpan<T> span, TArg arg);

0 commit comments

Comments
 (0)