diff --git a/CHANGELOG.md b/CHANGELOG.md index 74e741719..155da0f47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,32 @@ Release Notes ==== +# 10-13-2024 +DotNext 5.14.0 +* Added helpers to `DelegateHelpers` class to convert delegates with synchronous signature to their asynchronous counterparts +* Added support of async enumerator to `SingletonList` +* Fixed exception propagation in `DynamicTaskAwaitable` +* Added support of [ConfigureAwaitOptions](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.configureawaitoptions) to `DynamicTaskAwaitable` + +DotNext.Metaprogramming 5.14.0 +* Updated dependencies + +DotNext.Unsafe 5.14.0 +* Updated dependencies + +DotNext.Threading 5.14.0 +* Updated dependencies + +DotNext.IO 5.14.0 +* Updated dependencies + +DotNext.Net.Cluster 5.14.0 +* Fixed graceful shutdown of Raft TCP listener +* Updated vulnerable dependencies + +DotNext.AspNetCore.Cluster 5.14.0 +* Updated vulnerable dependencies + # 08-30-2024 DotNext 5.13.0 * Improved interoperability of `DotNext.Runtime.ValueReference` and `DotNext.Runtime.ReadOnlyValueReference` with .NEXT ecosystem diff --git a/README.md b/README.md index f536b72a5..6df156b15 100644 --- a/README.md +++ b/README.md @@ -44,35 +44,32 @@ 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: 08-30-2024 +Release Date: 10-13-2024 -DotNext 5.13.0 -* Improved interoperability of `DotNext.Runtime.ValueReference` and `DotNext.Runtime.ReadOnlyValueReference` with .NEXT ecosystem -* Fixed [249](https://github.com/dotnet/dotNext/issues/249) -* Improved codegen quality for ad-hoc enumerator types +DotNext 5.14.0 +* Added helpers to `DelegateHelpers` class to convert delegates with synchronous signature to their asynchronous counterparts +* Added support of async enumerator to `SingletonList` +* Fixed exception propagation in `DynamicTaskAwaitable` +* Added support of [ConfigureAwaitOptions](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.configureawaitoptions) to `DynamicTaskAwaitable` -DotNext.Metaprogramming 5.13.0 +DotNext.Metaprogramming 5.14.0 * Updated dependencies -DotNext.Unsafe 5.13.0 +DotNext.Unsafe 5.14.0 * Updated dependencies -DotNext.Threading 5.13.0 -* Redesigned `AsyncEventHub` to improve overall performance and reduce memory allocation -* Improved codegen quality for ad-hoc enumerator types - -DotNext.IO 5.13.0 -* Improved codegen quality for ad-hoc enumerator types - -DotNext.Net.Cluster 5.13.0 +DotNext.Threading 5.14.0 * Updated dependencies -DotNext.AspNetCore.Cluster 5.13.0 +DotNext.IO 5.14.0 * Updated dependencies -DotNext.MaintenanceServices 0.4.0 -* Added [gc refresh-mem-limit](https://learn.microsoft.com/en-us/dotnet/api/system.gc.refreshmemorylimit) maintenance command -* Updated dependencies +DotNext.Net.Cluster 5.14.0 +* Fixed graceful shutdown of Raft TCP listener +* Updated vulnerable dependencies + +DotNext.AspNetCore.Cluster 5.14.0 +* Updated vulnerable dependencies Changelog for previous versions located [here](./CHANGELOG.md). diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index f0b9c57e2..33f98518b 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -14,20 +14,20 @@ - + - - - - - + + + + + - - - + + + diff --git a/src/DotNext.IO/DotNext.IO.csproj b/src/DotNext.IO/DotNext.IO.csproj index 97d13b014..9a1bb78e5 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.13.0 + 5.14.0 DotNext.IO MIT diff --git a/src/DotNext.IO/IO/FileWriter.Binary.cs b/src/DotNext.IO/IO/FileWriter.Binary.cs index 69ed18d58..9d9cd599d 100644 --- a/src/DotNext.IO/IO/FileWriter.Binary.cs +++ b/src/DotNext.IO/IO/FileWriter.Binary.cs @@ -47,7 +47,7 @@ private ValueTask WriteAsync(T arg, SpanAction writer, int length, C [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))] private async ValueTask WriteBufferedAsync(T arg, SpanAction writer, int length, CancellationToken token) { - await FlushCoreAsync(token).ConfigureAwait(false); + await FlushAsync(token).ConfigureAwait(false); writer(BufferSpan, arg); Debug.Assert(bufferOffset is 0); @@ -143,7 +143,7 @@ private int WriteLength(int length, LengthFormat lengthFormat) public async ValueTask WriteAsync(ReadOnlyMemory input, LengthFormat lengthFormat, CancellationToken token = default) { if (FreeCapacity < SevenBitEncodedInt.MaxSize) - await FlushCoreAsync(token).ConfigureAwait(false); + await FlushAsync(token).ConfigureAwait(false); WriteLength(input.Length, lengthFormat); await WriteAsync(input, token).ConfigureAwait(false); @@ -165,7 +165,7 @@ public async ValueTask EncodeAsync(ReadOnlyMemory chars, EncodingCon if (lengthFormat.HasValue) { if (FreeCapacity < SevenBitEncodedInt.MaxSize) - await FlushCoreAsync(token).ConfigureAwait(false); + await FlushAsync(token).ConfigureAwait(false); result = WriteLength(context.Encoding.GetByteCount(chars.Span), lengthFormat.GetValueOrDefault()); } @@ -182,7 +182,7 @@ public async ValueTask EncodeAsync(ReadOnlyMemory chars, EncodingCon for (int charsUsed, bytesUsed; !chars.IsEmpty; chars = chars.Slice(charsUsed), result += bytesUsed) { if (FreeCapacity < maxByteCount) - await FlushCoreAsync(token).ConfigureAwait(false); + await FlushAsync(token).ConfigureAwait(false); Convert(encoder, chars.Span, BufferSpan, maxByteCount, chars.Length, out charsUsed, out bytesUsed); Produce(bytesUsed); @@ -285,7 +285,7 @@ private bool TryFormat(T value, LengthFormat? lengthFormat, ReadOnlySpan FormatSlowAsync(T value, LengthFormat? lengthFormat, string? format, IFormatProvider? provider, CancellationToken token) where T : notnull, IUtf8SpanFormattable { - await FlushCoreAsync(token).ConfigureAwait(false); + await FlushAsync(token).ConfigureAwait(false); if (!TryFormat(value, lengthFormat, format, provider, out var bytesWritten)) { const int maxBufferSize = int.MaxValue / 2; diff --git a/src/DotNext.IO/IO/FileWriter.cs b/src/DotNext.IO/IO/FileWriter.cs index 4de20e4e4..ef1ee3c54 100644 --- a/src/DotNext.IO/IO/FileWriter.cs +++ b/src/DotNext.IO/IO/FileWriter.cs @@ -91,9 +91,9 @@ public FileWriter(FileStream destination, int bufferSize = 4096, MemoryAllocator public int MaxBufferSize => buffer.Length; /// - /// Marks the specified number of bytes in the buffer as consumed. + /// Marks the specified number of bytes in the buffer as produced. /// - /// The number of consumed bytes. + /// The number of produced bytes. /// is larger than the length of . public void Produce(int bytes) { @@ -140,10 +140,10 @@ public long FilePosition /// public long WritePosition => fileOffset + bufferOffset; - private ValueTask FlushCoreAsync(CancellationToken token) + private ValueTask FlushAsync(CancellationToken token) => Submit(RandomAccess.WriteAsync(handle, WrittenBuffer, fileOffset, token), writeCallback); - private void FlushCore() + private void Flush() { RandomAccess.Write(handle, WrittenBuffer.Span, fileOffset); fileOffset += bufferOffset; @@ -165,7 +165,7 @@ public ValueTask WriteAsync(CancellationToken token = default) if (token.IsCancellationRequested) return ValueTask.FromCanceled(token); - return HasBufferedData ? FlushCoreAsync(token) : ValueTask.CompletedTask; + return HasBufferedData ? FlushAsync(token) : ValueTask.CompletedTask; } /// @@ -191,7 +191,7 @@ public void Write() ObjectDisposedException.ThrowIf(IsDisposed, this); if (HasBufferedData) - FlushCore(); + Flush(); } /// diff --git a/src/DotNext.Metaprogramming/DotNext.Metaprogramming.csproj b/src/DotNext.Metaprogramming/DotNext.Metaprogramming.csproj index 52be6e9bc..c295a09d7 100644 --- a/src/DotNext.Metaprogramming/DotNext.Metaprogramming.csproj +++ b/src/DotNext.Metaprogramming/DotNext.Metaprogramming.csproj @@ -8,7 +8,7 @@ true false nullablePublicOnly - 5.13.0 + 5.14.0 .NET Foundation .NEXT Family of Libraries diff --git a/src/DotNext.Tests/Buffers/BufferWriterSlimTests.cs b/src/DotNext.Tests/Buffers/BufferWriterSlimTests.cs index add7d2e59..8dac05303 100644 --- a/src/DotNext.Tests/Buffers/BufferWriterSlimTests.cs +++ b/src/DotNext.Tests/Buffers/BufferWriterSlimTests.cs @@ -1,4 +1,6 @@ +using System.Numerics; using System.Text; +using DotNext.Buffers.Binary; using static System.Globalization.CultureInfo; namespace DotNext.Buffers; @@ -313,4 +315,24 @@ public static void Rendering() writer.Format(CompositeFormat.Parse("{0}, {1}!"), ["Hello", "world"]); Equal("Hello, world!", writer.ToString()); } + + [Fact] + public static void WriteBlittable() + { + var writer = new BufferWriterSlim(stackalloc byte[16]); + writer.Write>(new() { Value = 42 }); + + var reader = new SpanReader(writer.WrittenSpan); + Equal(42, reader.Read>().Value); + } + + [Fact] + public static void ReadWriteBigInteger() + { + var expected = (BigInteger)100500; + var writer = new BufferWriterSlim(stackalloc byte[16]); + Equal(3, writer.Write(expected)); + + Equal(expected, new BigInteger(writer.WrittenSpan)); + } } \ No newline at end of file diff --git a/src/DotNext.Tests/Buffers/BufferWriterTests.cs b/src/DotNext.Tests/Buffers/BufferWriterTests.cs index 4f4e3d592..83163677e 100644 --- a/src/DotNext.Tests/Buffers/BufferWriterTests.cs +++ b/src/DotNext.Tests/Buffers/BufferWriterTests.cs @@ -226,11 +226,12 @@ public static void WriteInterpolatedStringToBufferWriter(int x, int y) [InlineData(int.MaxValue, int.MinValue)] public static async Task WriteInterpolatedStringToBufferWriterAsync(int x, int y) { - var xt = Task.FromResult(x); - var yt = Task.FromResult(y); + var xt = Task.FromResult(x); + var yt = Task.FromResult(y); using var buffer = new PoolingArrayBufferWriter(); - buffer.Interpolate($"{await xt,4:X} = {await yt,-3:X}"); + var actualCount = buffer.Interpolate($"{await xt,4:X} = {await yt,-3:X}"); + Equal(buffer.WrittenCount, actualCount); Equal($"{x,4:X} = {y,-3:X}", buffer.ToString()); } @@ -334,4 +335,46 @@ public static void Rendering() writer.Format(CompositeFormat.Parse("{0}, {1}!"), ["Hello", "world"]); Equal("Hello, world!", writer.WrittenSpan.ToString()); } + + [Theory] + [InlineData(0)] + [InlineData(16)] + [InlineData(128)] + [InlineData(124)] + public static void WriteStringBuilder(int stringLength) + { + var str = Random.Shared.NextString("abcdefghijklmnopqrstuvwxyz", stringLength); + + var builder = new StringBuilder(); + for (var i = 0; i < 3; i++) + { + builder.Append(str); + } + + var writer = new BufferWriterSlim(); + + writer.Write(builder); + Equal(builder.ToString(), writer.WrittenSpan); + } + + [Theory] + [InlineData(0)] + [InlineData(16)] + [InlineData(128)] + [InlineData(124)] + public static void WriteStringBuilder2(int stringLength) + { + var str = Random.Shared.NextString("abcdefghijklmnopqrstuvwxyz", stringLength); + + var builder = new StringBuilder(); + for (var i = 0; i < 3; i++) + { + builder.Append(str); + } + + var writer = new ArrayBufferWriter(); + + writer.Write(builder); + Equal(builder.ToString(), writer.WrittenSpan); + } } \ No newline at end of file diff --git a/src/DotNext.Tests/Buffers/ChunkSequenceTests.cs b/src/DotNext.Tests/Buffers/ChunkSequenceTests.cs index 630736785..f3818d040 100644 --- a/src/DotNext.Tests/Buffers/ChunkSequenceTests.cs +++ b/src/DotNext.Tests/Buffers/ChunkSequenceTests.cs @@ -1,4 +1,5 @@ using System.Buffers; +using System.Text; namespace DotNext.Buffers; @@ -93,4 +94,22 @@ public static void CopyFromSequence() Equal(10, writtenCount); Equal(sequence.Slice(0, 10).ToArray(), dest.ToArray()); } + + [Theory] + [InlineData(0)] + [InlineData(16)] + [InlineData(128)] + [InlineData(124)] + public static void StringBuilderToSequence(int stringLength) + { + var str = Random.Shared.NextString("abcdefghijklmnopqrstuvwxyz", stringLength); + + var builder = new StringBuilder(); + for (var i = 0; i < 3; i++) + { + builder.Append(str); + } + + Equal(builder.ToString(), builder.ToReadOnlySequence().ToString()); + } } \ No newline at end of file diff --git a/src/DotNext.Tests/Buffers/SpanReaderWriterTests.cs b/src/DotNext.Tests/Buffers/SpanReaderWriterTests.cs index c4ee61987..797104c59 100644 --- a/src/DotNext.Tests/Buffers/SpanReaderWriterTests.cs +++ b/src/DotNext.Tests/Buffers/SpanReaderWriterTests.cs @@ -378,4 +378,26 @@ public static void Rendering() True(writer.TryFormat(CompositeFormat.Parse("{0}, {1}!"), ["Hello", "world"])); Equal("Hello, world!", writer.WrittenSpan.ToString()); } + + [Theory] + [InlineData(0)] + [InlineData(16)] + [InlineData(128)] + [InlineData(124)] + public static void WriteStringBuilder(int stringLength) + { + var str = Random.Shared.NextString("abcdefghijklmnopqrstuvwxyz", stringLength); + + var builder = new StringBuilder(); + for (var i = 0; i < 3; i++) + { + builder.Append(str); + } + + var chars = new char[builder.Length]; + var writer = new SpanWriter(chars); + + writer.Write(builder); + Equal(builder.ToString(), writer.WrittenSpan); + } } \ No newline at end of file diff --git a/src/DotNext.Tests/Collections/Generic/AsyncEnumerableTests.cs b/src/DotNext.Tests/Collections/Generic/AsyncEnumerableTests.cs index 2ebb718aa..6ed4ad29f 100644 --- a/src/DotNext.Tests/Collections/Generic/AsyncEnumerableTests.cs +++ b/src/DotNext.Tests/Collections/Generic/AsyncEnumerableTests.cs @@ -97,4 +97,11 @@ public static async Task SkipNullsTestAsync() True(Array.Exists(array, "a".Equals)); True(Array.Exists(array, "b".Equals)); } + + [Fact] + public static void Singleton() + { + var enumerable = AsyncEnumerable.Singleton(42); + Equal(42, Single(enumerable)); + } } \ No newline at end of file diff --git a/src/DotNext.Tests/DelegateHelpersTests.cs b/src/DotNext.Tests/DelegateHelpersTests.cs index 8c6ba50df..14ced299d 100644 --- a/src/DotNext.Tests/DelegateHelpersTests.cs +++ b/src/DotNext.Tests/DelegateHelpersTests.cs @@ -521,6 +521,50 @@ public static void ToAsync2() func = new Action(static _ => throw new Exception()).ToAsync(); True(func.Invoke(42, new(canceled: false)).IsFaulted); } + + [Fact] + public static void ToAsync3() + { + var func = new Action(static (_, _) => { }).ToAsync(); + True(func.Invoke(42, 42, new(canceled: false)).IsCompletedSuccessfully); + True(func.Invoke(42, 42, new(canceled: true)).IsCanceled); + + func = new Action(static (_, _) => throw new Exception()).ToAsync(); + True(func.Invoke(42, 42, new(canceled: false)).IsFaulted); + } + + [Fact] + public static async Task ToAsync4() + { + var func = new Func(static (x, y) => x + y).ToAsync(); + Equal(84, await func.Invoke(42, 42, new(canceled: false))); + True(func.Invoke(42, 42, new(canceled: true)).IsCanceled); + + func = new Func(static (_, _) => throw new Exception()).ToAsync(); + await ThrowsAsync(func.Invoke(42, 42, new(canceled: false)).AsTask); + } + + [Fact] + public static async Task ToAsync5() + { + var func = Func.Identity().ToAsync(); + Equal(42, await func.Invoke(42, new(canceled: false))); + True(func.Invoke(42, new(canceled: true)).IsCanceled); + + func = new Func(static _ => throw new Exception()).ToAsync(); + await ThrowsAsync(func.Invoke(42, new(canceled: false)).AsTask); + } + + [Fact] + public static async Task ToAsync6() + { + var func = Func.Constant(42).ToAsync(); + Equal(42, await func.Invoke(new(canceled: false))); + True(func.Invoke(new(canceled: true)).IsCanceled); + + func = new Func(static () => throw new Exception()).ToAsync(); + await ThrowsAsync(func.Invoke(new(canceled: false)).AsTask); + } [Fact] public static void HideReturnValue1() diff --git a/src/DotNext.Tests/IO/FileReaderTests.cs b/src/DotNext.Tests/IO/FileReaderTests.cs index 6b294a433..582fa06d6 100644 --- a/src/DotNext.Tests/IO/FileReaderTests.cs +++ b/src/DotNext.Tests/IO/FileReaderTests.cs @@ -148,10 +148,12 @@ public static void ReadLargeData() public static async Task ReadSequentially() { var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - using var handle = File.OpenHandle(path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, FileOptions.Asynchronous); - using var reader = new FileReader(handle, bufferSize: 32); + await using var fs = new FileStream(path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.Asynchronous); + using var reader = new FileReader(fs, bufferSize: 32); var bytes = RandomBytes(1024); - await RandomAccess.WriteAsync(handle, bytes, 0L); + + await fs.WriteAsync(bytes); + await fs.FlushAsync(); using var ms = new MemoryStream(1024); await foreach (var chunk in reader) diff --git a/src/DotNext.Tests/IO/FileWriterTests.cs b/src/DotNext.Tests/IO/FileWriterTests.cs index bfd7a086e..3d00484e7 100644 --- a/src/DotNext.Tests/IO/FileWriterTests.cs +++ b/src/DotNext.Tests/IO/FileWriterTests.cs @@ -112,8 +112,8 @@ public static void WriteWithOverflow() public static void WriteUsingBufferWriter() { var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - using var handle = File.OpenHandle(path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, FileOptions.None); - using var writer = new FileWriter(handle, bufferSize: 64); + using var fs = new FileStream(path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.None); + using var writer = new FileWriter(fs, bufferSize: 64); False(writer.HasBufferedData); Equal(0L, writer.FilePosition); @@ -126,7 +126,7 @@ public static void WriteUsingBufferWriter() Equal(expected.Length, writer.FilePosition); var actual = new byte[expected.Length]; - RandomAccess.Read(handle, actual, 0L); + fs.ReadExactly(actual); Equal(expected, actual); } diff --git a/src/DotNext.Tests/IO/SequenceBinaryReaderTests.cs b/src/DotNext.Tests/IO/SequenceBinaryReaderTests.cs index 3b9df3de5..ddbdc5a9f 100644 --- a/src/DotNext.Tests/IO/SequenceBinaryReaderTests.cs +++ b/src/DotNext.Tests/IO/SequenceBinaryReaderTests.cs @@ -1,5 +1,6 @@ using System.Buffers; using System.IO.Pipelines; +using System.Numerics; using System.Text; namespace DotNext.IO; @@ -174,4 +175,16 @@ public static void TryRead2() True(reader.IsEmpty); False(reader.TryRead(4, out actual)); } + + [Fact] + public static void ReadWriteBigInteger() + { + var expected = (BigInteger)100500; + var writer = new ArrayBufferWriter(); + + Equal(3, writer.Write(expected)); + + var reader = IAsyncBinaryReader.Create(writer.WrittenMemory); + Equal(expected, new BigInteger(reader.ReadToEnd().FirstSpan)); + } } \ No newline at end of file diff --git a/src/DotNext.Tests/Threading/Tasks/ConversionTests.cs b/src/DotNext.Tests/Threading/Tasks/ConversionTests.cs index 170ab9394..037c18904 100644 --- a/src/DotNext.Tests/Threading/Tasks/ConversionTests.cs +++ b/src/DotNext.Tests/Threading/Tasks/ConversionTests.cs @@ -27,7 +27,7 @@ public static async Task DynamicTask() Equal("Hello", result); //check for caching result = await Task.CompletedTask.AsDynamic(); - Equal(Missing.Value, result); + Same(Missing.Value, result); result = await Task.FromResult("Hello2").AsDynamic(); Equal("Hello2", result); await ThrowsAnyAsync(async () => await Task.FromCanceled(new CancellationToken(true)).AsDynamic()); @@ -47,4 +47,20 @@ public static async Task SuspendException() await Task.FromException(new Exception()).SuspendException(); await ValueTask.FromException(new Exception()).SuspendException(); } + + [Fact] + public static async Task SuspendException2() + { + var t = Task.FromException(new Exception()); + var result = await t.AsDynamic().ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing | ConfigureAwaitOptions.ContinueOnCapturedContext); + Same(result, Missing.Value); + } + + [Fact] + public static async Task SuspendExceptionParametrized() + { + await Task.FromException(new Exception()).SuspendException(42, (_, i) => i is 42); + await ValueTask.FromException(new Exception()).SuspendException(42, (_, i) => i is 42); + await ThrowsAsync(async () => await Task.FromException(new Exception()).SuspendException(43, (_, i) => i is 42)); + } } \ No newline at end of file diff --git a/src/DotNext.Threading/DotNext.Threading.csproj b/src/DotNext.Threading/DotNext.Threading.csproj index 43bb9173f..97e8fa52f 100644 --- a/src/DotNext.Threading/DotNext.Threading.csproj +++ b/src/DotNext.Threading/DotNext.Threading.csproj @@ -7,7 +7,7 @@ true true nullablePublicOnly - 5.13.0 + 5.14.0 .NET Foundation and Contributors .NEXT Family of Libraries diff --git a/src/DotNext.Threading/Threading/Tasks/TaskQueue.cs b/src/DotNext.Threading/Threading/Tasks/TaskQueue.cs index 18e92f1ff..3c1aa4f7e 100644 --- a/src/DotNext.Threading/Threading/Tasks/TaskQueue.cs +++ b/src/DotNext.Threading/Threading/Tasks/TaskQueue.cs @@ -178,7 +178,7 @@ public async ValueTask EnqueueAsync(T task, CancellationToken token = default) } } - private T? TryPeekOrDequeue(out int head, out Task enqueueTask, out bool completed) + private T? TryPeekOrDequeue(out int head, out Task enqueueTask) { T? result; lock (array) @@ -187,7 +187,7 @@ public async ValueTask EnqueueAsync(T task, CancellationToken token = default) { result = this[head = this.head]; enqueueTask = Task.CompletedTask; - if (completed = result is { IsCompleted: true }) + if (result is { IsCompleted: true }) { MoveNext(ref head); ChangeCount(increment: false); @@ -197,7 +197,6 @@ public async ValueTask EnqueueAsync(T task, CancellationToken token = default) { head = default; result = null; - completed = default; signal ??= new(); enqueueTask = signal.Task; } @@ -258,17 +257,17 @@ public bool TryDequeue([NotNullWhen(true)] out T? task) /// The operation has been canceled. public async ValueTask DequeueAsync(CancellationToken token = default) { - for (var filter = token.CanBeCanceled ? null : Predicate.Constant(true);;) + for (;;) { - if (TryPeekOrDequeue(out var expectedHead, out var enqueueTask, out var completed) is not { } task) + if (TryPeekOrDequeue(out var expectedHead, out var enqueueTask) is not { } task) { await enqueueTask.WaitAsync(token).ConfigureAwait(false); continue; } - if (!completed) + if (!task.IsCompleted) { - await task.WaitAsync(token).SuspendException(filter ??= token.SuspendAllExceptCancellation).ConfigureAwait(false); + await task.WaitAsync(token).SuspendException(token, SuspendAllExceptCancellation).ConfigureAwait(false); if (!TryDequeue(expectedHead, task)) continue; @@ -286,12 +285,12 @@ public async ValueTask DequeueAsync(CancellationToken token = default) /// The operation has been canceled. public async ValueTask TryDequeueAsync(CancellationToken token = default) { - for (var filter = token.CanBeCanceled ? null : Predicate.Constant(true);;) + for (;;) { T? task; - if ((task = TryPeekOrDequeue(out var expectedHead, out _, out var completed)) is not null && !completed) + if ((task = TryPeekOrDequeue(out var expectedHead, out _)) is not null && !task.IsCompleted) { - await task.WaitAsync(token).SuspendException(filter ??= token.SuspendAllExceptCancellation).ConfigureAwait(false); + await task.WaitAsync(token).SuspendException(token, SuspendAllExceptCancellation).ConfigureAwait(false); if (!TryDequeue(expectedHead, task)) continue; @@ -311,12 +310,11 @@ public async ValueTask DequeueAsync(CancellationToken token = default) /// The enumerator over completed tasks. public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken token) { - for (var filter = token.CanBeCanceled ? null : Predicate.Constant(true); - TryPeekOrDequeue(out var expectedHead, out _, out var completed) is { } task;) + while (TryPeekOrDequeue(out var expectedHead, out _) is { } task) { - if (!completed) + if (!task.IsCompleted) { - await task.WaitAsync(token).SuspendException(filter ??= token.SuspendAllExceptCancellation).ConfigureAwait(false); + await task.WaitAsync(token).SuspendException(token, SuspendAllExceptCancellation).ConfigureAwait(false); if (!TryDequeue(expectedHead, task)) continue; } @@ -325,6 +323,9 @@ public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken token) } } + private static bool SuspendAllExceptCancellation(Exception e, CancellationToken token) + => e is not OperationCanceledException canceledEx || token != canceledEx.CancellationToken; + /// /// Clears the queue. /// @@ -343,10 +344,4 @@ public void Clear() void IResettable.Reset() => Clear(); private sealed class Signal() : TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); -} - -file static class CancellationTokenExtensions -{ - internal static bool SuspendAllExceptCancellation(this object token, Exception e) - => e is not OperationCanceledException canceledEx || !canceledEx.CancellationToken.Equals(token); } \ No newline at end of file diff --git a/src/DotNext.Unsafe/DotNext.Unsafe.csproj b/src/DotNext.Unsafe/DotNext.Unsafe.csproj index 1a7f9db28..2cbe4dd42 100644 --- a/src/DotNext.Unsafe/DotNext.Unsafe.csproj +++ b/src/DotNext.Unsafe/DotNext.Unsafe.csproj @@ -7,7 +7,7 @@ enable true true - 5.13.0 + 5.14.0 nullablePublicOnly .NET Foundation and Contributors diff --git a/src/DotNext/Buffers/ByteBuffer.cs b/src/DotNext/Buffers/ByteBuffer.cs index b0e8584b1..71abe2692 100644 --- a/src/DotNext/Buffers/ByteBuffer.cs +++ b/src/DotNext/Buffers/ByteBuffer.cs @@ -76,8 +76,8 @@ public static int WriteBigEndian(this IBufferWriter writer, T value) /// /// The buffer writer. /// The value to be written as a sequence of bytes. - /// to use unsigned encoding; otherwise, . - /// to write the bytes in a big-endian byte order; otherwise, . + /// to write the bytes in a big-endian byte order; otherwise, . + /// to use unsigned encoding; otherwise, . /// The number of bytes written. /// has not enough space to place . public static int Write(this IBufferWriter writer, in BigInteger value, bool isBigEndian = false, bool isUnsigned = false) diff --git a/src/DotNext/Buffers/Memory.ReadOnlySequence.cs b/src/DotNext/Buffers/Memory.ReadOnlySequence.cs index 79ff9e2b2..1d6ba732c 100644 --- a/src/DotNext/Buffers/Memory.ReadOnlySequence.cs +++ b/src/DotNext/Buffers/Memory.ReadOnlySequence.cs @@ -1,6 +1,7 @@ using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; namespace DotNext.Buffers; @@ -94,10 +95,9 @@ public static ReadOnlySequence ToReadOnlySequence(this IEnumerable.Empty; - if (ReferenceEquals(head, tail)) - return new(head.Memory); - - return Chunk.CreateSequence(head, tail); + return ReferenceEquals(head, tail) + ? new(head.Memory) + : Chunk.CreateSequence(head, tail); static void ToReadOnlySequence(ReadOnlySpan strings, ref Chunk? head, ref Chunk? tail) { @@ -118,6 +118,31 @@ static void ToReadOnlySequenceSlow(IEnumerable strings, ref Chunk } } + /// + /// Gets a sequence of characters written to the builder. + /// + /// A string builder. + /// A sequence of characters written to the builder. + public static ReadOnlySequence ToReadOnlySequence(this StringBuilder builder) + { + ArgumentNullException.ThrowIfNull(builder); + + Chunk? head = null, tail = null; + + foreach (var chunk in builder.GetChunks()) + { + if (chunk.IsEmpty is false) + Chunk.AddChunk(chunk, ref head, ref tail); + } + + if (head is null || tail is null) + return ReadOnlySequence.Empty; + + return ReferenceEquals(head, tail) + ? new(head.Memory) + : Chunk.CreateSequence(head, tail); + } + /// /// Converts two memory blocks to data type. /// diff --git a/src/DotNext/Buffers/MemoryOwner.cs b/src/DotNext/Buffers/MemoryOwner.cs index d45b32776..9601ed8de 100644 --- a/src/DotNext/Buffers/MemoryOwner.cs +++ b/src/DotNext/Buffers/MemoryOwner.cs @@ -1,5 +1,6 @@ using System.Buffers; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -38,7 +39,7 @@ internal MemoryOwner(ArrayPool? pool, T[] array, int length) this.length = length; } - internal MemoryOwner(ArrayPool pool, int length, bool exactSize) + internal MemoryOwner(ArrayPool pool, int length, [ConstantExpected] bool exactSize) { Debug.Assert(pool is not null); diff --git a/src/DotNext/Buffers/ReadOnlySequencePartitioner.cs b/src/DotNext/Buffers/ReadOnlySequencePartitioner.cs index 6b3f868c0..17e2b0501 100644 --- a/src/DotNext/Buffers/ReadOnlySequencePartitioner.cs +++ b/src/DotNext/Buffers/ReadOnlySequencePartitioner.cs @@ -10,15 +10,10 @@ namespace DotNext.Buffers; file sealed class ReadOnlySequencePartitioner : OrderablePartitioner { - private sealed class SegmentProvider : IEnumerable> + private sealed class SegmentProvider(in ReadOnlySequence sequence) : IEnumerable> { private long runningIndex; - private ReadOnlySequence.Enumerator enumerator; - - internal SegmentProvider(ReadOnlySequence sequence) - { - enumerator = sequence.GetEnumerator(); - } + private ReadOnlySequence.Enumerator enumerator = sequence.GetEnumerator(); [MethodImpl(MethodImplOptions.Synchronized)] private ReadOnlyMemory NextSegment(out long startIndex) diff --git a/src/DotNext/Buffers/Text/Base64Decoder.cs b/src/DotNext/Buffers/Text/Base64Decoder.cs index 87bab6508..efa9467b7 100644 --- a/src/DotNext/Buffers/Text/Base64Decoder.cs +++ b/src/DotNext/Buffers/Text/Base64Decoder.cs @@ -74,7 +74,7 @@ private readonly ReadOnlySpan BufferedBytes { Debug.Assert((uint)reservedBufferSize <= sizeof(ulong)); - return MemoryMarshal.CreateReadOnlySpan(in ChangeType(in reservedBuffer), reservedBufferSize); + return MemoryMarshal.CreateReadOnlySpan(in InToRef(in reservedBuffer), reservedBufferSize); } } } \ No newline at end of file diff --git a/src/DotNext/Collections/Generic/AsyncEnumerable.cs b/src/DotNext/Collections/Generic/AsyncEnumerable.cs index 5eb5bcdf2..50fead5c8 100644 --- a/src/DotNext/Collections/Generic/AsyncEnumerable.cs +++ b/src/DotNext/Collections/Generic/AsyncEnumerable.cs @@ -219,4 +219,13 @@ public static IAsyncEnumerable Throw(Exception e) return new ThrowingEnumerator(e); } + + /// + /// Constructs read-only sequence with a single item in it. + /// + /// An item to be placed into list. + /// Type of list items. + /// Read-only list containing single item. + public static IAsyncEnumerable Singleton(T item) + => new Specialized.SingletonList { Item = item }; } \ No newline at end of file diff --git a/src/DotNext/Collections/Specialized/SingletonList.cs b/src/DotNext/Collections/Specialized/SingletonList.cs index 53b9613fa..4e98514b6 100644 --- a/src/DotNext/Collections/Specialized/SingletonList.cs +++ b/src/DotNext/Collections/Specialized/SingletonList.cs @@ -12,7 +12,7 @@ namespace DotNext.Collections.Specialized; /// /// The type of the element in the list. [StructLayout(LayoutKind.Auto)] -public struct SingletonList : IReadOnlyList, IList, ITuple, IReadOnlySet +public struct SingletonList : IReadOnlyList, IList, ITuple, IReadOnlySet, IAsyncEnumerable { /// /// Represents an enumerator over the collection containing a single element. @@ -145,6 +145,10 @@ readonly IEnumerator IEnumerable.GetEnumerator() readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator().ToClassicEnumerator(); + /// + readonly IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken token) + => GetEnumerator().ToAsyncEnumerator(token); + /// /// Converts a value to the read-only list. /// diff --git a/src/DotNext/DelegateHelpers.cs b/src/DotNext/DelegateHelpers.cs index 657137d58..aa65a3b24 100644 --- a/src/DotNext/DelegateHelpers.cs +++ b/src/DotNext/DelegateHelpers.cs @@ -126,6 +126,43 @@ private static ValueTask Invoke(this Action action, T arg, CancellationTok return task; } + /// + /// Converts function to async delegate. + /// + /// Synchronous function. + /// The type of the argument to be passed to the action. + /// The type of the return value. + /// The asynchronous function that wraps . + /// is . + public static Func> ToAsync(this Func action) + { + ArgumentNullException.ThrowIfNull(action); + + return action.Invoke; + } + + private static ValueTask Invoke(this Func action, T arg, CancellationToken token) + { + ValueTask task; + if (token.IsCancellationRequested) + { + task = ValueTask.FromCanceled(token); + } + else + { + try + { + task = new(action.Invoke(arg)); + } + catch (Exception e) + { + task = ValueTask.FromException(e); + } + } + + return task; + } + /// /// Converts action to async delegate. /// @@ -161,6 +198,118 @@ private static ValueTask Invoke(this Action action, CancellationToken token) return task; } + + /// + /// Converts function to async delegate. + /// + /// The type of the return value. + /// Synchronous function. + /// The asynchronous function that wraps . + /// is . + public static Func> ToAsync(this Func func) + { + ArgumentNullException.ThrowIfNull(func); + + return func.Invoke; + } + + private static ValueTask Invoke(this Func func, CancellationToken token) + { + ValueTask task; + if (token.IsCancellationRequested) + { + task = ValueTask.FromCanceled(token); + } + else + { + try + { + task = new(func.Invoke()); + } + catch (Exception e) + { + task = ValueTask.FromException(e); + } + } + + return task; + } + + /// + /// Converts action to async delegate. + /// + /// The type of the first argument to be passed to the action. + /// The type of the second argument to be passed to the action. + /// Synchronous action. + /// The asynchronous function that wraps . + /// is . + public static Func ToAsync(this Action action) + { + ArgumentNullException.ThrowIfNull(action); + + return action.Invoke; + } + + private static ValueTask Invoke(this Action action, T1 arg1, T2 arg2, CancellationToken token) + { + ValueTask task; + if (token.IsCancellationRequested) + { + task = ValueTask.FromCanceled(token); + } + else + { + task = ValueTask.CompletedTask; + try + { + action.Invoke(arg1, arg2); + } + catch (Exception e) + { + task = ValueTask.FromException(e); + } + } + + return task; + } + + /// + /// Converts function to async delegate. + /// + /// The type of the first argument to be passed to the action. + /// The type of the second argument to be passed to the action. + /// The type of the return value. + /// Synchronous action. + /// The asynchronous function that wraps . + /// is . + public static Func> ToAsync(this Func func) + { + ArgumentNullException.ThrowIfNull(func); + + return func.Invoke; + } + + private static ValueTask Invoke(this Func func, T1 arg1, T2 arg2, CancellationToken token) + { + ValueTask task; + if (token.IsCancellationRequested) + { + task = ValueTask.FromCanceled(token); + } + else + { + try + { + task = new(func.Invoke(arg1, arg2)); + } + catch (Exception e) + { + task = ValueTask.FromException(e); + } + } + + return task; + } /// /// Creates a delegate that hides the return value. diff --git a/src/DotNext/DotNext.csproj b/src/DotNext/DotNext.csproj index 2e4e5b6de..e46855216 100644 --- a/src/DotNext/DotNext.csproj +++ b/src/DotNext/DotNext.csproj @@ -11,7 +11,7 @@ .NET Foundation and Contributors .NEXT Family of Libraries - 5.13.0 + 5.14.0 DotNext MIT diff --git a/src/DotNext/Dynamic/TaskResultBinder.cs b/src/DotNext/Dynamic/TaskResultBinder.cs index 10fa2fa41..23b08fc00 100644 --- a/src/DotNext/Dynamic/TaskResultBinder.cs +++ b/src/DotNext/Dynamic/TaskResultBinder.cs @@ -11,9 +11,6 @@ namespace DotNext.Dynamic; [RequiresDynamicCode("DLR is required to resolve underlying task type at runtime")] internal sealed class TaskResultBinder : CallSiteBinder { - private const string ResultPropertyName = nameof(Task.Result); - private const BindingFlags ResultPropertyFlags = BindingFlags.Public | BindingFlags.Instance; - private static Expression BindProperty(PropertyInfo resultProperty, Expression target, out Expression restrictions) { Debug.Assert(resultProperty.DeclaringType is not null); @@ -27,7 +24,10 @@ private static Expression BindProperty(PropertyInfo resultProperty, Expression t private static Expression Bind(object targetValue, Expression target, LabelTarget returnLabel) { - PropertyInfo? property = targetValue.GetType().GetProperty(ResultPropertyName, ResultPropertyFlags); + const string resultPropertyName = nameof(Task.Result); + const BindingFlags resultPropertyFlags = BindingFlags.Public | BindingFlags.Instance; + + var property = targetValue.GetType().GetProperty(resultPropertyName, resultPropertyFlags); Debug.Assert(property is not null); target = BindProperty(property, target, out var restrictions); diff --git a/src/DotNext/Func.cs b/src/DotNext/Func.cs index 7b8515e03..3dddebb5f 100644 --- a/src/DotNext/Func.cs +++ b/src/DotNext/Func.cs @@ -94,15 +94,15 @@ public static Func Constant(T obj) // slow path - allocates a new delegate return obj is null - ? Default! + ? Default! : typeof(T).IsValueType - ? new BoxedConstant() { Value = obj }.GetValue - : Unsafe.As(ref obj).UnboxRefType; - - static T? Default() => default; + ? new BoxedConstant(obj).GetValue + : Unsafe.As(ref obj).ReinterpretRefType; } + + internal static T? Default() => default; - private static T UnboxRefType(this object obj) + private static T ReinterpretRefType(this object obj) => Unsafe.As(ref obj); private static Func Constant(bool value) @@ -485,10 +485,10 @@ public static Result TryInvoke : StrongBox + private sealed class BoxedConstant(T value) { - internal T GetValue() => Value!; + internal T GetValue() => value; - public override string? ToString() => Value?.ToString(); + public override string? ToString() => value?.ToString(); } } \ No newline at end of file diff --git a/src/DotNext/ISupplier0.cs b/src/DotNext/ISupplier0.cs index 10e4f8772..b788dcfdc 100644 --- a/src/DotNext/ISupplier0.cs +++ b/src/DotNext/ISupplier0.cs @@ -198,4 +198,6 @@ public DelegatingSupplier(Func func) file sealed class DefaultSupplier : ISupplier { T? ISupplier.Invoke() => default; + + Func IFunctional>.ToDelegate() => Func.Default; } \ No newline at end of file diff --git a/src/DotNext/Reflection/TaskType.cs b/src/DotNext/Reflection/TaskType.cs index 9b0810dd5..62adba158 100644 --- a/src/DotNext/Reflection/TaskType.cs +++ b/src/DotNext/Reflection/TaskType.cs @@ -41,17 +41,6 @@ static TaskType() IsCompletedSuccessfullyGetter = propertyGetter; } - /// - /// Returns task type for the specified result type. - /// - /// Task result type. - /// Returns if is ; or with actual generic argument equals to . - /// - /// - [RequiresUnreferencedCode("Runtime binding may be incompatible with IL trimming")] - public static Type MakeTaskType(this Type taskResult) - => MakeTaskType(taskResult, valueTask: false); - /// /// Returns task type for the specified result type. /// @@ -63,7 +52,7 @@ public static Type MakeTaskType(this Type taskResult) /// /// [RequiresUnreferencedCode("Runtime generic instantiation may be incompatible with IL trimming")] - public static Type MakeTaskType(this Type taskResult, bool valueTask) + public static Type MakeTaskType(this Type taskResult, bool valueTask = false) { if (taskResult == typeof(void)) return valueTask ? typeof(ValueTask) : typeof(Task); diff --git a/src/DotNext/Runtime/CompilerServices/SuspendedExceptionTaskAwaitable.cs b/src/DotNext/Runtime/CompilerServices/SuspendedExceptionTaskAwaitable.cs index 6ec23a437..de636f575 100644 --- a/src/DotNext/Runtime/CompilerServices/SuspendedExceptionTaskAwaitable.cs +++ b/src/DotNext/Runtime/CompilerServices/SuspendedExceptionTaskAwaitable.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -15,7 +16,9 @@ internal SuspendedExceptionTaskAwaitable(ValueTask task) => this.task = task; internal SuspendedExceptionTaskAwaitable(Task task) - => this.task = new(task); + : this(new ValueTask(task)) + { + } internal bool ContinueOnCapturedContext { @@ -91,4 +94,97 @@ public void GetResult() } } } +} + +/// +/// Represents awaitable object that can suspend exception raised by the underlying task. +/// +/// The type of the argument to be passed to the exception filter. +[StructLayout(LayoutKind.Auto)] +public readonly struct SuspendedExceptionTaskAwaitable +{ + private readonly TArg arg; + private readonly ValueTask task; + private readonly Func filter; + + internal SuspendedExceptionTaskAwaitable(ValueTask task, TArg arg, Func filter) + { + Debug.Assert(filter is not null); + + this.task = task; + this.arg = arg; + this.filter = filter; + } + + internal SuspendedExceptionTaskAwaitable(Task task, TArg arg, Func filter) + : this(new ValueTask(task), arg, filter) + { + } + + internal bool ContinueOnCapturedContext + { + get; + init; + } + + /// + /// Configures an awaiter for this value. + /// + /// + /// to attempt to marshal the continuation back to the captured context; + /// otherwise, . + /// + /// The configured object. + public SuspendedExceptionTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) + => this with { ContinueOnCapturedContext = continueOnCapturedContext }; + + /// + /// Gets the awaiter for this object. + /// + /// The awaiter for this object. + public Awaiter GetAwaiter() => new(task, arg, filter, ContinueOnCapturedContext); + + /// + /// Represents the awaiter that suspends exception. + /// + [StructLayout(LayoutKind.Auto)] + public readonly struct Awaiter : ICriticalNotifyCompletion + { + private readonly ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter awaiter; + private readonly TArg arg; + private readonly Func filter; + + internal Awaiter(in ValueTask task, TArg arg, Func filter, bool continueOnCapturedContext) + { + awaiter = task.ConfigureAwait(continueOnCapturedContext).GetAwaiter(); + this.arg = arg; + this.filter = filter; + } + + /// + /// Gets a value indicating that has completed. + /// + public bool IsCompleted => awaiter.IsCompleted; + + /// + public void OnCompleted(Action action) => awaiter.OnCompleted(action); + + /// + public void UnsafeOnCompleted(Action action) => awaiter.UnsafeOnCompleted(action); + + /// + /// Obtains a result of asynchronous operation, and suspends exception if needed. + /// + public void GetResult() + { + try + { + awaiter.GetResult(); + } + catch (Exception e) when (filter.Invoke(e, arg)) + { + // suspend exception + } + } + } } \ No newline at end of file diff --git a/src/DotNext/Runtime/Intrinsics.cs b/src/DotNext/Runtime/Intrinsics.cs index 0ffeded59..ef33cec8c 100644 --- a/src/DotNext/Runtime/Intrinsics.cs +++ b/src/DotNext/Runtime/Intrinsics.cs @@ -36,13 +36,6 @@ public static bool IsNullable() [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ref TTo InToRef(scoped ref readonly TFrom source) => ref Unsafe.As(ref Unsafe.AsRef(in source)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static ref readonly TTo ChangeType(ref readonly TFrom source) - { - PushInRef(in source); - return ref ReturnRef(); - } /// /// Indicates that specified value type is the default value. diff --git a/src/DotNext/Runtime/ValueReference.cs b/src/DotNext/Runtime/ValueReference.cs index 834ecd888..ce111542c 100644 --- a/src/DotNext/Runtime/ValueReference.cs +++ b/src/DotNext/Runtime/ValueReference.cs @@ -7,7 +7,7 @@ namespace DotNext.Runtime; -using Runtime.CompilerServices; +using CompilerServices; /// /// Represents a mutable reference to the field. @@ -95,7 +95,7 @@ public ValueReference(ref T staticFieldRef) private bool SameObject(object? other) => ReferenceEquals(owner, other); private Func ToFunc() - => Intrinsics.ChangeType, ReadOnlyValueReference>(in this).ToFunc(); + => Intrinsics.InToRef, ReadOnlyValueReference>(in this).ToFunc(); private Action ToAction() { @@ -343,7 +343,7 @@ static RawData() internal static nint GetOffset(object owner, ref readonly T field, [CallerArgumentExpression(nameof(field))] string? paramName = null) { ref var rawData = ref Unsafe.As(owner).data; - var offset = Unsafe.ByteOffset(in rawData, in Intrinsics.ChangeType(in field)); + var offset = Unsafe.ByteOffset(in rawData, in Intrinsics.InToRef(in field)); // Ensure that the reference is an interior pointer to the field inside the object if (GetRawObjectDataSize is not null && owner != Sentinel.Instance && (nuint)(offset + Unsafe.SizeOf()) > GetRawObjectDataSize(owner)) diff --git a/src/DotNext/Threading/Tasks/Conversion.cs b/src/DotNext/Threading/Tasks/Conversion.cs index 0283f4fcd..daa2c5498 100644 --- a/src/DotNext/Threading/Tasks/Conversion.cs +++ b/src/DotNext/Threading/Tasks/Conversion.cs @@ -3,7 +3,7 @@ namespace DotNext.Threading.Tasks; -using SuspendedExceptionTaskAwaitable = Runtime.CompilerServices.SuspendedExceptionTaskAwaitable; +using Runtime.CompilerServices; /// /// Provides task result conversion methods. @@ -84,4 +84,32 @@ public static SuspendedExceptionTaskAwaitable SuspendException(this Task task, P /// The awaitable object that suspends exceptions according to the filter. public static SuspendedExceptionTaskAwaitable SuspendException(this ValueTask task, Predicate? filter = null) => new(task) { Filter = filter }; + + /// + /// Suspends the exception that can be raised by the task. + /// + /// The task. + /// The argument to be passed to the filter. + /// The filter of the exception to be suspended. + /// The awaitable object that suspends exceptions according to the filter. + public static SuspendedExceptionTaskAwaitable SuspendException(this Task task, TArg arg, Func filter) + { + ArgumentNullException.ThrowIfNull(filter); + + return new(task, arg, filter); + } + + /// + /// Suspends the exception that can be raised by the task. + /// + /// The task. + /// The argument to be passed to the filter. + /// The filter of the exception to be suspended. + /// The awaitable object that suspends exceptions according to the filter. + public static SuspendedExceptionTaskAwaitable SuspendException(this ValueTask task, TArg arg, Func filter) + { + ArgumentNullException.ThrowIfNull(filter); + + return new(task, arg, filter); + } } \ No newline at end of file diff --git a/src/DotNext/Threading/Tasks/DynamicTaskAwaitable.cs b/src/DotNext/Threading/Tasks/DynamicTaskAwaitable.cs index 9d3e2cbbe..9315f6e38 100644 --- a/src/DotNext/Threading/Tasks/DynamicTaskAwaitable.cs +++ b/src/DotNext/Threading/Tasks/DynamicTaskAwaitable.cs @@ -27,45 +27,43 @@ public readonly struct DynamicTaskAwaitable { private static CallSite>? getResultCallSite; - private readonly Task task; private readonly ConfiguredTaskAwaitable.ConfiguredTaskAwaiter awaiter; - internal Awaiter(Task task, bool continueOnCaptureContext) - { - this.task = task; - awaiter = task.ConfigureAwait(continueOnCaptureContext).GetAwaiter(); - } + internal Awaiter(Task task, ConfigureAwaitOptions options) + => awaiter = task.ConfigureAwait(options).GetAwaiter(); + + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "m_task")] + private static extern ref readonly Task GetTask(ref readonly ConfiguredTaskAwaitable.ConfiguredTaskAwaiter awaiter); /// /// Gets a value that indicates whether the asynchronous task has completed. /// public bool IsCompleted => awaiter.IsCompleted; - /// - /// Sets the action to perform when this object stops waiting for the asynchronous task to complete. - /// - /// The action to perform when the wait operation completes. + /// public void OnCompleted(Action continuation) => awaiter.OnCompleted(continuation); - /// - void ICriticalNotifyCompletion.UnsafeOnCompleted(Action continuation) + /// + public void UnsafeOnCompleted(Action continuation) => awaiter.UnsafeOnCompleted(continuation); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsTaskWithResult(Type type) - => type != CompletedTaskType && type.IsConstructedGenericType; - [RequiresUnreferencedCode("Runtime binding may be incompatible with IL trimming")] internal object? GetRawResult() - => IsTaskWithResult(task.GetType()) ? - GetRawResult(task) : - Missing.Value; - - [RequiresUnreferencedCode("Runtime binding may be incompatible with IL trimming")] - private static object? GetRawResult(Task task) { - var callSite = getResultCallSite ??= CallSite>.Create(new TaskResultBinder()); - return callSite.Target(callSite, task); + awaiter.GetResult(); + var task = GetTask(in awaiter); + + return IsTaskWithResult(task.GetType()) ? GetDynamicResult(task) : Missing.Value; + + [RequiresUnreferencedCode("Runtime binding may be incompatible with IL trimming")] + static object? GetDynamicResult(Task task) + { + var callSite = getResultCallSite ??= CallSite>.Create(new TaskResultBinder()); + return callSite.Target(callSite, task); + } + + static bool IsTaskWithResult(Type type) + => type != CompletedTaskType && type.IsConstructedGenericType; } /// @@ -73,23 +71,16 @@ private static bool IsTaskWithResult(Type type) /// /// The result of the completed task; or if underlying task is not of type . [RequiresUnreferencedCode("Runtime binding may be incompatible with IL trimming")] - public dynamic? GetResult() - { - if (IsTaskWithResult(task.GetType())) - return GetRawResult(task); - - awaiter.GetResult(); - return Missing.Value; - } + public dynamic? GetResult() => GetRawResult(); } private readonly Task task; - private readonly bool continueOnCapturedContext; + private readonly ConfigureAwaitOptions options; - internal DynamicTaskAwaitable(Task task, bool continueOnCapturedContext = true) + internal DynamicTaskAwaitable(Task task, ConfigureAwaitOptions options = ConfigureAwaitOptions.ContinueOnCapturedContext) { this.task = task; - this.continueOnCapturedContext = continueOnCapturedContext; + this.options = options; } /// @@ -97,11 +88,21 @@ internal DynamicTaskAwaitable(Task task, bool continueOnCapturedContext = true) /// /// to attempt to marshal the continuation back to the original context captured; otherwise, . /// An object used to await this task. - public DynamicTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) => new(task, continueOnCapturedContext); + public DynamicTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) + => new(task, continueOnCapturedContext + ? options | ConfigureAwaitOptions.ContinueOnCapturedContext + : options & ~ConfigureAwaitOptions.ContinueOnCapturedContext); + + /// + /// Configures an awaiter used to await this task. + /// + /// Options used to configure how awaits on this task are performed. + /// Configured awaitable object. + public DynamicTaskAwaitable ConfigureAwait(ConfigureAwaitOptions options) => new(task, options); /// /// Gets an awaiter used to await this task. /// /// An awaiter instance. - public Awaiter GetAwaiter() => new(task, continueOnCapturedContext); + public Awaiter GetAwaiter() => new(task, options); } \ No newline at end of file diff --git a/src/DotNext/Threading/Tasks/Synchronization.cs b/src/DotNext/Threading/Tasks/Synchronization.cs index ce1e3e94b..be2e0cc2b 100644 --- a/src/DotNext/Threading/Tasks/Synchronization.cs +++ b/src/DotNext/Threading/Tasks/Synchronization.cs @@ -83,7 +83,7 @@ public static Result GetResult(this Task task, Cancel try { task.Wait(token); - var awaiter = new DynamicTaskAwaitable.Awaiter(task, false); + var awaiter = new DynamicTaskAwaitable.Awaiter(task, ConfigureAwaitOptions.None); result = new(awaiter.GetRawResult()); } catch (AggregateException e) when (e.InnerExceptions.Count is 1) @@ -114,7 +114,7 @@ public static Result GetResult(this Task task, Cancel if (!task.Wait(timeout)) throw new TimeoutException(); - var awaiter = new DynamicTaskAwaitable.Awaiter(task, false); + var awaiter = new DynamicTaskAwaitable.Awaiter(task, ConfigureAwaitOptions.None); result = new(awaiter.GetRawResult()); } catch (AggregateException e) when (e.InnerExceptions.Count is 1) diff --git a/src/cluster/DotNext.AspNetCore.Cluster/DotNext.AspNetCore.Cluster.csproj b/src/cluster/DotNext.AspNetCore.Cluster/DotNext.AspNetCore.Cluster.csproj index 88b3d71e0..4a3be274e 100644 --- a/src/cluster/DotNext.AspNetCore.Cluster/DotNext.AspNetCore.Cluster.csproj +++ b/src/cluster/DotNext.AspNetCore.Cluster/DotNext.AspNetCore.Cluster.csproj @@ -8,7 +8,7 @@ true true nullablePublicOnly - 5.13.0 + 5.14.0 .NET Foundation and Contributors .NEXT Family of Libraries diff --git a/src/cluster/DotNext.Net.Cluster/DotNext.Net.Cluster.csproj b/src/cluster/DotNext.Net.Cluster/DotNext.Net.Cluster.csproj index 1a546354e..79207c81e 100644 --- a/src/cluster/DotNext.Net.Cluster/DotNext.Net.Cluster.csproj +++ b/src/cluster/DotNext.Net.Cluster/DotNext.Net.Cluster.csproj @@ -8,7 +8,7 @@ enable true nullablePublicOnly - 5.13.0 + 5.14.0 .NET Foundation and Contributors .NEXT Family of Libraries diff --git a/src/cluster/DotNext.Net.Cluster/Net/Cluster/Consensus/Raft/IRaftCluster.cs b/src/cluster/DotNext.Net.Cluster/Net/Cluster/Consensus/Raft/IRaftCluster.cs index c2b881c29..6db87eb61 100644 --- a/src/cluster/DotNext.Net.Cluster/Net/Cluster/Consensus/Raft/IRaftCluster.cs +++ b/src/cluster/DotNext.Net.Cluster/Net/Cluster/Consensus/Raft/IRaftCluster.cs @@ -34,7 +34,7 @@ public interface IRaftCluster : IReplicationCluster, IPeerMesh - /// Tries to get the lease that can be used to perform the read with linerizability guarantees. + /// Tries to get the lease that can be used to perform the read with linearizability guarantees. /// /// The token representing lease. /// if the current node is leader; otherwise, . diff --git a/src/cluster/DotNext.Net.Cluster/Net/Cluster/Consensus/Raft/IRaftClusterMember.cs b/src/cluster/DotNext.Net.Cluster/Net/Cluster/Consensus/Raft/IRaftClusterMember.cs index d40e7b790..36b53e5f2 100644 --- a/src/cluster/DotNext.Net.Cluster/Net/Cluster/Consensus/Raft/IRaftClusterMember.cs +++ b/src/cluster/DotNext.Net.Cluster/Net/Cluster/Consensus/Raft/IRaftClusterMember.cs @@ -30,7 +30,7 @@ public interface IRaftClusterMember : IClusterMember Task> VoteAsync(long term, long lastLogIndex, long lastLogTerm, CancellationToken token); /// - /// Checks whether the transition to Candidate state makes sence. + /// Checks whether the transition to Candidate state makes sense. /// /// /// Called by a server before changing itself to Candidate status. diff --git a/src/cluster/DotNext.Net.Cluster/Net/Cluster/Consensus/Raft/Tcp/TcpServer.cs b/src/cluster/DotNext.Net.Cluster/Net/Cluster/Consensus/Raft/Tcp/TcpServer.cs index a909af331..4e9dcea15 100644 --- a/src/cluster/DotNext.Net.Cluster/Net/Cluster/Consensus/Raft/Tcp/TcpServer.cs +++ b/src/cluster/DotNext.Net.Cluster/Net/Cluster/Consensus/Raft/Tcp/TcpServer.cs @@ -174,7 +174,8 @@ private async void Listen() ITcpTransport.ConfigureSocket(remoteClient, linger, ttl); ThreadPool.UnsafeQueueUserWorkItem(HandleConnection, remoteClient, preferLocal: false); } - catch (Exception e) when (e is ObjectDisposedException || (e is OperationCanceledException canceledEx && canceledEx.CancellationToken == lifecycleToken)) + catch (Exception e) when (e is ObjectDisposedException || + (e is OperationCanceledException canceledEx && canceledEx.CancellationToken == lifecycleToken)) { break; } @@ -199,6 +200,9 @@ private async void Listen() break; } } + + if (connections is 0) + noPendingConnectionsEvent.TrySetResult(); } public override ValueTask StartAsync(CancellationToken token) diff --git a/src/cluster/DotNext.Net.Cluster/Net/IPeerMesh.cs b/src/cluster/DotNext.Net.Cluster/Net/IPeerMesh.cs index a8d31449e..8b3887fca 100644 --- a/src/cluster/DotNext.Net.Cluster/Net/IPeerMesh.cs +++ b/src/cluster/DotNext.Net.Cluster/Net/IPeerMesh.cs @@ -39,7 +39,7 @@ public interface IPeerMesh : IPeerMesh where TPeer : class, IPeer { /// - /// Gets a client used to communucate with a remote peer. + /// Gets a client used to communicate with a remote peer. /// /// The address of the peer. /// The peer client; or if the specified peer is not visible from the current peer. diff --git a/src/examples/RandomAccessCacheBenchmark/Program.cs b/src/examples/RandomAccessCacheBenchmark/Program.cs index 195939c70..a7081c28f 100644 --- a/src/examples/RandomAccessCacheBenchmark/Program.cs +++ b/src/examples/RandomAccessCacheBenchmark/Program.cs @@ -17,7 +17,7 @@ await RunBenchmark( int.Parse(parallelRequests)).ConfigureAwait(false); break; default: - Console.WriteLine("Usage: program "); + Console.WriteLine("Usage: program "); break; }