From 850348c87ee31d76b2cafe04918c0297e8bdbdd4 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Mon, 10 Jun 2024 20:32:05 +0100 Subject: [PATCH 01/47] Support for .NET Standard 2.0 and 2.1 --- .../Commands/CommandWriter.cs | 103 ++++++- .../Commands/NatsPooledBufferWriter.cs | 2 + .../Commands/ProtocolWriter.cs | 3 + src/NATS.Client.Core/INatsSerialize.cs | 15 +- .../Internal/ActivityEndingMsgReader.cs | 6 +- .../Internal/BufferExtensions.cs | 4 + .../Internal/CancellationTimer.cs | 124 -------- .../Internal/DebuggingExtensions.cs | 3 + .../Internal/FixedArrayBufferWriter.cs | 76 ----- src/NATS.Client.Core/Internal/HeaderWriter.cs | 3 + src/NATS.Client.Core/Internal/InboxSub.cs | 72 ++++- .../Internal/NatsReadProtocolProcessor.cs | 20 ++ src/NATS.Client.Core/Internal/NuidWriter.cs | 31 +- src/NATS.Client.Core/Internal/ObjectPool.cs | 12 +- src/NATS.Client.Core/Internal/SocketReader.cs | 7 + ...slClientAuthenticationOptionsExtensions.cs | 4 + .../Internal/SslStreamConnection.cs | 36 ++- .../Internal/SubscriptionManager.cs | 26 +- .../Internal/TcpConnection.cs | 29 +- src/NATS.Client.Core/Internal/Telemetry.cs | 4 + .../Internal/ThreadPoolWorkItem.cs | 65 ---- .../Internal/UserCredentials.cs | 8 + .../Internal/WebSocketConnection.cs | 14 + src/NATS.Client.Core/Internal/netstandard.cs | 278 ++++++++++++++++++ src/NATS.Client.Core/NATS.Client.Core.csproj | 29 +- src/NATS.Client.Core/NKeyPair.cs | 4 + src/NATS.Client.Core/NaCl/Sha512.cs | 9 + src/NATS.Client.Core/NatsBufferWriter.cs | 11 +- .../NatsConnection.LowLevelApi.cs | 4 + src/NATS.Client.Core/NatsConnection.Ping.cs | 8 +- .../NatsConnection.Publish.cs | 3 + .../NatsConnection.RequestReply.cs | 46 ++- .../NatsConnection.Subscribe.cs | 11 + src/NATS.Client.Core/NatsConnection.cs | 36 ++- src/NATS.Client.Core/NatsException.cs | 8 +- src/NATS.Client.Core/NatsHeaderParser.cs | 5 + src/NATS.Client.Core/NatsHeaders.cs | 14 + src/NATS.Client.Core/NatsMemoryOwner.cs | 10 + src/NATS.Client.Core/NatsMsg.cs | 14 + src/NATS.Client.Core/NatsPooledConnection.cs | 2 +- src/NATS.Client.Core/NatsSubBase.cs | 27 +- src/NATS.Client.Core/NatsTlsOpts.cs | 36 ++- .../CancellationTest.cs | 18 -- .../FixedArrayBufferWriterTest.cs | 66 ----- 44 files changed, 889 insertions(+), 417 deletions(-) delete mode 100644 src/NATS.Client.Core/Internal/CancellationTimer.cs delete mode 100644 src/NATS.Client.Core/Internal/FixedArrayBufferWriter.cs delete mode 100644 src/NATS.Client.Core/Internal/ThreadPoolWorkItem.cs create mode 100644 src/NATS.Client.Core/Internal/netstandard.cs delete mode 100644 tests/NATS.Client.Core.Tests/FixedArrayBufferWriterTest.cs diff --git a/src/NATS.Client.Core/Commands/CommandWriter.cs b/src/NATS.Client.Core/Commands/CommandWriter.cs index 0619953f9..2f280d7e5 100644 --- a/src/NATS.Client.Core/Commands/CommandWriter.cs +++ b/src/NATS.Client.Core/Commands/CommandWriter.cs @@ -4,6 +4,9 @@ using System.Threading.Channels; using Microsoft.Extensions.Logging; using NATS.Client.Core.Internal; +#if !NET6_0_OR_GREATER +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Core.Commands; @@ -113,10 +116,10 @@ public async Task CancelReaderLoopAsync() if (cts != null) { -#if NET6_0 - cts.Cancel(); -#else +#if NET8_0_OR_GREATER await cts.CancelAsync().ConfigureAwait(false); +#else + cts.Cancel(); #endif } @@ -133,10 +136,10 @@ public async ValueTask DisposeAsync() _disposed = true; -#if NET6_0 - _cts.Cancel(); -#else +#if NET8_0_OR_GREATER await _cts.CancelAsync().ConfigureAwait(false); +#else + _cts.Cancel(); #endif _channelLock.Writer.TryComplete(); @@ -164,7 +167,11 @@ public ValueTask ConnectAsync(ClientOpts connectOpts, CancellationToken cancella return ConnectStateMachineAsync(false, connectOpts, cancellationToken); } +#if NETSTANDARD2_0 + if (_flushTask.IsNotCompletedSuccessfully()) +#else if (_flushTask is { IsCompletedSuccessfully: false }) +#endif { return ConnectStateMachineAsync(true, connectOpts, cancellationToken); } @@ -184,7 +191,7 @@ public ValueTask ConnectAsync(ClientOpts connectOpts, CancellationToken cancella _semLock.Release(); } - return ValueTask.CompletedTask; + return default; } public ValueTask PingAsync(PingCommand pingCommand, CancellationToken cancellationToken) @@ -198,7 +205,11 @@ public ValueTask PingAsync(PingCommand pingCommand, CancellationToken cancellati return PingStateMachineAsync(false, pingCommand, cancellationToken); } +#if NETSTANDARD2_0 + if (_flushTask.IsNotCompletedSuccessfully()) +#else if (_flushTask is { IsCompletedSuccessfully: false }) +#endif { return PingStateMachineAsync(true, pingCommand, cancellationToken); } @@ -219,7 +230,7 @@ public ValueTask PingAsync(PingCommand pingCommand, CancellationToken cancellati _semLock.Release(); } - return ValueTask.CompletedTask; + return default; } public ValueTask PongAsync(CancellationToken cancellationToken = default) @@ -233,7 +244,11 @@ public ValueTask PongAsync(CancellationToken cancellationToken = default) return PongStateMachineAsync(false, cancellationToken); } +#if NETSTANDARD2_0 + if (_flushTask.IsNotCompletedSuccessfully()) +#else if (_flushTask is { IsCompletedSuccessfully: false }) +#endif { return PongStateMachineAsync(true, cancellationToken); } @@ -253,7 +268,7 @@ public ValueTask PongAsync(CancellationToken cancellationToken = default) _semLock.Release(); } - return ValueTask.CompletedTask; + return default; } public ValueTask PublishAsync(string subject, T? value, NatsHeaders? headers, string? replyTo, INatsSerialize serializer, CancellationToken cancellationToken) @@ -306,7 +321,11 @@ public ValueTask PublishAsync(string subject, T? value, NatsHeaders? headers, return PublishStateMachineAsync(false, subject, replyTo, headersBuffer, payloadBuffer, cancellationToken); } +#if NETSTANDARD2_0 + if (_flushTask.IsNotCompletedSuccessfully()) +#else if (_flushTask is { IsCompletedSuccessfully: false }) +#endif { return PublishStateMachineAsync(true, subject, replyTo, headersBuffer, payloadBuffer, cancellationToken); } @@ -335,7 +354,7 @@ public ValueTask PublishAsync(string subject, T? value, NatsHeaders? headers, } } - return ValueTask.CompletedTask; + return default; } public ValueTask SubscribeAsync(int sid, string subject, string? queueGroup, int? maxMsgs, CancellationToken cancellationToken) @@ -349,7 +368,11 @@ public ValueTask SubscribeAsync(int sid, string subject, string? queueGroup, int return SubscribeStateMachineAsync(false, sid, subject, queueGroup, maxMsgs, cancellationToken); } +#if NETSTANDARD2_0 + if (_flushTask.IsNotCompletedSuccessfully()) +#else if (_flushTask is { IsCompletedSuccessfully: false }) +#endif { return SubscribeStateMachineAsync(true, sid, subject, queueGroup, maxMsgs, cancellationToken); } @@ -369,7 +392,7 @@ public ValueTask SubscribeAsync(int sid, string subject, string? queueGroup, int _semLock.Release(); } - return ValueTask.CompletedTask; + return default; } public ValueTask UnsubscribeAsync(int sid, int? maxMsgs, CancellationToken cancellationToken) @@ -383,7 +406,11 @@ public ValueTask UnsubscribeAsync(int sid, int? maxMsgs, CancellationToken cance return UnsubscribeStateMachineAsync(false, sid, maxMsgs, cancellationToken); } +#if NETSTANDARD2_0 + if (_flushTask.IsNotCompletedSuccessfully()) +#else if (_flushTask is { IsCompletedSuccessfully: false }) +#endif { return UnsubscribeStateMachineAsync(true, sid, maxMsgs, cancellationToken); } @@ -403,7 +430,7 @@ public ValueTask UnsubscribeAsync(int sid, int? maxMsgs, CancellationToken cance _semLock.Release(); } - return ValueTask.CompletedTask; + return default; } // only used for internal testing @@ -413,10 +440,17 @@ internal async Task TestStallFlushAsync(TimeSpan timeSpan, CancellationToken can try { +#if NETSTANDARD2_0 + if (_flushTask.IsNotCompletedSuccessfully()) + { + await _flushTask!.ConfigureAwait(false); + } +#else if (_flushTask is { IsCompletedSuccessfully: false }) { await _flushTask.ConfigureAwait(false); } +#endif _flushTask = Task.Delay(timeSpan, cancellationToken); } @@ -630,10 +664,17 @@ private async ValueTask ConnectStateMachineAsync(bool lockHeld, ClientOpts conne throw new ObjectDisposedException(nameof(CommandWriter)); } +#if NETSTANDARD2_0 + if (_flushTask.IsNotCompletedSuccessfully()) + { + await _flushTask!.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); + } +#else if (_flushTask is { IsCompletedSuccessfully: false }) { await _flushTask.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); } +#endif _protocolWriter.WriteConnect(_pipeWriter, connectOpts); EnqueueCommand(); @@ -667,10 +708,17 @@ private async ValueTask PingStateMachineAsync(bool lockHeld, PingCommand pingCom throw new ObjectDisposedException(nameof(CommandWriter)); } +#if NETSTANDARD2_0 + if (_flushTask.IsNotCompletedSuccessfully()) + { + await _flushTask!.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); + } +#else if (_flushTask is { IsCompletedSuccessfully: false }) { await _flushTask.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); } +#endif _protocolWriter.WritePing(_pipeWriter); _enqueuePing(pingCommand); @@ -705,11 +753,17 @@ private async ValueTask PongStateMachineAsync(bool lockHeld, CancellationToken c throw new ObjectDisposedException(nameof(CommandWriter)); } +#if NETSTANDARD2_0 + if (_flushTask.IsNotCompletedSuccessfully()) + { + await _flushTask!.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); + } +#else if (_flushTask is { IsCompletedSuccessfully: false }) { await _flushTask.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); } - +#endif _protocolWriter.WritePong(_pipeWriter); EnqueueCommand(); } @@ -725,7 +779,9 @@ private async ValueTask PongStateMachineAsync(bool lockHeld, CancellationToken c } } +#if NET6_0_OR_GREATER [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))] +#endif private async ValueTask PublishStateMachineAsync(bool lockHeld, string subject, string? replyTo, NatsPooledBufferWriter? headersBuffer, NatsPooledBufferWriter payloadBuffer, CancellationToken cancellationToken) { try @@ -745,10 +801,17 @@ private async ValueTask PublishStateMachineAsync(bool lockHeld, string subject, throw new ObjectDisposedException(nameof(CommandWriter)); } +#if NETSTANDARD2_0 + if (_flushTask.IsNotCompletedSuccessfully()) + { + await _flushTask!.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); + } +#else if (_flushTask is { IsCompletedSuccessfully: false }) { await _flushTask.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); } +#endif _protocolWriter.WritePublish(_pipeWriter, subject, replyTo, headersBuffer?.WrittenMemory, payloadBuffer.WrittenMemory); EnqueueCommand(); @@ -794,10 +857,17 @@ private async ValueTask SubscribeStateMachineAsync(bool lockHeld, int sid, strin throw new ObjectDisposedException(nameof(CommandWriter)); } +#if NETSTANDARD2_0 + if (_flushTask.IsNotCompletedSuccessfully()) + { + await _flushTask!.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); + } +#else if (_flushTask is { IsCompletedSuccessfully: false }) { await _flushTask.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); } +#endif _protocolWriter.WriteSubscribe(_pipeWriter, sid, subject, queueGroup, maxMsgs); EnqueueCommand(); @@ -831,10 +901,17 @@ private async ValueTask UnsubscribeStateMachineAsync(bool lockHeld, int sid, int throw new ObjectDisposedException(nameof(CommandWriter)); } +#if NETSTANDARD2_0 + if (_flushTask.IsNotCompletedSuccessfully()) + { + await _flushTask!.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); + } +#else if (_flushTask is { IsCompletedSuccessfully: false }) { await _flushTask.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); } +#endif _protocolWriter.WriteUnsubscribe(_pipeWriter, sid, maxMsgs); EnqueueCommand(); diff --git a/src/NATS.Client.Core/Commands/NatsPooledBufferWriter.cs b/src/NATS.Client.Core/Commands/NatsPooledBufferWriter.cs index b62d1c617..235127ed4 100644 --- a/src/NATS.Client.Core/Commands/NatsPooledBufferWriter.cs +++ b/src/NATS.Client.Core/Commands/NatsPooledBufferWriter.cs @@ -193,6 +193,7 @@ private void ResizeBuffer(int sizeHint) { var minimumSize = (uint)_index + (uint)sizeHint; +#if NET6_0_OR_GREATER // The ArrayPool class has a maximum threshold of 1024 * 1024 for the maximum length of // pooled arrays, and once this is exceeded it will just allocate a new array every time // of exactly the requested size. In that case, we manually round up the requested size to @@ -202,6 +203,7 @@ private void ResizeBuffer(int sizeHint) { minimumSize = BitOperations.RoundUpToPowerOf2(minimumSize); } +#endif _pool.Resize(ref _array, (int)minimumSize); } diff --git a/src/NATS.Client.Core/Commands/ProtocolWriter.cs b/src/NATS.Client.Core/Commands/ProtocolWriter.cs index cf48fbbac..22fcd3213 100644 --- a/src/NATS.Client.Core/Commands/ProtocolWriter.cs +++ b/src/NATS.Client.Core/Commands/ProtocolWriter.cs @@ -5,6 +5,9 @@ using System.Text; using System.Text.Json; using NATS.Client.Core.Internal; +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Core.Commands; diff --git a/src/NATS.Client.Core/INatsSerialize.cs b/src/NATS.Client.Core/INatsSerialize.cs index c7b2e2c2c..0739c912d 100644 --- a/src/NATS.Client.Core/INatsSerialize.cs +++ b/src/NATS.Client.Core/INatsSerialize.cs @@ -4,6 +4,9 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; +#if !NET6_0_OR_GREATER +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Core; @@ -375,7 +378,13 @@ public void Serialize(IBufferWriter bufferWriter, T value) return (T)(object)Encoding.UTF8.GetString(buffer); } - var span = buffer.IsSingleSegment ? buffer.FirstSpan : buffer.ToArray(); + var span = buffer.IsSingleSegment +#if NETSTANDARD2_0 + ? buffer.First.Span +#else + ? buffer.FirstSpan +#endif + : buffer.ToArray(); if (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateTime?)) { @@ -621,7 +630,11 @@ public void Serialize(IBufferWriter bufferWriter, T value) { if (readOnlySequence.IsSingleSegment) { +#if NETSTANDARD2_0 + bufferWriter.Write(readOnlySequence.First.Span); +#else bufferWriter.Write(readOnlySequence.FirstSpan); +#endif } else { diff --git a/src/NATS.Client.Core/Internal/ActivityEndingMsgReader.cs b/src/NATS.Client.Core/Internal/ActivityEndingMsgReader.cs index 156917d2c..d475b37ba 100644 --- a/src/NATS.Client.Core/Internal/ActivityEndingMsgReader.cs +++ b/src/NATS.Client.Core/Internal/ActivityEndingMsgReader.cs @@ -109,7 +109,11 @@ public override bool TryPeek(out NatsMsg item) return _inner.TryPeek(out item); } - public override async IAsyncEnumerable> ReadAllAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) + public +#if NETSTANDARD2_1 || NET6_0_OR_GREATER + override +#endif + async IAsyncEnumerable> ReadAllAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) { var handle = GCHandle.Alloc(_sub); try diff --git a/src/NATS.Client.Core/Internal/BufferExtensions.cs b/src/NATS.Client.Core/Internal/BufferExtensions.cs index e94150dbd..8dd7295ca 100644 --- a/src/NATS.Client.Core/Internal/BufferExtensions.cs +++ b/src/NATS.Client.Core/Internal/BufferExtensions.cs @@ -29,7 +29,11 @@ public static ReadOnlySpan ToSpan(this ReadOnlySequence buffer) { if (buffer.IsSingleSegment) { +#if NETSTANDARD2_0 + return buffer.First.Span; +#else return buffer.FirstSpan; +#endif } return buffer.ToArray(); diff --git a/src/NATS.Client.Core/Internal/CancellationTimer.cs b/src/NATS.Client.Core/Internal/CancellationTimer.cs deleted file mode 100644 index 6fe7b280d..000000000 --- a/src/NATS.Client.Core/Internal/CancellationTimer.cs +++ /dev/null @@ -1,124 +0,0 @@ -namespace NATS.Client.Core.Internal; - -// Support efficiently cancellation support for connection-dispose/timeout/cancel-per-operation -internal sealed class CancellationTimerPool -{ - private readonly ObjectPool _pool; - private readonly CancellationToken _rootToken; - - public CancellationTimerPool(ObjectPool pool, CancellationToken rootToken) - { - _pool = pool; - _rootToken = rootToken; - } - - public CancellationTimer Start(TimeSpan timeout, CancellationToken externalCancellationToken) - { - return CancellationTimer.Start(_pool, _rootToken, timeout, externalCancellationToken); - } -} - -internal sealed class CancellationTimer : IObjectPoolNode -{ - // underyling source - private readonly CancellationTokenSource _cancellationTokenSource; - private readonly ObjectPool _pool; - private readonly CancellationToken _rootToken; - - // timer itself is ObjectPool Node - private CancellationTimer? _next; - private bool _calledExternalTokenCancel; - private TimeSpan _timeout; - private CancellationToken _externalCancellationToken; - private CancellationTokenRegistration _externalTokenRegistration; - - // this timer pool is tightly coupled with rootToken lifetime(e.g. connection lifetime). - private CancellationTimer(ObjectPool pool, CancellationToken rootToken) - { - _pool = pool; - _rootToken = rootToken; - _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(rootToken); - } - - public ref CancellationTimer? NextNode => ref _next; - - public CancellationToken Token => _cancellationTokenSource.Token; - - public static CancellationTimer Start(ObjectPool pool, CancellationToken rootToken, TimeSpan timeout, CancellationToken externalCancellationToken) - { - if (!pool.TryRent(out var self)) - { - self = new CancellationTimer(pool, rootToken); - } - - // Timeout with external cancellationToken - self._externalCancellationToken = externalCancellationToken; - if (externalCancellationToken.CanBeCanceled) - { - self._externalTokenRegistration = externalCancellationToken.UnsafeRegister( - static state => - { - var self = (CancellationTimer)state!; - self._calledExternalTokenCancel = true; - self._cancellationTokenSource.Cancel(); - }, - self); - } - - self._timeout = timeout; - if (timeout != Timeout.InfiniteTimeSpan) - { - self._cancellationTokenSource.CancelAfter(timeout); - } - - return self; - } - - public Exception GetExceptionWhenCanceled() - { - if (_rootToken.IsCancellationRequested) - { - return new NatsException("Operation is canceled because connection is disposed."); - } - - if (_externalCancellationToken.IsCancellationRequested) - { - return new OperationCanceledException(_externalCancellationToken); - } - - return new TimeoutException($"Nats operation is canceled due to the configured timeout of {_timeout.TotalSeconds} seconds elapsing."); - } - - // We can check cancel is called(calling) by return value - public bool TryReturn() - { - if (_externalTokenRegistration.Token.CanBeCanceled) - { - var notCancelRaised = _externalTokenRegistration.Unregister(); - if (!notCancelRaised) - { - // may invoking CancellationTokenSource.Cancel so don't call .Dispose. - return false; - } - } - - // if timer is not raised, successful reset so ok to return pool - if (_cancellationTokenSource.TryReset()) - { - _calledExternalTokenCancel = false; - _externalCancellationToken = default; - _externalTokenRegistration = default; - _timeout = TimeSpan.Zero; - - _pool.Return(this); - return true; - } - else - { - // otherwise, don't reuse. - _cancellationTokenSource.Cancel(); - _cancellationTokenSource.Dispose(); - return false; - } - } -} diff --git a/src/NATS.Client.Core/Internal/DebuggingExtensions.cs b/src/NATS.Client.Core/Internal/DebuggingExtensions.cs index efdcc987d..bc703006a 100644 --- a/src/NATS.Client.Core/Internal/DebuggingExtensions.cs +++ b/src/NATS.Client.Core/Internal/DebuggingExtensions.cs @@ -1,5 +1,8 @@ using System.Buffers; using System.Text; +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Core.Internal; diff --git a/src/NATS.Client.Core/Internal/FixedArrayBufferWriter.cs b/src/NATS.Client.Core/Internal/FixedArrayBufferWriter.cs deleted file mode 100644 index 4a868f5f9..000000000 --- a/src/NATS.Client.Core/Internal/FixedArrayBufferWriter.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Buffers; -using System.Runtime.CompilerServices; - -namespace NATS.Client.Core.Internal; - -// similar as ArrayBufferWriter but adds more functional for ProtocolWriter -internal sealed class FixedArrayBufferWriter : IBufferWriter -{ - private byte[] _buffer; - private int _written; - - public FixedArrayBufferWriter(int capacity = 65535) - { - _buffer = new byte[capacity]; - _written = 0; - } - - public ReadOnlyMemory WrittenMemory => _buffer.AsMemory(0, _written); - - public ReadOnlySpan WrittenSpan => _buffer.AsSpan(0, _written); - - public int WrittenCount => _written; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Range PreAllocate(int size) - { - var range = new Range(_written, _written + size); - Advance(size); - return range; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetSpanInPreAllocated(Range range) - { - return _buffer.AsSpan(range); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Reset() - { - _written = 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Advance(int count) - { - _written += count; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Memory GetMemory(int sizeHint = 0) - { - if (_buffer.Length - _written < sizeHint) - { - Resize(sizeHint + _written); - } - - return _buffer.AsMemory(_written); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetSpan(int sizeHint = 0) - { - if (_buffer.Length - _written < sizeHint) - { - Resize(sizeHint + _written); - } - - return _buffer.AsSpan(_written); - } - - private void Resize(int sizeHint) - { - Array.Resize(ref _buffer, Math.Max(sizeHint, _buffer.Length * 2)); - } -} diff --git a/src/NATS.Client.Core/Internal/HeaderWriter.cs b/src/NATS.Client.Core/Internal/HeaderWriter.cs index 8bb62694d..7e42bd267 100644 --- a/src/NATS.Client.Core/Internal/HeaderWriter.cs +++ b/src/NATS.Client.Core/Internal/HeaderWriter.cs @@ -2,6 +2,9 @@ using System.IO.Pipelines; using System.Text; using NATS.Client.Core.Commands; +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Core.Internal; diff --git a/src/NATS.Client.Core/Internal/InboxSub.cs b/src/NATS.Client.Core/Internal/InboxSub.cs index 25cb73efb..ef29ebfff 100644 --- a/src/NATS.Client.Core/Internal/InboxSub.cs +++ b/src/NATS.Client.Core/Internal/InboxSub.cs @@ -28,7 +28,7 @@ public override ValueTask ReceiveAsync(string subject, string? replyTo, ReadOnly // Not used. Dummy implementation to keep base happy. protected override ValueTask ReceiveInternalAsync(string subject, string? replyTo, ReadOnlySequence? headersBuffer, ReadOnlySequence payloadBuffer) - => ValueTask.CompletedTask; + => default; protected override void TryComplete() { @@ -38,7 +38,11 @@ protected override void TryComplete() internal class InboxSubBuilder : ISubscriptionManager { private readonly ILogger _logger; +#if NETSTANDARD2_0 + private readonly ConcurrentDictionary>> _bySubject = new(); +#else private readonly ConcurrentDictionary> _bySubject = new(); +#endif public InboxSubBuilder(ILogger logger) => _logger = logger; @@ -49,6 +53,46 @@ public InboxSub Build(string subject, NatsSubOpts? opts, NatsConnection connecti public ValueTask RegisterAsync(NatsSubBase sub) { +#if NETSTANDARD2_0 + _bySubject.AddOrUpdate( + sub.Subject, + _ => + { + return new List> { new WeakReference(sub) }; + }, + (_, subTable) => + { + lock (subTable) + { + if (subTable.Count == 0) + { + subTable.Add(new WeakReference(sub)); + return subTable; + } + + var wr = subTable.FirstOrDefault(w => + { + if (w.TryGetTarget(out var t)) + { + if (t == sub) + { + return true; + } + } + + return false; + }); + + if (wr != null) + { + subTable.Remove(wr); + } + + subTable.Add(new WeakReference(sub)); + return subTable; + } + }); +#else _bySubject.AddOrUpdate( sub.Subject, static (_, s) => new ConditionalWeakTable { { s, new object() } }, @@ -70,6 +114,7 @@ public ValueTask RegisterAsync(NatsSubBase sub) } }, sub); +#endif return sub.ReadyAsync(); } @@ -82,10 +127,20 @@ public async ValueTask ReceivedAsync(string subject, string? replyTo, ReadOnlySe return; } +#if NETSTANDARD2_0 + foreach (var weakReference in subTable) + { + if (weakReference.TryGetTarget(out var sub)) + { + await sub.ReceiveAsync(subject, replyTo, headersBuffer, payloadBuffer).ConfigureAwait(false); + } + } +#else foreach (var (sub, _) in subTable) { await sub.ReceiveAsync(subject, replyTo, headersBuffer, payloadBuffer).ConfigureAwait(false); } +#endif } public ValueTask RemoveAsync(NatsSubBase sub) @@ -93,11 +148,17 @@ public ValueTask RemoveAsync(NatsSubBase sub) if (!_bySubject.TryGetValue(sub.Subject, out var subTable)) { _logger.LogWarning(NatsLogEvents.InboxSubscription, "Unregistered message inbox received for {Subject}", sub.Subject); - return ValueTask.CompletedTask; + return default; } lock (subTable) { +#if NETSTANDARD2_0 + if (subTable.Count == 0) + { + _bySubject.TryRemove(sub.Subject, out _); + } +#else if (!subTable.Remove(sub)) _logger.LogWarning(NatsLogEvents.InboxSubscription, "Unregistered message inbox received for {Subject}", sub.Subject); @@ -105,10 +166,15 @@ public ValueTask RemoveAsync(NatsSubBase sub) { // try to remove this specific instance of the subTable // if an update is in process and sees an empty subTable, it will set a new instance +#if NET6_0_OR_GREATER _bySubject.TryRemove(KeyValuePair.Create(sub.Subject, subTable)); +#else + _bySubject.TryRemove(sub.Subject, out _); +#endif } +#endif } - return ValueTask.CompletedTask; + return default; } } diff --git a/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs b/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs index fd77311d6..da999ff07 100644 --- a/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs +++ b/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs @@ -8,6 +8,9 @@ using System.Text.Json; using Microsoft.Extensions.Logging; using NATS.Client.Core.Commands; +#if !NET6_0_OR_GREATER +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Core.Internal; @@ -86,10 +89,17 @@ private static int GetInt32(ReadOnlySpan span) private static int GetInt32(in ReadOnlySequence sequence) { +#if NETSTANDARD2_0 + if (sequence.IsSingleSegment || sequence.First.Span.Length <= 10) + { + return GetInt32(sequence.First.Span); + } +#else if (sequence.IsSingleSegment || sequence.FirstSpan.Length <= 10) { return GetInt32(sequence.FirstSpan); } +#endif Span buf = stackalloc byte[Math.Min((int)sequence.Length, 10)]; sequence.Slice(buf.Length).CopyTo(buf); @@ -300,7 +310,9 @@ await _connection.PublishToClientHandlersAsync(subject, replyTo, sid, headerSlic } } +#if NET6_0_OR_GREATER [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] +#endif private async ValueTask> DispatchCommandAsync(int code, ReadOnlySequence buffer) { var length = (int)buffer.Length; @@ -458,7 +470,11 @@ private async ValueTask> DispatchCommandAsync(int code, R { if (msgHeader.IsSingleSegment) { +#if NETSTANDARD2_0 + return ParseMessageHeader(msgHeader.First.Span); +#else return ParseMessageHeader(msgHeader.FirstSpan); +#endif } // header parsing use Slice frequently so ReadOnlySequence is high cost, should use Span. @@ -519,7 +535,11 @@ private async ValueTask> DispatchCommandAsync(int code, R { if (msgHeader.IsSingleSegment) { +#if NETSTANDARD2_0 + return ParseHMessageHeader(msgHeader.First.Span); +#else return ParseHMessageHeader(msgHeader.FirstSpan); +#endif } // header parsing use Slice frequently so ReadOnlySequence is high cost, should use Span. diff --git a/src/NATS.Client.Core/Internal/NuidWriter.cs b/src/NATS.Client.Core/Internal/NuidWriter.cs index 0458859cf..892115910 100644 --- a/src/NATS.Client.Core/Internal/NuidWriter.cs +++ b/src/NATS.Client.Core/Internal/NuidWriter.cs @@ -2,10 +2,15 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; +#if !NET6_0_OR_GREATER +using Random = NATS.Client.Core.Internal.NetStandardExtensions.Random; +#endif namespace NATS.Client.Core.Internal; +#if NET6_0_OR_GREATER [SkipLocalsInit] +#endif internal sealed class NuidWriter { internal const nuint NuidLength = PrefixLength + SequentialLength; @@ -19,7 +24,11 @@ internal sealed class NuidWriter [ThreadStatic] private static NuidWriter? _writer; +#if NET6_0_OR_GREATER private char[] _prefix; +#else + private char[] _prefix = null!; +#endif private ulong _increment; private ulong _sequential; @@ -28,7 +37,11 @@ private NuidWriter() Refresh(out _); } - private static ReadOnlySpan Digits => "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + private static ReadOnlySpan Digits => "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +#if NETSTANDARD2_0 + .AsSpan() +#endif + ; public static bool TryWriteNuid(Span nuidBuffer) { @@ -45,7 +58,11 @@ public static string NewNuid() Span buffer = stackalloc char[22]; if (TryWriteNuid(buffer)) { +#if NETSTANDARD2_0 + return new string(buffer.ToArray()); +#else return new string(buffer); +#endif } throw new InvalidOperationException("Internal error: can't generate nuid"); @@ -87,6 +104,15 @@ private static ulong GetSequential() private static char[] GetPrefix(RandomNumberGenerator? rng = null) { +#if NETSTANDARD2_0 + var randomBytes = new byte[(int)PrefixLength]; + + if (rng == null) + { + using var randomNumberGenerator = RandomNumberGenerator.Create(); + randomNumberGenerator.GetBytes(randomBytes); + } +#else Span randomBytes = stackalloc byte[(int)PrefixLength]; // TODO: For .NET 8+, use GetItems for better distribution @@ -94,6 +120,7 @@ private static char[] GetPrefix(RandomNumberGenerator? rng = null) { RandomNumberGenerator.Fill(randomBytes); } +#endif else { rng.GetBytes(randomBytes); @@ -137,7 +164,9 @@ bool RefreshAndWrite(Span buffer) } [MethodImpl(MethodImplOptions.NoInlining)] +#if NET6_0_OR_GREATER [MemberNotNull(nameof(_prefix))] +#endif private char[] Refresh(out ulong sequential) { var prefix = _prefix = GetPrefix(); diff --git a/src/NATS.Client.Core/Internal/ObjectPool.cs b/src/NATS.Client.Core/Internal/ObjectPool.cs index 0658294f2..d36e7e583 100644 --- a/src/NATS.Client.Core/Internal/ObjectPool.cs +++ b/src/NATS.Client.Core/Internal/ObjectPool.cs @@ -23,7 +23,11 @@ public ObjectPool(int poolLimit) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryRent([NotNullWhen(true)] out T? value) + public bool TryRent( +#if NETSTANDARD2_1 || NET6_0_OR_GREATER + [NotNullWhen(true)] +#endif + out T? value) where T : class, IObjectPoolNode { // poolNodes is grow only, safe to access indexer with no-lock @@ -103,7 +107,11 @@ public ObjectPool(int limit) public int Size => _size; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryPop([NotNullWhen(true)] out T? result) + public bool TryPop( +#if NETSTANDARD2_1 || NET6_0_OR_GREATER + [NotNullWhen(true)] +#endif + out T? result) { // Instead of lock, use CompareExchange gate. // In a worst case, missed cached object(create new one) but it's not a big deal. diff --git a/src/NATS.Client.Core/Internal/SocketReader.cs b/src/NATS.Client.Core/Internal/SocketReader.cs index ea032a9e9..7802c2037 100644 --- a/src/NATS.Client.Core/Internal/SocketReader.cs +++ b/src/NATS.Client.Core/Internal/SocketReader.cs @@ -2,6 +2,9 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; +#if !NET6_0_OR_GREATER +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Core.Internal; @@ -27,7 +30,9 @@ public SocketReader(ISocketConnection socketConnection, int minimumBufferSize, C _isTraceLogging = _logger.IsEnabled(LogLevel.Trace); } +#if NET6_0_OR_GREATER [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] +#endif public async ValueTask> ReadAtLeastAsync(int minimumSize) { var totalRead = 0; @@ -73,7 +78,9 @@ public async ValueTask> ReadAtLeastAsync(int minimumSize) return _seqeunceBuilder.ToReadOnlySequence(); } +#if NET6_0_OR_GREATER [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] +#endif public async ValueTask> ReadUntilReceiveNewLineAsync() { while (true) diff --git a/src/NATS.Client.Core/Internal/SslClientAuthenticationOptionsExtensions.cs b/src/NATS.Client.Core/Internal/SslClientAuthenticationOptionsExtensions.cs index a3f2482a2..0c527c725 100644 --- a/src/NATS.Client.Core/Internal/SslClientAuthenticationOptionsExtensions.cs +++ b/src/NATS.Client.Core/Internal/SslClientAuthenticationOptionsExtensions.cs @@ -1,3 +1,5 @@ +#if NET6_0_OR_GREATER + using System.Net.Security; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; @@ -118,3 +120,5 @@ static bool RcsCbInsecureSkipVerify( SslPolicyErrors sslPolicyErrors) => true; } } + +#endif diff --git a/src/NATS.Client.Core/Internal/SslStreamConnection.cs b/src/NATS.Client.Core/Internal/SslStreamConnection.cs index 5f5cb6f34..9effdc08e 100644 --- a/src/NATS.Client.Core/Internal/SslStreamConnection.cs +++ b/src/NATS.Client.Core/Internal/SslStreamConnection.cs @@ -1,8 +1,13 @@ using System.Net.Security; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Security.Authentication; using Microsoft.Extensions.Logging; +#if NETSTANDARD2_0 +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously +#endif + namespace NATS.Client.Core.Internal; internal sealed class SslStreamConnection : ISocketConnection @@ -30,10 +35,10 @@ public async ValueTask DisposeAsync() { try { -#if NET6_0 - _closeCts.Cancel(); -#else +#if NET8_0_OR_GREATER await _closeCts.CancelAsync().ConfigureAwait(false); +#else + _closeCts.Cancel(); #endif _waitForClosedSource.TrySetCanceled(); } @@ -41,28 +46,47 @@ public async ValueTask DisposeAsync() { } +#if NETSTANDARD2_0 + _sslStream.Dispose(); +#else await _sslStream.DisposeAsync().ConfigureAwait(false); +#endif } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public async ValueTask SendAsync(ReadOnlyMemory buffer) { +#if NETSTANDARD2_0 + // ReSharper disable once SuggestVarOrType_Elsewhere + MemoryMarshal.TryGetArray(buffer, out ArraySegment segment); + await _sslStream.WriteAsync(segment.Array, segment.Offset, segment.Count, _closeCts.Token).ConfigureAwait(false); +#else await _sslStream.WriteAsync(buffer, _closeCts.Token).ConfigureAwait(false); +#endif return buffer.Length; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask ReceiveAsync(Memory buffer) { +#if NETSTANDARD2_0 + MemoryMarshal.TryGetArray(buffer, out ArraySegment segment); + return new ValueTask(_sslStream.ReadAsync(segment.Array!, segment.Offset, segment.Count, _closeCts.Token)); +#else return _sslStream.ReadAsync(buffer, _closeCts.Token); +#endif } public async ValueTask AbortConnectionAsync(CancellationToken cancellationToken) { // SslStream.ShutdownAsync() doesn't accept a cancellation token, so check at the beginning of this method cancellationToken.ThrowIfCancellationRequested(); +#if NETSTANDARD2_0 + _sslStream.Close(); +#else await _sslStream.ShutdownAsync().ConfigureAwait(false); +#endif } // when catch SocketClosedException, call this method. @@ -73,11 +97,17 @@ public void SignalDisconnected(Exception exception) public async Task AuthenticateAsClientAsync(NatsUri uri, TimeSpan timeout) { +#if NET6_0_OR_GREATER var options = await _tlsOpts.AuthenticateAsClientOptionsAsync(uri).ConfigureAwait(true); +#endif try { +#if NET6_0_OR_GREATER using var cts = new CancellationTokenSource(timeout); await _sslStream.AuthenticateAsClientAsync(options, cts.Token).ConfigureAwait(false); +#else + await _sslStream.AuthenticateAsClientAsync(uri.Host).ConfigureAwait(false); +#endif } catch (OperationCanceledException) { diff --git a/src/NATS.Client.Core/Internal/SubscriptionManager.cs b/src/NATS.Client.Core/Internal/SubscriptionManager.cs index e363d0068..dbc938b62 100644 --- a/src/NATS.Client.Core/Internal/SubscriptionManager.cs +++ b/src/NATS.Client.Core/Internal/SubscriptionManager.cs @@ -3,6 +3,9 @@ using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; using NATS.Client.Core.Commands; +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Core.Internal; @@ -121,15 +124,15 @@ public ValueTask PublishToClientHandlersAsync(string subject, string? replyTo, i } } - return ValueTask.CompletedTask; + return default; } public async ValueTask DisposeAsync() { -#if NET6_0 - _cts.Cancel(); -#else +#if NET8_0_OR_GREATER await _cts.CancelAsync().ConfigureAwait(false); +#else + _cts.Cancel(); #endif WeakReference[] subRefs; @@ -152,13 +155,17 @@ public ValueTask RemoveAsync(NatsSubBase sub) { // this can happen when a call to SubscribeAsync is canceled or timed out before subscribing // in that case, return as there is nothing to unsubscribe - return ValueTask.CompletedTask; + return default; } lock (_gate) { _bySub.Remove(sub); +#if NETSTANDARD2_0 + _bySid.TryRemove(subMetadata.Sid, out _); +#else _bySid.Remove(subMetadata.Sid, out _); +#endif } return _connection.UnsubscribeAsync(subMetadata.Sid); @@ -237,7 +244,16 @@ private async ValueTask SubscribeInternalAsync(string subject, string? queueGrou lock (_gate) { _bySid[sid] = new SidMetadata(Subject: subject, WeakReference: new WeakReference(sub)); +#if NETSTANDARD2_0 + lock (_bySub) + { + if (_bySub.TryGetValue(sub, out _)) + _bySub.Remove(sub); + _bySub.Add(sub, new SubscriptionMetadata(Sid: sid)); + } +#else _bySub.AddOrUpdate(sub, new SubscriptionMetadata(Sid: sid)); +#endif } try diff --git a/src/NATS.Client.Core/Internal/TcpConnection.cs b/src/NATS.Client.Core/Internal/TcpConnection.cs index a6b175ff8..d2f99181b 100644 --- a/src/NATS.Client.Core/Internal/TcpConnection.cs +++ b/src/NATS.Client.Core/Internal/TcpConnection.cs @@ -3,6 +3,9 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; +#if !NET6_0_OR_GREATER +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Core.Internal; @@ -47,7 +50,11 @@ public TcpConnection(ILogger logger) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask ConnectAsync(string host, int port, CancellationToken cancellationToken) { +#if NET6_0_OR_GREATER return _socket.ConnectAsync(host, port, cancellationToken); +#else + return new ValueTask(_socket.ConnectAsync(host, port).WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken)); +#endif } /// @@ -58,7 +65,11 @@ public async ValueTask ConnectAsync(string host, int port, TimeSpan timeout) using var cts = new CancellationTokenSource(timeout); try { +#if NET6_0_OR_GREATER await _socket.ConnectAsync(host, port, cts.Token).ConfigureAwait(false); +#else + await _socket.ConnectAsync(host, port).WaitAsync(timeout, cts.Token).ConfigureAwait(false); +#endif } catch (Exception ex) { @@ -77,18 +88,34 @@ public async ValueTask ConnectAsync(string host, int port, TimeSpan timeout) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask SendAsync(ReadOnlyMemory buffer) { +#if NETSTANDARD2_0 + // ReSharper disable once SuggestVarOrType_Elsewhere + MemoryMarshal.TryGetArray(buffer, out ArraySegment segment); + return new ValueTask(_socket.SendAsync(segment, SocketFlags.None)); +#else return _socket.SendAsync(buffer, SocketFlags.None, CancellationToken.None); +#endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask ReceiveAsync(Memory buffer) { +#if NETSTANDARD2_0 + MemoryMarshal.TryGetArray(buffer, out ArraySegment segment); + return new ValueTask(_socket.ReceiveAsync(segment, SocketFlags.None)); +#else return _socket.ReceiveAsync(buffer, SocketFlags.None, CancellationToken.None); +#endif } public ValueTask AbortConnectionAsync(CancellationToken cancellationToken) { +#if NET6_0_OR_GREATER return _socket.DisconnectAsync(false, cancellationToken); +#else + _socket.Disconnect(false); + return default; +#endif } public ValueTask DisposeAsync() @@ -114,7 +141,7 @@ public ValueTask DisposeAsync() _socket.Dispose(); } - return ValueTask.CompletedTask; + return default; } // when catch SocketClosedException, call this method. diff --git a/src/NATS.Client.Core/Internal/Telemetry.cs b/src/NATS.Client.Core/Internal/Telemetry.cs index 35261fc18..5c5cb2fb7 100644 --- a/src/NATS.Client.Core/Internal/Telemetry.cs +++ b/src/NATS.Client.Core/Internal/Telemetry.cs @@ -204,7 +204,11 @@ static string GetMessage(Exception exception) static string GetStackTrace(Exception? exception) { var stackTrace = exception?.StackTrace; +#if NETSTANDARD2_0 + return string.IsNullOrWhiteSpace(stackTrace) ? string.Empty : stackTrace!; +#else return string.IsNullOrWhiteSpace(stackTrace) ? string.Empty : stackTrace; +#endif } } diff --git a/src/NATS.Client.Core/Internal/ThreadPoolWorkItem.cs b/src/NATS.Client.Core/Internal/ThreadPoolWorkItem.cs deleted file mode 100644 index e29079160..000000000 --- a/src/NATS.Client.Core/Internal/ThreadPoolWorkItem.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Collections.Concurrent; -using System.Runtime.CompilerServices; -using Microsoft.Extensions.Logging; - -namespace NATS.Client.Core.Internal; - -internal sealed class ThreadPoolWorkItem : IThreadPoolWorkItem -{ - private static readonly ConcurrentQueue> Pool = new(); - - private ThreadPoolWorkItem? _nextNode; - - private Action? _continuation; - private T? _value; - - private ILoggerFactory? _loggerFactory; - - private ThreadPoolWorkItem() - { - } - - public ref ThreadPoolWorkItem? NextNode => ref _nextNode; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ThreadPoolWorkItem Create(Action continuation, T? value, ILoggerFactory loggerFactory) - { - if (!Pool.TryDequeue(out var item)) - { - item = new ThreadPoolWorkItem(); - } - - item._continuation = continuation; - item._value = value; - item._loggerFactory = loggerFactory; - - return item; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Execute() - { - var call = _continuation; - var v = _value; - var factory = _loggerFactory; - _continuation = null; - _value = default; - _loggerFactory = null; - if (call != null) - { - Pool.Enqueue(this); - - try - { - call.Invoke(v); - } - catch (Exception ex) - { - if (_loggerFactory != null) - { - _loggerFactory.CreateLogger>().LogError(NatsLogEvents.Internal, ex, "Error occured during execute callback on ThreadPool"); - } - } - } - } -} diff --git a/src/NATS.Client.Core/Internal/UserCredentials.cs b/src/NATS.Client.Core/Internal/UserCredentials.cs index 898060841..b0a8336e7 100644 --- a/src/NATS.Client.Core/Internal/UserCredentials.cs +++ b/src/NATS.Client.Core/Internal/UserCredentials.cs @@ -14,12 +14,20 @@ public UserCredentials(NatsAuthOpts authOpts) if (!string.IsNullOrEmpty(authOpts.CredsFile)) { +#if NETSTANDARD2_0 + (Jwt, Seed) = LoadCredsFile(authOpts.CredsFile!); +#else (Jwt, Seed) = LoadCredsFile(authOpts.CredsFile); +#endif } if (!string.IsNullOrEmpty(authOpts.NKeyFile)) { +#if NETSTANDARD2_0 + (Seed, NKey) = LoadNKeyFile(authOpts.NKeyFile!); +#else (Seed, NKey) = LoadNKeyFile(authOpts.NKeyFile); +#endif } } diff --git a/src/NATS.Client.Core/Internal/WebSocketConnection.cs b/src/NATS.Client.Core/Internal/WebSocketConnection.cs index 252700212..871a5f1bb 100644 --- a/src/NATS.Client.Core/Internal/WebSocketConnection.cs +++ b/src/NATS.Client.Core/Internal/WebSocketConnection.cs @@ -1,6 +1,9 @@ using System.Net.Sockets; using System.Net.WebSockets; using System.Runtime.CompilerServices; +#if !NET6_0_OR_GREATER +using System.Runtime.InteropServices; +#endif namespace NATS.Client.Core.Internal; @@ -60,14 +63,25 @@ public async ValueTask ConnectAsync(Uri uri, TimeSpan timeout) [MethodImpl(MethodImplOptions.AggressiveInlining)] public async ValueTask SendAsync(ReadOnlyMemory buffer) { +#if NET6_0_OR_GREATER await _socket.SendAsync(buffer, WebSocketMessageType.Binary, WebSocketMessageFlags.EndOfMessage, CancellationToken.None).ConfigureAwait(false); +#else + // ReSharper disable once SuggestVarOrType_Elsewhere + MemoryMarshal.TryGetArray(buffer, out ArraySegment segment); + await _socket.SendAsync(segment, WebSocketMessageType.Binary, true, CancellationToken.None).ConfigureAwait(false); +#endif return buffer.Length; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public async ValueTask ReceiveAsync(Memory buffer) { +#if NETSTANDARD2_0 + MemoryMarshal.TryGetArray(buffer, out ArraySegment segment); + var wsRead = await _socket.ReceiveAsync(segment, CancellationToken.None).ConfigureAwait(false); +#else var wsRead = await _socket.ReceiveAsync(buffer, CancellationToken.None).ConfigureAwait(false); +#endif return wsRead.Count; } diff --git a/src/NATS.Client.Core/Internal/netstandard.cs b/src/NATS.Client.Core/Internal/netstandard.cs new file mode 100644 index 000000000..92d51ebad --- /dev/null +++ b/src/NATS.Client.Core/Internal/netstandard.cs @@ -0,0 +1,278 @@ +// ReSharper disable SuggestVarOrType_BuiltInTypes +// ReSharper disable ConvertToPrimaryConstructor +// ReSharper disable RedundantCast +// ReSharper disable SuggestVarOrType_Elsewhere + +#pragma warning disable SA1403 +#pragma warning disable SA1204 +#pragma warning disable SA1405 + +#if NETSTANDARD2_0 || NETSTANDARD2_1 + +namespace System.Runtime.CompilerServices +{ + internal static class IsExternalInit + { + } +} + +namespace NATS.Client.Core.Internal.NetStandardExtensions +{ + using System.Buffers; + using System.Diagnostics; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Text; + + [StructLayout(LayoutKind.Sequential, Size = 1)] + internal readonly struct VoidResult + { + } + + internal sealed class TaskCompletionSource : TaskCompletionSource + { + public TaskCompletionSource(TaskCreationOptions creationOptions) + : base(creationOptions) + { + } + + public new Task Task => base.Task; + + public bool TrySetResult() => TrySetResult(default); + + public void SetResult() => SetResult(default); + } + + internal static class TaskExtensionsCommon + { + internal static Task WaitAsync(this Task task, CancellationToken cancellationToken) + => WaitAsync(task, Timeout.InfiniteTimeSpan, cancellationToken); + + internal static Task WaitAsync(this Task task, TimeSpan timeout, CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + var timeoutTask = Task.Delay(timeout, cancellationToken); + +#pragma warning disable VSTHRD105 + return Task.WhenAny(task, timeoutTask).ContinueWith( +#pragma warning restore VSTHRD105 + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + completedTask => + { +#pragma warning disable VSTHRD103 + if (completedTask.Result == timeoutTask) +#pragma warning restore VSTHRD103 + { + throw new TimeoutException("The operation has timed out."); + } + + return task; + }, + cancellationToken).Unwrap(); + } + } + + internal static class SequenceReaderExtensions + { + public static bool TryReadTo(this ref SequenceReader reader, out ReadOnlySpan result, ReadOnlySpan delimiters) + { + if (reader.TryReadTo(out var buffer, delimiters)) + { + result = buffer.ToSpan(); + return true; + } + + result = default; + return false; + } + } + + internal class Random + { + [ThreadStatic] + private static System.Random? _internal; + + internal static Random Shared { get; } = new(); + + private static System.Random LocalRandom => _internal ?? Create(); + + internal double NextDouble() => LocalRandom.NextDouble(); + + internal int Next(int minValue, int maxValue) => LocalRandom.Next(minValue, maxValue); + + internal long NextInt64(long minValue, long maxValue) + { + if (minValue > maxValue) + throw new ArgumentOutOfRangeException(nameof(minValue), "minValue must be less than or equal to maxValue"); + + double range = (double)maxValue - (double)minValue + 1; + double sample = NextDouble(); + double scaled = sample * range; + + return (long)(scaled + minValue); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static System.Random Create() => _internal = new System.Random(); + } + + internal static class SpanExtensionsCommon + { + internal static bool Contains(this Span span, byte value) => span.IndexOf(value) >= 0; + } + + internal class PeriodicTimer : IDisposable + { + private readonly Timer _timer; + private readonly TimeSpan _period; + private TaskCompletionSource _tcs; + private bool _disposed; + + public PeriodicTimer(TimeSpan period) + { + _period = period; + _timer = new Timer(Callback, null, period, Timeout.InfiniteTimeSpan); + _tcs = new TaskCompletionSource(); + } + + public Task WaitForNextTickAsync(CancellationToken cancellationToken = default) + { + if (_disposed) + throw new ObjectDisposedException(nameof(PeriodicTimer)); + + _timer.Change(_period, Timeout.InfiniteTimeSpan); + + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + cancellationToken.Register(() => _tcs.TrySetCanceled(cancellationToken)); + + return _tcs.Task; + } + + public void Dispose() + { + _disposed = true; + _timer.Dispose(); + _tcs.TrySetResult(false); // Signal no more ticks will occur + } + + private void Callback(object state) + { + var tcs = Interlocked.Exchange(ref _tcs, new TaskCompletionSource()); + tcs.TrySetResult(true); + } + } + + internal static class ReadOnlySequenceExtensions + { + // Adapted from .NET 6.0 implementation + internal static long GetOffset(this in ReadOnlySequence sequence, SequencePosition position) + { + object? positionSequenceObject = position.GetObject(); + bool positionIsNull = positionSequenceObject == null; + + object? startObject = sequence.Start.GetObject(); + object? endObject = sequence.End.GetObject(); + + uint positionIndex = (uint)position.GetInteger(); + + // if a sequence object is null, we suppose start segment + if (positionIsNull) + { + positionSequenceObject = sequence.Start.GetObject(); + positionIndex = (uint)sequence.Start.GetInteger(); + } + + // Single-Segment Sequence + if (startObject == endObject) + { + return positionIndex; + } + else + { + // Verify position validity, this is not covered by BoundsCheck for Multi-Segment Sequence + // BoundsCheck for Multi-Segment Sequence check only validity inside a current sequence but not for SequencePosition validity. + // For single segment position bound check it is implicit. + Debug.Assert(positionSequenceObject != null); + + if (((ReadOnlySequenceSegment)positionSequenceObject!).Memory.Length - positionIndex < 0) + throw new ArgumentOutOfRangeException(); + + // Multi-Segment Sequence + ReadOnlySequenceSegment? currentSegment = (ReadOnlySequenceSegment?)startObject; + while (currentSegment != null && currentSegment != positionSequenceObject) + { + currentSegment = currentSegment.Next!; + } + + // Hit the end of the segments but didn't find the segment + if (currentSegment is null) + { + throw new ArgumentOutOfRangeException(); + } + + Debug.Assert(currentSegment!.RunningIndex + positionIndex >= 0); + + return currentSegment!.RunningIndex + positionIndex; + } + } + } + + internal static class EncodingExtensionsCommon + { + internal static string GetString(this Encoding encoding, in ReadOnlySequence buffer) + => encoding.GetString(buffer.ToArray()); + + internal static void GetBytes(this Encoding encoding, string chars, IBufferWriter bw) + { + var buffer = encoding.GetBytes(chars); + bw.Write(buffer); + } + } +} + +#endif + +#if NETSTANDARD2_0 + +namespace NATS.Client.Core.Internal.NetStandardExtensions +{ + using System.Text; + + internal static class EncodingExtensions + { + internal static int GetBytes(this Encoding encoding, string chars, Span bytes) + { + var buffer = encoding.GetBytes(chars); + buffer.AsSpan().CopyTo(bytes); + return buffer.Length; + } + + internal static string GetString(this Encoding encoding, in ReadOnlySpan buffer) + { + return encoding.GetString(buffer.ToArray()); + } + } + + internal static class TaskExtensions + { + internal static bool IsNotCompletedSuccessfully(this Task? task) + { + return task != null && (!task.IsCompleted || task.IsCanceled || task.IsFaulted); + } + } + + internal static class KeyValuePairExtensions + { + internal static void Deconstruct(this KeyValuePair kv, out TKey key, out TValue value) + { + key = kv.Key; + value = kv.Value; + } + } +} + +#endif diff --git a/src/NATS.Client.Core/NATS.Client.Core.csproj b/src/NATS.Client.Core/NATS.Client.Core.csproj index 58767fd80..98f8acc4a 100644 --- a/src/NATS.Client.Core/NATS.Client.Core.csproj +++ b/src/NATS.Client.Core/NATS.Client.Core.csproj @@ -1,11 +1,13 @@  - net6.0;net8.0 + + + + netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable true - true pubsub;messaging @@ -13,10 +15,31 @@ true + + true + + + + + + + + + + + + + + + + + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/NATS.Client.Core/NKeyPair.cs b/src/NATS.Client.Core/NKeyPair.cs index 4e3e5f416..e4c7f48c2 100644 --- a/src/NATS.Client.Core/NKeyPair.cs +++ b/src/NATS.Client.Core/NKeyPair.cs @@ -377,6 +377,10 @@ internal static ushort Checksum(byte[] data) // Borrowed from: https://stackoverflow.com/a/7135008 internal class Base32 { +#if NETSTANDARD2_0 + public static byte[] Decode(string input) => Decode(input.AsSpan()); +#endif + public static byte[] Decode(ReadOnlySpan input) { if (input == null || input.Length == 0) diff --git a/src/NATS.Client.Core/NaCl/Sha512.cs b/src/NATS.Client.Core/NaCl/Sha512.cs index d5275f1cf..c517550fd 100644 --- a/src/NATS.Client.Core/NaCl/Sha512.cs +++ b/src/NATS.Client.Core/NaCl/Sha512.cs @@ -66,9 +66,18 @@ public byte[] Finalize() /// Hash bytes public static byte[] Hash(byte[] data, int index, int length) { +#if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(data); ReadOnlySpan dataSpan = data; return SHA512.HashData(dataSpan.Slice(index, length)); +#else + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + using var sha512 = SHA512.Create(); + return sha512.ComputeHash(data, index, length); +#endif } /// diff --git a/src/NATS.Client.Core/NatsBufferWriter.cs b/src/NATS.Client.Core/NatsBufferWriter.cs index 3e1326891..dffe2be0c 100644 --- a/src/NATS.Client.Core/NatsBufferWriter.cs +++ b/src/NATS.Client.Core/NatsBufferWriter.cs @@ -1,10 +1,13 @@ // adapted from https://github.com/CommunityToolkit/dotnet/blob/main/src/CommunityToolkit.HighPerformance/Buffers/NatsBufferWriter%7BT%7D.cs +#pragma warning disable SA1116, SA1117 using System.Buffers; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#if NET6_0_OR_GREATER using BitOperations = System.Numerics.BitOperations; +#endif namespace NATS.Client.Core; @@ -361,6 +364,7 @@ private void ResizeBuffer(int sizeHint) { var minimumSize = (uint)_index + (uint)sizeHint; +#if NET6_0_OR_GREATER // The ArrayPool class has a maximum threshold of 1024 * 1024 for the maximum length of // pooled arrays, and once this is exceeded it will just allocate a new array every time // of exactly the requested size. In that case, we manually round up the requested size to @@ -370,6 +374,7 @@ private void ResizeBuffer(int sizeHint) { minimumSize = BitOperations.RoundUpToPowerOf2(minimumSize); } +#endif _pool.Resize(ref _array, (int)minimumSize); } @@ -387,7 +392,11 @@ internal static class NatsBufferWriterExtensions /// Indicates whether the contents of the array should be cleared before reuse. /// Thrown when is less than 0. /// When this method returns, the caller must not use any references to the old array anymore. - public static void Resize(this ArrayPool pool, [NotNull] ref T[]? array, int newSize, bool clearArray = false) + public static void Resize(this ArrayPool pool, +#if !NETSTANDARD2_0 + [NotNull] +#endif + ref T[]? array, int newSize, bool clearArray = false) { // If the old array is null, just create a new one with the requested size if (array is null) diff --git a/src/NATS.Client.Core/NatsConnection.LowLevelApi.cs b/src/NATS.Client.Core/NatsConnection.LowLevelApi.cs index 9cbcf23e7..ba085818d 100644 --- a/src/NATS.Client.Core/NatsConnection.LowLevelApi.cs +++ b/src/NATS.Client.Core/NatsConnection.LowLevelApi.cs @@ -1,3 +1,7 @@ +#if !NET6_0_OR_GREATER +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif + namespace NATS.Client.Core; public partial class NatsConnection diff --git a/src/NATS.Client.Core/NatsConnection.Ping.cs b/src/NATS.Client.Core/NatsConnection.Ping.cs index a2d7a882a..f518a0334 100644 --- a/src/NATS.Client.Core/NatsConnection.Ping.cs +++ b/src/NATS.Client.Core/NatsConnection.Ping.cs @@ -1,12 +1,18 @@ using System.Runtime.CompilerServices; using NATS.Client.Core.Commands; +using NATS.Client.Core.Internal; +#if !NET6_0_OR_GREATER +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Core; public partial class NatsConnection { /// +#if NET6_0_OR_GREATER [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] +#endif public async ValueTask PingAsync(CancellationToken cancellationToken = default) { if (ConnectionState != NatsConnectionState.Open) @@ -38,5 +44,5 @@ public async ValueTask PingAsync(CancellationToken cancellationToken = private ValueTask PingOnlyAsync(CancellationToken cancellationToken = default) => ConnectionState == NatsConnectionState.Open ? CommandWriter.PingAsync(new PingCommand(_pool), cancellationToken) - : ValueTask.CompletedTask; + : default; } diff --git a/src/NATS.Client.Core/NatsConnection.Publish.cs b/src/NATS.Client.Core/NatsConnection.Publish.cs index abe0fbf7b..254ffbe6d 100644 --- a/src/NATS.Client.Core/NatsConnection.Publish.cs +++ b/src/NATS.Client.Core/NatsConnection.Publish.cs @@ -1,5 +1,8 @@ using System.Diagnostics; using NATS.Client.Core.Internal; +#if !NET6_0_OR_GREATER +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Core; diff --git a/src/NATS.Client.Core/NatsConnection.RequestReply.cs b/src/NATS.Client.Core/NatsConnection.RequestReply.cs index cec7504dc..faff3c5de 100644 --- a/src/NATS.Client.Core/NatsConnection.RequestReply.cs +++ b/src/NATS.Client.Core/NatsConnection.RequestReply.cs @@ -42,11 +42,21 @@ public async ValueTask> RequestAsync( await using var sub1 = await RequestSubAsync(subject, data, headers, requestSerializer, replySerializer, requestOpts, replyOpts, cancellationToken) .ConfigureAwait(false); +#if NETSTANDARD2_0 + if (await sub1.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + if (sub1.Msgs.TryRead(out var msg)) + { + return msg; + } + } +#else + // Prefer ReadAllAsync() since underlying ActivityEndingMsgReader maintains GCHandle for the subscription more efficiently. await foreach (var msg in sub1.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) { return msg; } - +#endif throw new NatsNoReplyException(); } catch (Exception e) @@ -60,10 +70,21 @@ public async ValueTask> RequestAsync( await using var sub = await RequestSubAsync(subject, data, headers, requestSerializer, replySerializer, requestOpts, replyOpts, cancellationToken) .ConfigureAwait(false); +#if NETSTANDARD2_0 + if (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + if (sub.Msgs.TryRead(out var msg)) + { + return msg; + } + } +#else + // Prefer ReadAllAsync() since underlying ActivityEndingMsgReader maintains GCHandle for the subscription more efficiently. await foreach (var msg in sub.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) { return msg; } +#endif throw new NatsNoReplyException(); } @@ -99,13 +120,30 @@ public async IAsyncEnumerable> RequestManyAsync(subject, data, headers, requestSerializer, replySerializer, requestOpts, replyOpts, cancellationToken) .ConfigureAwait(false); +#if NETSTANDARD2_0 + if (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + if (sub.Msgs.TryRead(out var msg)) + { + yield return msg; + } + } +#else + // Prefer ReadAllAsync() since underlying ActivityEndingMsgReader maintains GCHandle for the subscription more efficiently. await foreach (var msg in sub.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) { yield return msg; } +#endif } +#if NETSTANDARD2_0 + internal static string NewInbox(string prefix) => NewInbox(prefix.AsSpan()); +#endif + +#if NET6_0_OR_GREATER [SkipLocalsInit] +#endif internal static string NewInbox(ReadOnlySpan prefix) { Span buffer = stackalloc char[64]; @@ -128,12 +166,18 @@ internal static string NewInbox(ReadOnlySpan prefix) var remaining = buffer.Slice((int)totalPrefixLength); var didWrite = NuidWriter.TryWriteNuid(remaining); Debug.Assert(didWrite, "didWrite"); +#if NETSTANDARD2_0 + return new string(buffer.ToArray()); +#else return new string(buffer); +#endif } return Throw(); +#if NETSTANDARD2_1 || NET6_0_OR_GREATER [DoesNotReturn] +#endif string Throw() { Debug.Fail("Must not happen"); diff --git a/src/NATS.Client.Core/NatsConnection.Subscribe.cs b/src/NATS.Client.Core/NatsConnection.Subscribe.cs index d163db30e..0c1b4d82f 100644 --- a/src/NATS.Client.Core/NatsConnection.Subscribe.cs +++ b/src/NATS.Client.Core/NatsConnection.Subscribe.cs @@ -15,10 +15,21 @@ public async IAsyncEnumerable> SubscribeAsync(string subject, stri // We don't cancel the channel reader here because we want to keep reading until the subscription // channel writer completes so that messages left in the channel can be consumed before exit the loop. +#if NETSTANDARD2_0 + while (await sub.Msgs.WaitToReadAsync(CancellationToken.None).ConfigureAwait(false)) + { + while (sub.Msgs.TryRead(out var msg)) + { + yield return msg; + } + } +#else + // Prefer ReadAllAsync() since underlying ActivityEndingMsgReader maintains GCHandle for the subscription more efficiently. await foreach (var msg in sub.Msgs.ReadAllAsync(CancellationToken.None).ConfigureAwait(false)) { yield return msg; } +#endif } /// diff --git a/src/NATS.Client.Core/NatsConnection.cs b/src/NATS.Client.Core/NatsConnection.cs index 2414bdd4b..1de5d3503 100644 --- a/src/NATS.Client.Core/NatsConnection.cs +++ b/src/NATS.Client.Core/NatsConnection.cs @@ -4,6 +4,10 @@ using Microsoft.Extensions.Logging; using NATS.Client.Core.Commands; using NATS.Client.Core.Internal; +#if !NET6_0_OR_GREATER +using NATS.Client.Core.Internal.NetStandardExtensions; +using Random = NATS.Client.Core.Internal.NetStandardExtensions.Random; +#endif namespace NATS.Client.Core; @@ -38,7 +42,6 @@ public partial class NatsConnection : INatsConnection private readonly object _gate = new object(); private readonly ILogger _logger; private readonly ObjectPool _pool; - private readonly CancellationTimerPool _cancellationTimerPool; private readonly CancellationTokenSource _disposedCancellationTokenSource; private readonly string _name; private readonly TimeSpan _socketComponentDisposeTimeout = TimeSpan.FromSeconds(5); @@ -75,7 +78,6 @@ public NatsConnection(NatsOpts opts) _waitForOpenConnection = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _disposedCancellationTokenSource = new CancellationTokenSource(); _pool = new ObjectPool(opts.ObjectPoolSize); - _cancellationTimerPool = new CancellationTimerPool(_pool, _disposedCancellationTokenSource.Token); _name = opts.Name; Counter = new ConnectionStatsCounter(); CommandWriter = new CommandWriter(this, _pool, Opts, Counter, EnqueuePing); @@ -183,20 +185,20 @@ public virtual async ValueTask DisposeAsync() await DisposeSocketAsync(false).ConfigureAwait(false); if (_pingTimerCancellationTokenSource != null) { -#if NET6_0 - _pingTimerCancellationTokenSource.Cancel(); -#else +#if NET8_0_OR_GREATER await _pingTimerCancellationTokenSource.CancelAsync().ConfigureAwait(false); +#else + _pingTimerCancellationTokenSource.Cancel(); #endif } await SubscriptionManager.DisposeAsync().ConfigureAwait(false); await CommandWriter.DisposeAsync().ConfigureAwait(false); _waitForOpenConnection.TrySetCanceled(); -#if NET6_0 - _disposedCancellationTokenSource.Cancel(); -#else +#if NET8_0_OR_GREATER await _disposedCancellationTokenSource.CancelAsync().ConfigureAwait(false); +#else + _disposedCancellationTokenSource.Cancel(); #endif } } @@ -240,13 +242,13 @@ internal ValueTask UnsubscribeAsync(int sid) // connection is disposed, don't need to unsubscribe command. if (IsDisposed) { - return ValueTask.CompletedTask; + return default; } _logger.LogError(NatsLogEvents.Subscription, ex, "Failed to send unsubscribe command"); } - return ValueTask.CompletedTask; + return default; } internal void OnMessageDropped(NatsSubBase natsSub, int pending, NatsMsg msg) @@ -717,7 +719,11 @@ private NatsUri FixTlsHost(NatsUri uri) && Uri.CheckHostName(uri.Host) != UriHostNameType.Dns && Uri.CheckHostName(lastSeedHost) == UriHostNameType.Dns) { +#if NETSTANDARD2_0 + return uri.CloneWith(lastSeedHost!); +#else return uri.CloneWith(lastSeedHost); +#endif } return uri; @@ -746,7 +752,11 @@ private async Task WaitWithJitterAsync() } else { +#if NETSTANDARD2_0 + _backoff = new TimeSpan(_backoff.Ticks * 2); +#else _backoff *= 2; +#endif if (_backoff > Opts.ReconnectWaitMax) { _backoff = Opts.ReconnectWaitMax; @@ -822,12 +832,6 @@ private void EnqueuePing(PingCommand pingCommand) pingCommand.SetCanceled(); } - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - private CancellationTimer GetRequestCommandTimer(CancellationToken cancellationToken) - { - return _cancellationTimerPool.Start(Opts.RequestTimeout, cancellationToken); - } - // catch and log all exceptions, enforcing the socketComponentDisposeTimeout private async ValueTask DisposeSocketComponentAsync(IAsyncDisposable component, string description) { diff --git a/src/NATS.Client.Core/NatsException.cs b/src/NATS.Client.Core/NatsException.cs index 4b8e75404..53303ab48 100644 --- a/src/NATS.Client.Core/NatsException.cs +++ b/src/NATS.Client.Core/NatsException.cs @@ -35,10 +35,10 @@ public NatsServerException(string error) : base($"Server error: {error}") { Error = error; - IsAuthError = Error.Contains("authorization violation", StringComparison.OrdinalIgnoreCase) - || Error.Contains("user authentication expired", StringComparison.OrdinalIgnoreCase) - || Error.Contains("user authentication revoked", StringComparison.OrdinalIgnoreCase) - || Error.Contains("account authentication expired", StringComparison.OrdinalIgnoreCase); + IsAuthError = Error.Contains("authorization violation") + || Error.Contains("user authentication expired") + || Error.Contains("user authentication revoked") + || Error.Contains("account authentication expired"); } public string Error { get; } diff --git a/src/NATS.Client.Core/NatsHeaderParser.cs b/src/NATS.Client.Core/NatsHeaderParser.cs index e26077aa5..e2ced4c6b 100644 --- a/src/NATS.Client.Core/NatsHeaderParser.cs +++ b/src/NATS.Client.Core/NatsHeaderParser.cs @@ -7,6 +7,9 @@ using Microsoft.Extensions.Primitives; using NATS.Client.Core.Commands; using NATS.Client.Core.Internal; +#if !NET6_0_OR_GREATER +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif // ReSharper disable ConditionIsAlwaysTrueOrFalse // ReSharper disable PossiblyImpureMethodCallOnReadonlyVariable @@ -237,7 +240,9 @@ private bool TryTakeSingleHeader(ReadOnlySpan headerLine, NatsHeaders head return true; } +#if NET6_0_OR_GREATER [StackTraceHidden] +#endif private void RejectRequestHeader(ReadOnlySpan headerLine) => throw new NatsException( $"Protocol error: invalid request header line '{headerLine.Dump()}'"); diff --git a/src/NATS.Client.Core/NatsHeaders.cs b/src/NATS.Client.Core/NatsHeaders.cs index 55af3665f..04325f73c 100644 --- a/src/NATS.Client.Core/NatsHeaders.cs +++ b/src/NATS.Client.Core/NatsHeaders.cs @@ -102,7 +102,9 @@ public NatsHeaders(int capacity) private Dictionary? Store { get; set; } +#if NET6_0_OR_GREATER [MemberNotNull(nameof(Store))] +#endif private void EnsureStore(int capacity) { if (Store == null) @@ -146,7 +148,11 @@ public StringValues this[string key] else { EnsureStore(1); +#if NET6_0_OR_GREATER Store[key] = value; +#else + Store![key] = value; +#endif } } } @@ -217,7 +223,11 @@ public void Add(KeyValuePair item) } ThrowIfReadOnly(); EnsureStore(1); +#if NET6_0_OR_GREATER Store.Add(item.Key, item.Value); +#else + Store!.Add(item.Key, item.Value); +#endif } /// @@ -233,7 +243,11 @@ public void Add(string key, StringValues value) } ThrowIfReadOnly(); EnsureStore(1); +#if NET6_0_OR_GREATER Store.Add(key, value); +#else + Store!.Add(key, value); +#endif } /// diff --git a/src/NATS.Client.Core/NatsMemoryOwner.cs b/src/NATS.Client.Core/NatsMemoryOwner.cs index 8a6430852..dc53e178c 100644 --- a/src/NATS.Client.Core/NatsMemoryOwner.cs +++ b/src/NATS.Client.Core/NatsMemoryOwner.cs @@ -177,6 +177,7 @@ public Span Span [MethodImpl(MethodImplOptions.AggressiveInlining)] get { +#if NET6_0_OR_GREATER var array = _array; if (array is null) @@ -196,9 +197,13 @@ public Span Span // default Span constructor and paying the cost of the extra conditional branches, // especially if T is a value type, in which case the covariance check is JIT removed. return MemoryMarshal.CreateSpan(ref r0, _length); +#else + return _array.AsSpan().Slice(_start, _length); +#endif } } +#if NET6_0_OR_GREATER /// /// Returns a reference to the first element within the current instance, with no bounds check. /// @@ -221,6 +226,7 @@ public ref T DangerousGetReference() return ref array!.DangerousGetReferenceAt(_start); } +#endif /// /// Gets an instance wrapping the underlying array in use. @@ -346,6 +352,8 @@ private static void ThrowInvalidLengthException() } } +#if NET6_0_OR_GREATER + internal static class NatsMemoryOwnerArrayExtensions { /// @@ -379,3 +387,5 @@ public static ref T DangerousGetReferenceAt(this T[] array, int i) #pragma warning restore CS8619 } } + +#endif diff --git a/src/NATS.Client.Core/NatsMsg.cs b/src/NATS.Client.Core/NatsMsg.cs index 3a1f0d929..33cd06278 100644 --- a/src/NATS.Client.Core/NatsMsg.cs +++ b/src/NATS.Client.Core/NatsMsg.cs @@ -148,7 +148,11 @@ public void EnsureSuccess() public ValueTask ReplyAsync(NatsHeaders? headers = default, string? replyTo = default, NatsPubOpts? opts = default, CancellationToken cancellationToken = default) { CheckReplyPreconditions(); +#if NET6_0_OR_GREATER return Connection.PublishAsync(ReplyTo, headers, replyTo, opts, cancellationToken); +#else + return Connection!.PublishAsync(ReplyTo!, headers, replyTo, opts, cancellationToken); +#endif } /// @@ -176,7 +180,11 @@ public ValueTask ReplyAsync(NatsHeaders? headers = default, string? replyTo = de public ValueTask ReplyAsync(TReply data, NatsHeaders? headers = default, string? replyTo = default, INatsSerialize? serializer = default, NatsPubOpts? opts = default, CancellationToken cancellationToken = default) { CheckReplyPreconditions(); +#if NET6_0_OR_GREATER return Connection.PublishAsync(ReplyTo, data, headers, replyTo, serializer, opts, cancellationToken); +#else + return Connection!.PublishAsync(ReplyTo!, data, headers, replyTo, serializer, opts, cancellationToken); +#endif } /// @@ -194,7 +202,11 @@ public ValueTask ReplyAsync(TReply data, NatsHeaders? headers = default, public ValueTask ReplyAsync(NatsMsg msg, INatsSerialize? serializer = default, NatsPubOpts? opts = default, CancellationToken cancellationToken = default) { CheckReplyPreconditions(); +#if NET6_0_OR_GREATER return Connection.PublishAsync(msg with { Subject = ReplyTo }, serializer, opts, cancellationToken); +#else + return Connection!.PublishAsync(msg with { Subject = ReplyTo! }, serializer, opts, cancellationToken); +#endif } internal static NatsMsg Build( @@ -280,8 +292,10 @@ internal static NatsMsg Build( return new NatsMsg(subject, replyTo, (int)size, headers, data, connection); } +#if NET6_0_OR_GREATER [MemberNotNull(nameof(Connection))] [MemberNotNull(nameof(ReplyTo))] +#endif private void CheckReplyPreconditions() { if (Connection == default) diff --git a/src/NATS.Client.Core/NatsPooledConnection.cs b/src/NATS.Client.Core/NatsPooledConnection.cs index 00f52b333..8fbf01767 100644 --- a/src/NATS.Client.Core/NatsPooledConnection.cs +++ b/src/NATS.Client.Core/NatsPooledConnection.cs @@ -7,7 +7,7 @@ public NatsPooledConnection(NatsOpts opts) { } - public override ValueTask DisposeAsync() => ValueTask.CompletedTask; + public override ValueTask DisposeAsync() => default; internal ValueTask ForceDisposeAsync() => base.DisposeAsync(); } diff --git a/src/NATS.Client.Core/NatsSubBase.cs b/src/NATS.Client.Core/NatsSubBase.cs index 22e787ab2..56435e343 100644 --- a/src/NATS.Client.Core/NatsSubBase.cs +++ b/src/NATS.Client.Core/NatsSubBase.cs @@ -75,13 +75,20 @@ internal NatsSubBase( // might be a problem. This should reduce the impact of that problem. cancellationToken.ThrowIfCancellationRequested(); - _tokenRegistration = cancellationToken.UnsafeRegister( - state => - { - var self = (NatsSubBase)state!; - self.EndSubscription(NatsSubEndReason.Cancelled); - }, - this); + _tokenRegistration = cancellationToken +#if NET6_0_OR_GREATER + .UnsafeRegister( +#else + .Register( +#endif +#pragma warning disable SA1114 + state => + { + var self = (NatsSubBase)state!; + self.EndSubscription(NatsSubEndReason.Cancelled); + }, +#pragma warning restore SA1114 + this); // Only allocate timers if necessary to reduce GC pressure if (_idleTimeout != default) @@ -143,7 +150,7 @@ public virtual ValueTask ReadyAsync() _startUpTimeoutTimer?.Change(_startUpTimeout, Timeout.InfiniteTimeSpan); _timeoutTimer?.Change(dueTime: _timeout, period: Timeout.InfiniteTimeSpan); - return ValueTask.CompletedTask; + return default; } /// @@ -156,7 +163,7 @@ public ValueTask UnsubscribeAsync() lock (_gate) { if (_unsubscribed) - return ValueTask.CompletedTask; + return default; _unsubscribed = true; } @@ -186,7 +193,7 @@ public virtual ValueTask DisposeAsync() lock (_gate) { if (_disposed) - return ValueTask.CompletedTask; + return default; _disposed = true; } diff --git a/src/NATS.Client.Core/NatsTlsOpts.cs b/src/NATS.Client.Core/NatsTlsOpts.cs index c5a45633f..3f022307f 100644 --- a/src/NATS.Client.Core/NatsTlsOpts.cs +++ b/src/NATS.Client.Core/NatsTlsOpts.cs @@ -61,11 +61,14 @@ public sealed record NatsTlsOpts /// public string? KeyFile { get; init; } +#if NETSTANDARD2_1 || NET6_0_OR_GREATER /// /// Callback to configure /// public Func? ConfigureClientAuthentication { get; init; } +#endif +#if NETSTANDARD2_1 || NET6_0_OR_GREATER /// /// Callback that loads Client Certificate /// @@ -74,12 +77,19 @@ public sealed record NatsTlsOpts /// [Obsolete("use ConfigureClientAuthentication")] public Func>? LoadClientCert { get; init; } +#else + /// + /// Callback that loads Client Certificate + /// + public Func>? LoadClientCert { get; init; } +#endif /// /// String or file path to PEM-encoded X509 CA Certificate /// public string? CaFile { get; init; } +#if NETSTANDARD2_1 || NET6_0_OR_GREATER /// /// Callback that loads CA Certificates /// @@ -88,10 +98,17 @@ public sealed record NatsTlsOpts /// [Obsolete("use ConfigureClientAuthentication")] public Func>? LoadCaCerts { get; init; } +#else + /// + /// Callback that loads CA Certificates + /// + public Func>? LoadCaCerts { get; init; } +#endif /// When true, skip remote certificate verification and accept any server certificate public bool InsecureSkipVerify { get; init; } +#if NETSTANDARD2_1 || NET6_0_OR_GREATER /// Certificate revocation mode for certificate validation. /// One of the values in . The default is . /// @@ -99,12 +116,25 @@ public sealed record NatsTlsOpts /// [Obsolete("use ConfigureClientAuthentication")] public X509RevocationMode CertificateRevocationCheckMode { get; init; } +#else + /// Certificate revocation mode for certificate validation. + /// One of the values in . The default is . + public X509RevocationMode CertificateRevocationCheckMode { get; init; } +#endif /// TLS mode to use during connection public TlsMode Mode { get; init; } - internal bool HasTlsCerts => CertFile != default || KeyFile != default || CaFile != default || ConfigureClientAuthentication != default; + internal bool HasTlsCerts => + CertFile != default + || KeyFile != default + || CaFile != default +#if NETSTANDARD2_1 || NET6_0_OR_GREATER + || ConfigureClientAuthentication != default +#endif + ; +#if NET6_0_OR_GREATER /// /// Helper method to load a Client Certificate from a pem-encoded string /// @@ -131,6 +161,7 @@ public static Func> LoadCaCertsFromPem(str caCerts.ImportFromPem(caPem); return () => ValueTask.FromResult(caCerts); } +#endif internal TlsMode EffectiveMode(NatsUri uri) => Mode switch { @@ -144,6 +175,7 @@ internal bool TryTls(NatsUri uri) return effectiveMode is TlsMode.Require or TlsMode.Prefer; } +#if NET6_0_OR_GREATER internal async ValueTask AuthenticateAsClientOptionsAsync(NatsUri uri) { #pragma warning disable CS0618 // Type or member is obsolete @@ -206,4 +238,6 @@ await File.ReadAllTextAsync(CertFile).ConfigureAwait(false), return options; #pragma warning restore CS0618 // Type or member is obsolete } +#endif + } diff --git a/tests/NATS.Client.Core.Tests/CancellationTest.cs b/tests/NATS.Client.Core.Tests/CancellationTest.cs index 07c4ac80d..4d7ae848e 100644 --- a/tests/NATS.Client.Core.Tests/CancellationTest.cs +++ b/tests/NATS.Client.Core.Tests/CancellationTest.cs @@ -82,22 +82,4 @@ await Assert.ThrowsAsync(async () => } }); } - - [Fact] - public async Task Cancellation_timer() - { - var objectPool = new ObjectPool(10); - var cancellationTimerPool = new CancellationTimerPool(objectPool, CancellationToken.None); - var cancellationTimer = cancellationTimerPool.Start(TimeSpan.FromSeconds(2), CancellationToken.None); - - try - { - await Task.Delay(TimeSpan.FromSeconds(4), cancellationTimer.Token); - _output.WriteLine($"delayed 4 seconds"); - } - catch (Exception e) - { - _output.WriteLine($"Exception: {e.GetType().Name}"); - } - } } diff --git a/tests/NATS.Client.Core.Tests/FixedArrayBufferWriterTest.cs b/tests/NATS.Client.Core.Tests/FixedArrayBufferWriterTest.cs deleted file mode 100644 index 20f96e1fc..000000000 --- a/tests/NATS.Client.Core.Tests/FixedArrayBufferWriterTest.cs +++ /dev/null @@ -1,66 +0,0 @@ -using NATS.Client.Core.Internal; - -namespace NATS.Client.Core.Tests; - -public class FixedArrayBufferWriterTest -{ - [Fact] - public void Standard() - { - var writer = new FixedArrayBufferWriter(); - - var buffer = writer.GetSpan(); - buffer[0] = 100; - buffer[1] = 200; - buffer[2] = 220; - - writer.Advance(3); - - var buffer2 = writer.GetSpan(); - buffer2[0].Should().Be(0); - (buffer.Length - buffer2.Length).Should().Be(3); - - buffer2[0] = 244; - writer.Advance(1); - - writer.WrittenMemory.ToArray().Should().Equal(100, 200, 220, 244); - } - - [Fact] - public void Ensure() - { - var writer = new FixedArrayBufferWriter(); - - writer.Advance(20000); - - var newSpan = writer.GetSpan(50000); - - newSpan.Length.Should().Be((ushort.MaxValue * 2) - 20000); - } - - [Theory] - [InlineData(129, 0, "double capacity")] - [InlineData(257, 0, "adjust capacity to size")] - [InlineData(129, 1, "double capacity when already advanced")] - [InlineData(257, 1, "adjust capacity to size when already advanced")] - public void Resize(int size, int advance, string reason) - { - // GetSpan() - { - var writer = new FixedArrayBufferWriter(128); - if (advance > 0) - writer.Advance(advance); - var span = writer.GetSpan(size); - span.Length.Should().BeGreaterOrEqualTo(size, reason); - } - - // GetMemory() - { - var writer = new FixedArrayBufferWriter(128); - if (advance > 0) - writer.Advance(advance); - var memory = writer.GetMemory(size); - memory.Length.Should().BeGreaterOrEqualTo(size, reason); - } - } -} From c4c1994a2ce8cdec4244caa09d805e9afdf9dc7b Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 07:17:15 +0100 Subject: [PATCH 02/47] merge fix --- src/NATS.Client.Core/NatsHeaders.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/NATS.Client.Core/NatsHeaders.cs b/src/NATS.Client.Core/NatsHeaders.cs index 16fd1b0d8..32a5c0799 100644 --- a/src/NATS.Client.Core/NatsHeaders.cs +++ b/src/NATS.Client.Core/NatsHeaders.cs @@ -365,7 +365,12 @@ public bool TryGetValue(string key, out StringValues value) /// The header name. /// The value. /// true if the contains the key; otherwise, false. - public bool TryGetLastValue(string key, [NotNullWhen(returnValue: true)] out string? value) + public bool TryGetLastValue( + string key, +#if NET6_0_OR_GREATER + [NotNullWhen(returnValue: true)] +#endif + out string? value) { if (Store != null && Store.TryGetValue(key, out var values)) { From b1d53c94d178cc56779fadb8c739235cbcf57954 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 09:26:35 +0100 Subject: [PATCH 03/47] Auth error fix --- src/NATS.Client.Core/NatsException.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NATS.Client.Core/NatsException.cs b/src/NATS.Client.Core/NatsException.cs index 53303ab48..4f7d2be78 100644 --- a/src/NATS.Client.Core/NatsException.cs +++ b/src/NATS.Client.Core/NatsException.cs @@ -34,7 +34,7 @@ public sealed class NatsServerException : NatsException public NatsServerException(string error) : base($"Server error: {error}") { - Error = error; + Error = error.ToLower(); IsAuthError = Error.Contains("authorization violation") || Error.Contains("user authentication expired") || Error.Contains("user authentication revoked") From b190aaef73e4d1678a9600b9fc198c434020084d Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 09:35:57 +0100 Subject: [PATCH 04/47] dotnet format --- .../Internal/SslStreamConnection.cs | 3 +-- src/NATS.Client.Core/Internal/TcpConnection.cs | 3 +-- .../Internal/WebSocketConnection.cs | 3 +-- src/NATS.Client.Core/Internal/netstandard.cs | 18 +++++++++--------- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/NATS.Client.Core/Internal/SslStreamConnection.cs b/src/NATS.Client.Core/Internal/SslStreamConnection.cs index 9effdc08e..1c3df8e0d 100644 --- a/src/NATS.Client.Core/Internal/SslStreamConnection.cs +++ b/src/NATS.Client.Core/Internal/SslStreamConnection.cs @@ -58,8 +58,7 @@ public async ValueTask DisposeAsync() public async ValueTask SendAsync(ReadOnlyMemory buffer) { #if NETSTANDARD2_0 - // ReSharper disable once SuggestVarOrType_Elsewhere - MemoryMarshal.TryGetArray(buffer, out ArraySegment segment); + MemoryMarshal.TryGetArray(buffer, out var segment); await _sslStream.WriteAsync(segment.Array, segment.Offset, segment.Count, _closeCts.Token).ConfigureAwait(false); #else await _sslStream.WriteAsync(buffer, _closeCts.Token).ConfigureAwait(false); diff --git a/src/NATS.Client.Core/Internal/TcpConnection.cs b/src/NATS.Client.Core/Internal/TcpConnection.cs index d2f99181b..e4080e39f 100644 --- a/src/NATS.Client.Core/Internal/TcpConnection.cs +++ b/src/NATS.Client.Core/Internal/TcpConnection.cs @@ -89,8 +89,7 @@ public async ValueTask ConnectAsync(string host, int port, TimeSpan timeout) public ValueTask SendAsync(ReadOnlyMemory buffer) { #if NETSTANDARD2_0 - // ReSharper disable once SuggestVarOrType_Elsewhere - MemoryMarshal.TryGetArray(buffer, out ArraySegment segment); + MemoryMarshal.TryGetArray(buffer, out var segment); return new ValueTask(_socket.SendAsync(segment, SocketFlags.None)); #else return _socket.SendAsync(buffer, SocketFlags.None, CancellationToken.None); diff --git a/src/NATS.Client.Core/Internal/WebSocketConnection.cs b/src/NATS.Client.Core/Internal/WebSocketConnection.cs index 871a5f1bb..799c2fba4 100644 --- a/src/NATS.Client.Core/Internal/WebSocketConnection.cs +++ b/src/NATS.Client.Core/Internal/WebSocketConnection.cs @@ -66,8 +66,7 @@ public async ValueTask SendAsync(ReadOnlyMemory buffer) #if NET6_0_OR_GREATER await _socket.SendAsync(buffer, WebSocketMessageType.Binary, WebSocketMessageFlags.EndOfMessage, CancellationToken.None).ConfigureAwait(false); #else - // ReSharper disable once SuggestVarOrType_Elsewhere - MemoryMarshal.TryGetArray(buffer, out ArraySegment segment); + MemoryMarshal.TryGetArray(buffer, out var segment); await _socket.SendAsync(segment, WebSocketMessageType.Binary, true, CancellationToken.None).ConfigureAwait(false); #endif return buffer.Length; diff --git a/src/NATS.Client.Core/Internal/netstandard.cs b/src/NATS.Client.Core/Internal/netstandard.cs index 92d51ebad..287bdd2db 100644 --- a/src/NATS.Client.Core/Internal/netstandard.cs +++ b/src/NATS.Client.Core/Internal/netstandard.cs @@ -107,9 +107,9 @@ internal long NextInt64(long minValue, long maxValue) if (minValue > maxValue) throw new ArgumentOutOfRangeException(nameof(minValue), "minValue must be less than or equal to maxValue"); - double range = (double)maxValue - (double)minValue + 1; - double sample = NextDouble(); - double scaled = sample * range; + var range = (double)maxValue - (double)minValue + 1; + var sample = NextDouble(); + var scaled = sample * range; return (long)(scaled + minValue); } @@ -171,13 +171,13 @@ internal static class ReadOnlySequenceExtensions // Adapted from .NET 6.0 implementation internal static long GetOffset(this in ReadOnlySequence sequence, SequencePosition position) { - object? positionSequenceObject = position.GetObject(); - bool positionIsNull = positionSequenceObject == null; + var positionSequenceObject = position.GetObject(); + var positionIsNull = positionSequenceObject == null; - object? startObject = sequence.Start.GetObject(); - object? endObject = sequence.End.GetObject(); + var startObject = sequence.Start.GetObject(); + var endObject = sequence.End.GetObject(); - uint positionIndex = (uint)position.GetInteger(); + var positionIndex = (uint)position.GetInteger(); // if a sequence object is null, we suppose start segment if (positionIsNull) @@ -202,7 +202,7 @@ internal static long GetOffset(this in ReadOnlySequence sequence, Sequence throw new ArgumentOutOfRangeException(); // Multi-Segment Sequence - ReadOnlySequenceSegment? currentSegment = (ReadOnlySequenceSegment?)startObject; + var currentSegment = (ReadOnlySequenceSegment?)startObject; while (currentSegment != null && currentSegment != positionSequenceObject) { currentSegment = currentSegment.Next!; From 1560f409155b856978c51649e6970dd35425a2b9 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 09:37:08 +0100 Subject: [PATCH 05/47] dotnet format --- src/NATS.Client.Core/Internal/netstandard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NATS.Client.Core/Internal/netstandard.cs b/src/NATS.Client.Core/Internal/netstandard.cs index 287bdd2db..148763fd4 100644 --- a/src/NATS.Client.Core/Internal/netstandard.cs +++ b/src/NATS.Client.Core/Internal/netstandard.cs @@ -1,4 +1,4 @@ -// ReSharper disable SuggestVarOrType_BuiltInTypes +// ReSharper disable SuggestVarOrType_BuiltInTypes // ReSharper disable ConvertToPrimaryConstructor // ReSharper disable RedundantCast // ReSharper disable SuggestVarOrType_Elsewhere From dca0c0e45c560261245bc5838f615037deec2e39 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 11:32:19 +0100 Subject: [PATCH 06/47] standardize test utils --- src/NATS.Client.Core/NATS.Client.Core.csproj | 2 +- tests/NATS.Client.TestUtilities/MockServer.cs | 5 +++- .../NATS.Client.TestUtilities.csproj | 26 ++++++++++++++++--- tests/NATS.Client.TestUtilities/NatsProxy.cs | 6 ++--- tests/NATS.Client.TestUtilities/NatsServer.cs | 2 +- tests/NATS.Client.TestUtilities/Utils.cs | 16 ++++++++---- tests/NATS.Client.TestUtilities/WaitSignal.cs | 16 +++++++++++- 7 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/NATS.Client.Core/NATS.Client.Core.csproj b/src/NATS.Client.Core/NATS.Client.Core.csproj index 98f8acc4a..9f2e122d9 100644 --- a/src/NATS.Client.Core/NATS.Client.Core.csproj +++ b/src/NATS.Client.Core/NATS.Client.Core.csproj @@ -15,7 +15,7 @@ true - + true diff --git a/tests/NATS.Client.TestUtilities/MockServer.cs b/tests/NATS.Client.TestUtilities/MockServer.cs index 634154295..0589d2b6a 100644 --- a/tests/NATS.Client.TestUtilities/MockServer.cs +++ b/tests/NATS.Client.TestUtilities/MockServer.cs @@ -2,6 +2,9 @@ using System.Net.Sockets; using System.Text; using System.Text.RegularExpressions; +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.TestUtilities; @@ -32,7 +35,7 @@ public MockServer( var n = 0; while (!cancellationToken.IsCancellationRequested) { - var tcpClient = await _server.AcceptTcpClientAsync(cancellationToken); + var tcpClient = await _server.AcceptTcpClientAsync(); var client = new Client(this, tcpClient); n++; Log($"[S] [{n}] New client connected"); diff --git a/tests/NATS.Client.TestUtilities/NATS.Client.TestUtilities.csproj b/tests/NATS.Client.TestUtilities/NATS.Client.TestUtilities.csproj index 4bf81b8e4..c6f460605 100644 --- a/tests/NATS.Client.TestUtilities/NATS.Client.TestUtilities.csproj +++ b/tests/NATS.Client.TestUtilities/NATS.Client.TestUtilities.csproj @@ -1,15 +1,33 @@ - net6.0;net8.0 + + + netstandard2.0;net6.0;net8.0 enable enable false - $(NoWarn);CS8002 + $(NoWarn);CS8002;CS4014;VSTHRD110;VSTHRD002;VSTHRD103;VSTHRD200;VSTHRD111;VSTHRD105 + + + true - + + + + + + + + + + + + + + @@ -26,6 +44,8 @@ + + diff --git a/tests/NATS.Client.TestUtilities/NatsProxy.cs b/tests/NATS.Client.TestUtilities/NatsProxy.cs index ad2c887d9..ed5d44e49 100644 --- a/tests/NATS.Client.TestUtilities/NatsProxy.cs +++ b/tests/NATS.Client.TestUtilities/NatsProxy.cs @@ -259,15 +259,15 @@ void Write(string? rawFrame) { var size = int.Parse(match.Groups[1].Value); var buffer = new char[size + 2]; - var span = buffer.AsSpan(); + var offset = 0; while (true) { - var read = sr.Read(span); + var read = sr.Read(buffer, offset, buffer.Length - offset); if (read == 0) break; if (read == -1) return false; - span = span[read..]; + offset += read; } var bufferDump = Dump(buffer.AsSpan()[..size]); diff --git a/tests/NATS.Client.TestUtilities/NatsServer.cs b/tests/NATS.Client.TestUtilities/NatsServer.cs index 595d5b363..142c475f1 100644 --- a/tests/NATS.Client.TestUtilities/NatsServer.cs +++ b/tests/NATS.Client.TestUtilities/NatsServer.cs @@ -181,7 +181,7 @@ public void StartServerProcess() { try { - await client.ConnectAsync("127.0.0.1", Opts.ServerPort, _cancellationTokenSource.Token); + await client.ConnectAsync("127.0.0.1", Opts.ServerPort); if (client.Connected) return; } diff --git a/tests/NATS.Client.TestUtilities/Utils.cs b/tests/NATS.Client.TestUtilities/Utils.cs index 47024a41e..ad4de27ab 100644 --- a/tests/NATS.Client.TestUtilities/Utils.cs +++ b/tests/NATS.Client.TestUtilities/Utils.cs @@ -71,9 +71,12 @@ public static Task Register(this INatsSub? sub, Action> action) return Task.CompletedTask; return Task.Run(async () => { - await foreach (var natsMsg in sub.Msgs.ReadAllAsync()) + while (await sub.Msgs.WaitToReadAsync(CancellationToken.None).ConfigureAwait(false)) { - action(natsMsg); + while (sub.Msgs.TryRead(out var msg)) + { + action(msg); + } } }); } @@ -84,9 +87,12 @@ public static Task Register(this INatsSub? sub, Func, Task> act return Task.CompletedTask; return Task.Run(async () => { - await foreach (var natsMsg in sub.Msgs.ReadAllAsync()) + while (await sub.Msgs.WaitToReadAsync(CancellationToken.None).ConfigureAwait(false)) { - await action(natsMsg); + while (sub.Msgs.TryRead(out var msg)) + { + action(msg); + } } }); } @@ -144,7 +150,7 @@ await Retry.Until("service is found", async () => } return count == limit; - }); + }).ConfigureAwait(false); var count = 0; await foreach (var msg in nats.RequestManyAsync(subject, "{}", replySerializer: serializer, replyOpts: replyOpts, cancellationToken: ct).ConfigureAwait(false)) diff --git a/tests/NATS.Client.TestUtilities/WaitSignal.cs b/tests/NATS.Client.TestUtilities/WaitSignal.cs index 8a81ed97c..dcb91422f 100644 --- a/tests/NATS.Client.TestUtilities/WaitSignal.cs +++ b/tests/NATS.Client.TestUtilities/WaitSignal.cs @@ -1,4 +1,7 @@ using System.Runtime.CompilerServices; +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Core.Tests; @@ -134,6 +137,17 @@ public void Pulse(T result, Exception? exception = null) public TaskAwaiter GetAwaiter() { - return _tcs.Task.WaitAsync(_timeout).GetAwaiter(); + var timeoutTask = Task.Delay(_timeout); + + return Task.WhenAny(_tcs.Task, timeoutTask).ContinueWith( + completedTask => + { + if (completedTask.Result == timeoutTask) + { + throw new TimeoutException("The operation has timed out."); + } + + return _tcs.Task; + }).Unwrap().GetAwaiter(); } } From c61d0c3564508d809035a8a20f29f0c160e5aa1f Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 13:00:27 +0100 Subject: [PATCH 07/47] standardize JetStream --- .../NATS.Client.Hosting.csproj | 7 ++- .../Internal/NatsJSConsume.cs | 21 +++++-- .../Internal/NatsJSFetch.cs | 9 +++ .../Internal/NatsJSOptsDefaults.cs | 4 ++ .../Internal/NatsJSOrderedConsume.cs | 23 +++++-- .../Internal/NatsJSOrderedPushConsumer.cs | 23 ++++++- .../Internal/ReplyToDateTimeAndSeq.cs | 2 +- .../Internal/netstandard.cs | 40 ++++++++++++ .../NATS.Client.JetStream.csproj | 13 +++- src/NATS.Client.JetStream/NatsJSConsumer.cs | 15 +++-- src/NATS.Client.JetStream/NatsJSContext.cs | 61 +++++++++++-------- src/NATS.Client.JetStream/NatsJSExtensions.cs | 5 ++ src/NATS.Client.JetStream/NatsJSMsg.cs | 11 ++++ .../NatsJSOrderedConsumer.cs | 2 +- src/NATS.Client.JetStream/NatsJSStream.cs | 7 ++- 15 files changed, 191 insertions(+), 52 deletions(-) create mode 100644 src/NATS.Client.JetStream/Internal/netstandard.cs diff --git a/src/NATS.Client.Hosting/NATS.Client.Hosting.csproj b/src/NATS.Client.Hosting/NATS.Client.Hosting.csproj index de2811dbe..4f32e256f 100644 --- a/src/NATS.Client.Hosting/NATS.Client.Hosting.csproj +++ b/src/NATS.Client.Hosting/NATS.Client.Hosting.csproj @@ -1,11 +1,10 @@ - net6.0;net8.0 + netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable true - true pubsub;messaging @@ -13,6 +12,10 @@ true + + true + + diff --git a/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs b/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs index d94c9b88b..d5b23351d 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs @@ -81,7 +81,11 @@ public NatsJSConsume( _thresholdBytes = thresholdBytes; _expires = expires; _idle = idle; +#if NETSTANDARD2_0 + _hbTimeout = (int)new TimeSpan(idle.Ticks * 2).TotalMilliseconds; +#else _hbTimeout = (int)(idle * 2).TotalMilliseconds; +#endif if (_debug) { @@ -169,7 +173,11 @@ public override async ValueTask DisposeAsync() Interlocked.Exchange(ref _disposed, 1); await base.DisposeAsync().ConfigureAwait(false); await _pullTask.ConfigureAwait(false); +#if NETSTANDARD2_0 + _timer.Dispose(); +#else await _timer.DisposeAsync().ConfigureAwait(false); +#endif if (_notificationChannel != null) { await _notificationChannel.DisposeAsync(); @@ -434,13 +442,16 @@ private void Pull(string origin, long batch, long maxBytes) => _pullRequests.Wri private async Task PullLoop() { - await foreach (var pr in _pullRequests.Reader.ReadAllAsync().ConfigureAwait(false)) + if (await _pullRequests.Reader.WaitToReadAsync().ConfigureAwait(false)) { - var origin = $"pull-loop({pr.Origin})"; - await CallMsgNextAsync(origin, pr.Request).ConfigureAwait(false); - if (_debug) + if (_pullRequests.Reader.TryRead(out var pr)) { - _logger.LogDebug(NatsJSLogEvents.PullRequest, "Pull request issued for {Origin} {Batch}, {MaxBytes}", origin, pr.Request.Batch, pr.Request.MaxBytes); + var origin = $"pull-loop({pr.Origin})"; + await CallMsgNextAsync(origin, pr.Request).ConfigureAwait(false); + if (_debug) + { + _logger.LogDebug(NatsJSLogEvents.PullRequest, "Pull request issued for {Origin} {Batch}, {MaxBytes}", origin, pr.Request.Batch, pr.Request.MaxBytes); + } } } } diff --git a/src/NATS.Client.JetStream/Internal/NatsJSFetch.cs b/src/NATS.Client.JetStream/Internal/NatsJSFetch.cs index 751d2f923..1e9bc3897 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSFetch.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSFetch.cs @@ -64,7 +64,11 @@ public NatsJSFetch( _maxBytes = maxBytes; _expires = expires; _idle = idle; +#if NETSTANDARD2_0 + _hbTimeout = (int)new TimeSpan(idle.Ticks * 2).TotalMilliseconds; +#else _hbTimeout = (int)(idle * 2).TotalMilliseconds; +#endif _pendingMsgs = _maxMsgs; _pendingBytes = _maxBytes; @@ -140,8 +144,13 @@ public override async ValueTask DisposeAsync() { Interlocked.Exchange(ref _disposed, 1); await base.DisposeAsync().ConfigureAwait(false); +#if NETSTANDARD2_0 + _hbTimer.Dispose(); + _expiresTimer.Dispose(); +#else await _hbTimer.DisposeAsync().ConfigureAwait(false); await _expiresTimer.DisposeAsync().ConfigureAwait(false); +#endif if (_notificationChannel != null) { await _notificationChannel.DisposeAsync(); diff --git a/src/NATS.Client.JetStream/Internal/NatsJSOptsDefaults.cs b/src/NATS.Client.JetStream/Internal/NatsJSOptsDefaults.cs index 4c6b0d375..505b65d9b 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSOptsDefaults.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSOptsDefaults.cs @@ -69,7 +69,11 @@ internal static (TimeSpan Expires, TimeSpan IdleHeartbeat) SetTimeouts( throw new NatsJSException($"{nameof(expires)} must be greater than {ExpiresMin}"); } +#if NETSTANDARD2_0 + var idleHeartbeatOut = idleHeartbeat ?? new TimeSpan(expiresOut.Ticks / 2); +#else var idleHeartbeatOut = idleHeartbeat ?? expiresOut / 2; +#endif if (idleHeartbeatOut > HeartbeatCap) { diff --git a/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs b/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs index e1a698320..147764bf1 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs @@ -66,7 +66,11 @@ public NatsJSOrderedConsume( _thresholdBytes = thresholdBytes; _expires = expires; _idle = idle; +#if NETSTANDARD2_0 + _hbTimeout = (int)new TimeSpan(idle.Ticks * 2).TotalMilliseconds; +#else _hbTimeout = (int)(idle * 2).TotalMilliseconds; +#endif if (_debug) { @@ -139,13 +143,17 @@ public override async ValueTask DisposeAsync() await base.DisposeAsync().ConfigureAwait(false); await _pullTask.ConfigureAwait(false); +#if NETSTANDARD2_0 + _timer.Dispose(); +#else await _timer.DisposeAsync().ConfigureAwait(false); +#endif } internal override ValueTask WriteReconnectCommandsAsync(CommandWriter commandWriter, int sid) { // Override normal subscription behavior to resubscribe on reconnect - return ValueTask.CompletedTask; + return default; } protected override async ValueTask ReceiveInternalAsync( @@ -387,13 +395,16 @@ private void Pull(string origin, long batch, long maxBytes) => _pullRequests.Wri private async Task PullLoop() { - await foreach (var pr in _pullRequests.Reader.ReadAllAsync().ConfigureAwait(false)) + if (await _pullRequests.Reader.WaitToReadAsync().ConfigureAwait(false)) { - var origin = $"pull-loop({pr.Origin})"; - await CallMsgNextAsync(origin, pr.Request).ConfigureAwait(false); - if (_debug) + if (_pullRequests.Reader.TryRead(out var pr)) { - _logger.LogDebug(NatsJSLogEvents.PullRequest, "Pull request issued for {Origin} {Batch}, {MaxBytes}", origin, pr.Request.Batch, pr.Request.MaxBytes); + var origin = $"pull-loop({pr.Origin})"; + await CallMsgNextAsync(origin, pr.Request).ConfigureAwait(false); + if (_debug) + { + _logger.LogDebug(NatsJSLogEvents.PullRequest, "Pull request issued for {Origin} {Batch}, {MaxBytes}", origin, pr.Request.Batch, pr.Request.MaxBytes); + } } } } diff --git a/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs b/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs index 04c762ffc..be3f41057 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs @@ -61,8 +61,13 @@ internal class NatsJSOrderedPushConsumer private readonly Task _consumerCreateTask; private readonly Task _commandTask; +#if NET6_0_OR_GREATER private ulong _sequenceStream; private ulong _sequenceConsumer; +#else + private long _sequenceStream; + private long _sequenceConsumer; +#endif private string _consumer; private volatile NatsJSOrderedPushConsumerSub? _sub; private int _done; @@ -86,7 +91,11 @@ public NatsJSOrderedPushConsumer( _subOpts = subOpts; _cancellationToken = cancellationToken; _nats = context.Connection; +#if NETSTANDARD2_0 + _hbTimeout = (int)new TimeSpan(opts.IdleHeartbeat.Ticks * 2).TotalMilliseconds; +#else _hbTimeout = (int)(opts.IdleHeartbeat * 2).TotalMilliseconds; +#endif _consumer = NewNuid(); _nats.ConnectionDisconnected += OnDisconnected; @@ -221,7 +230,11 @@ private async Task CommandLoop() var sequence = Interlocked.Increment(ref _sequenceConsumer); +#if NET6_0_OR_GREATER if (sequence != metadata.Sequence.Consumer) +#else + if (sequence != (long)metadata.Sequence.Consumer) +#endif { CreateSub("sequence-mismatch"); _logger.LogWarning(NatsJSLogEvents.RecreateConsumer, "Missed messages, recreating consumer"); @@ -231,7 +244,11 @@ private async Task CommandLoop() // Increment the sequence before writing to the channel in case the channel is full // and the writer is waiting for the reader to read the message. This way the sequence // will be correctly incremented in case the timeout kicks in and recreated the consumer. +#if NET6_0_OR_GREATER Interlocked.Exchange(ref _sequenceStream, metadata.Sequence.Stream); +#else + Interlocked.Exchange(ref _sequenceStream, (long)metadata.Sequence.Stream); +#endif if (!IsDone) { @@ -357,7 +374,7 @@ private async ValueTask CreatePushConsumer(string origin) if (sequence > 0) { config.DeliverPolicy = ConsumerConfigDeliverPolicy.ByStartSequence; - config.OptStartSeq = sequence + 1; + config.OptStartSeq = (ulong)sequence + 1; } await _context.CreateOrUpdateConsumerAsync( @@ -376,7 +393,11 @@ private string NewNuid() Span buffer = stackalloc char[22]; if (NuidWriter.TryWriteNuid(buffer)) { +#if NETSTANDARD2_0 + return new string(buffer.ToArray()); +#else return new string(buffer); +#endif } throw new InvalidOperationException("Internal error: can't generate nuid"); diff --git a/src/NATS.Client.JetStream/Internal/ReplyToDateTimeAndSeq.cs b/src/NATS.Client.JetStream/Internal/ReplyToDateTimeAndSeq.cs index 90e284c63..5da919124 100644 --- a/src/NATS.Client.JetStream/Internal/ReplyToDateTimeAndSeq.cs +++ b/src/NATS.Client.JetStream/Internal/ReplyToDateTimeAndSeq.cs @@ -22,7 +22,7 @@ internal static class ReplyToDateTimeAndSeq return null; } - var originalTokens = reply.Split(".").AsSpan(); + var originalTokens = reply.Split('.').AsSpan(); var tokensLength = originalTokens.Length; // Parsed based on https://github.com/nats-io/nats-architecture-and-design/blob/main/adr/ADR-15.md#jsack diff --git a/src/NATS.Client.JetStream/Internal/netstandard.cs b/src/NATS.Client.JetStream/Internal/netstandard.cs new file mode 100644 index 000000000..f4f7db337 --- /dev/null +++ b/src/NATS.Client.JetStream/Internal/netstandard.cs @@ -0,0 +1,40 @@ +#if NETSTANDARD2_0 || NETSTANDARD2_1 + +#pragma warning disable SA1201 +#pragma warning disable SA1403 + +namespace System.Runtime.CompilerServices +{ + public class ExtensionAttribute : Attribute + { + } + + public sealed class CompilerFeatureRequiredAttribute : Attribute + { + public CompilerFeatureRequiredAttribute(string featureName) + { + FeatureName = featureName; + } + + public string FeatureName { get; } + + public bool IsOptional { get; init; } + + public const string RefStructs = nameof(RefStructs); + + public const string RequiredMembers = nameof(RequiredMembers); + } + + internal sealed class RequiredMemberAttribute : Attribute + { + } +} + +namespace System.Diagnostics.CodeAnalysis +{ + internal sealed class SetsRequiredMembersAttribute : Attribute + { + } +} + +#endif diff --git a/src/NATS.Client.JetStream/NATS.Client.JetStream.csproj b/src/NATS.Client.JetStream/NATS.Client.JetStream.csproj index b1af2b4bb..d44ba1812 100644 --- a/src/NATS.Client.JetStream/NATS.Client.JetStream.csproj +++ b/src/NATS.Client.JetStream/NATS.Client.JetStream.csproj @@ -1,11 +1,11 @@ - net6.0;net8.0 + + netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable true - true pubsub;messaging;persistance @@ -13,6 +13,15 @@ true + + true + + + + + + + diff --git a/src/NATS.Client.JetStream/NatsJSConsumer.cs b/src/NATS.Client.JetStream/NatsJSConsumer.cs index e1c7b400d..ad3b4cb68 100644 --- a/src/NATS.Client.JetStream/NatsJSConsumer.cs +++ b/src/NATS.Client.JetStream/NatsJSConsumer.cs @@ -148,12 +148,7 @@ public async IAsyncEnumerable> ConsumeAsync( serializer, cancellationToken: cancellationToken).ConfigureAwait(false); - await foreach (var natsJSMsg in f.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) - { - return natsJSMsg; - } - - return null; + return await f.Msgs.ReadAsync(cancellationToken).ConfigureAwait(false); } /// @@ -259,9 +254,13 @@ public async IAsyncEnumerable> FetchNoWaitAsync( serializer ??= _context.Connection.Opts.SerializerRegistry.GetDeserializer(); await using var fc = await FetchInternalAsync(opts with { NoWait = true }, serializer, cancellationToken).ConfigureAwait(false); - await foreach (var jsMsg in fc.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) + + if (await fc.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - yield return jsMsg; + if (fc.Msgs.TryRead(out var msg)) + { + yield return msg; + } } } diff --git a/src/NATS.Client.JetStream/NatsJSContext.cs b/src/NATS.Client.JetStream/NatsJSContext.cs index 2097af019..55607de9c 100644 --- a/src/NATS.Client.JetStream/NatsJSContext.cs +++ b/src/NATS.Client.JetStream/NatsJSContext.cs @@ -144,15 +144,13 @@ public async ValueTask PublishAsync( try { - await foreach (var msg in sub.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) + var msg = await sub.Msgs.ReadAsync(cancellationToken).ConfigureAwait(false); + if (msg.Data == null) { - if (msg.Data == null) - { - throw new NatsJSException("No response data received"); - } - - return msg.Data; + throw new NatsJSException("No response data received"); } + + return msg.Data; } catch (NatsNoRespondersException) { @@ -170,17 +168,34 @@ public async ValueTask PublishAsync( throw new NatsJSPublishNoResponseException(); } - internal static void ThrowIfInvalidStreamName([NotNull] string? name, [CallerArgumentExpression("name")] string? paramName = null) + internal static void ThrowIfInvalidStreamName( +#if NETSTANDARD2_1 || NET6_0_OR_GREATER + [NotNull] +#endif + string? name, +#if NET6_0_OR_GREATER + [CallerArgumentExpression("name")] +#endif + string? paramName = null) { +#if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(name, paramName); +#else + if (name == null) + throw new ArgumentException(); +#endif if (name.Length == 0) { ThrowEmptyException(paramName); } +#if NETSTANDARD2_0 + if (name.Contains(" .")) +#else var nameSpan = name.AsSpan(); if (nameSpan.IndexOfAny(" .") >= 0) +#endif { ThrowInvalidStreamNameException(paramName); } @@ -223,39 +238,35 @@ internal async ValueTask> JSRequestAsync(default, jsError.Error); - } - - throw error; - } + var msg = await sub.Msgs.ReadAsync(cancellationToken).ConfigureAwait(false); - if (msg.Data == null) + if (msg.Error is { } error) + { + if (error.InnerException is NatsJSApiErrorException jsError) { - throw new NatsJSException("No response data received"); + return new NatsJSResponse(default, jsError.Error); } - return new NatsJSResponse(msg.Data, default); + throw error; } - if (sub is NatsSubBase { EndReason: NatsSubEndReason.Exception, Exception: not null } sb) + if (msg.Data == null) { - throw sb.Exception; + throw new NatsJSException("No response data received"); } - throw new NatsJSApiNoResponseException(); + return new NatsJSResponse(msg.Data, default); } +#if NETSTANDARD2_1 || NET6_0_OR_GREATER [DoesNotReturn] +#endif private static void ThrowInvalidStreamNameException(string? paramName) => throw new ArgumentException("Stream name cannot contain ' ', '.'", paramName); +#if NETSTANDARD2_1 || NET6_0_OR_GREATER [DoesNotReturn] +#endif private static void ThrowEmptyException(string? paramName) => throw new ArgumentException("The value cannot be an empty string.", paramName); } diff --git a/src/NATS.Client.JetStream/NatsJSExtensions.cs b/src/NATS.Client.JetStream/NatsJSExtensions.cs index 257641dff..d9a4aa4ad 100644 --- a/src/NATS.Client.JetStream/NatsJSExtensions.cs +++ b/src/NATS.Client.JetStream/NatsJSExtensions.cs @@ -31,7 +31,12 @@ public static void EnsureSuccess(this PubAckResponse ack) /// is NULL. public static bool IsSuccess(this PubAckResponse ack) { +#if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(ack); +#else + if (ack == null) + throw new ArgumentNullException(nameof(ack)); +#endif return ack.Error == null && !ack.Duplicate; } } diff --git a/src/NATS.Client.JetStream/NatsJSMsg.cs b/src/NATS.Client.JetStream/NatsJSMsg.cs index e5a0bd151..9c49d4ae4 100644 --- a/src/NATS.Client.JetStream/NatsJSMsg.cs +++ b/src/NATS.Client.JetStream/NatsJSMsg.cs @@ -278,12 +278,21 @@ private async ValueTask SendAckAsync(ReadOnlySequence payload, AckOpts? op if (opts?.DoubleAck ?? _context.Opts.DoubleAck) { +#if NET6_0_OR_GREATER await Connection.RequestAsync, object?>( subject: ReplyTo, data: payload, requestSerializer: NatsRawSerializer>.Default, replySerializer: NatsRawSerializer.Default, cancellationToken: cancellationToken); +#else + await Connection!.RequestAsync, object?>( + subject: ReplyTo!, + data: payload, + requestSerializer: NatsRawSerializer>.Default, + replySerializer: NatsRawSerializer.Default, + cancellationToken: cancellationToken); +#endif } else { @@ -294,8 +303,10 @@ await _msg.ReplyAsync( } } +#if NET6_0_OR_GREATER [MemberNotNull(nameof(Connection))] [MemberNotNull(nameof(ReplyTo))] +#endif private void CheckPreconditions() { if (Connection == default) diff --git a/src/NATS.Client.JetStream/NatsJSOrderedConsumer.cs b/src/NATS.Client.JetStream/NatsJSOrderedConsumer.cs index a52d3a0f5..ce93c77b9 100644 --- a/src/NATS.Client.JetStream/NatsJSOrderedConsumer.cs +++ b/src/NATS.Client.JetStream/NatsJSOrderedConsumer.cs @@ -258,7 +258,7 @@ public async IAsyncEnumerable> FetchNoWaitAsync( /// For ordered consumer this is a no-op. /// /// A used to cancel the API call. - public ValueTask RefreshAsync(CancellationToken cancellationToken = default) => ValueTask.CompletedTask; + public ValueTask RefreshAsync(CancellationToken cancellationToken = default) => default; private async Task RecreateConsumer(string consumer, ulong seq, CancellationToken cancellationToken) { diff --git a/src/NATS.Client.JetStream/NatsJSStream.cs b/src/NATS.Client.JetStream/NatsJSStream.cs index 0e1e6ad13..1d36ada20 100644 --- a/src/NATS.Client.JetStream/NatsJSStream.cs +++ b/src/NATS.Client.JetStream/NatsJSStream.cs @@ -13,12 +13,17 @@ public class NatsJSStream : INatsJSStream private readonly string _name; private bool _deleted; -#if DOT_NET_6 +#if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] #endif internal NatsJSStream(NatsJSContext context, StreamInfo info) { +#if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(info.Config.Name, nameof(info.Config.Name)); +#else + if (info.Config.Name == null) + throw new ArgumentNullException(nameof(info.Config.Name)); +#endif _context = context; Info = info; _name = info.Config.Name!; From 117fe37a9369adba05a7952ff8a4753b1982ef27 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 13:03:18 +0100 Subject: [PATCH 08/47] dotnet format --- src/NATS.Client.JetStream/Internal/netstandard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NATS.Client.JetStream/Internal/netstandard.cs b/src/NATS.Client.JetStream/Internal/netstandard.cs index f4f7db337..791d2078c 100644 --- a/src/NATS.Client.JetStream/Internal/netstandard.cs +++ b/src/NATS.Client.JetStream/Internal/netstandard.cs @@ -1,4 +1,4 @@ -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD2_0 || NETSTANDARD2_1 #pragma warning disable SA1201 #pragma warning disable SA1403 From fcdec629f6975a84a14bce30465624a098e1adf3 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 13:24:04 +0100 Subject: [PATCH 09/47] standardize KV --- .../NatsConnection.RequestReply.cs | 12 ++-- .../Internal/NatsKVWatcher.cs | 14 ++--- .../NATS.Client.KeyValueStore.csproj | 8 ++- src/NATS.Client.KeyValueStore/NatsKVStore.cs | 60 +++++++++++++------ 4 files changed, 62 insertions(+), 32 deletions(-) diff --git a/src/NATS.Client.Core/NatsConnection.RequestReply.cs b/src/NATS.Client.Core/NatsConnection.RequestReply.cs index faff3c5de..e8a474451 100644 --- a/src/NATS.Client.Core/NatsConnection.RequestReply.cs +++ b/src/NATS.Client.Core/NatsConnection.RequestReply.cs @@ -43,9 +43,9 @@ public async ValueTask> RequestAsync( .ConfigureAwait(false); #if NETSTANDARD2_0 - if (await sub1.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + while (await sub1.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - if (sub1.Msgs.TryRead(out var msg)) + while (sub1.Msgs.TryRead(out var msg)) { return msg; } @@ -71,9 +71,9 @@ public async ValueTask> RequestAsync( .ConfigureAwait(false); #if NETSTANDARD2_0 - if (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + while (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - if (sub.Msgs.TryRead(out var msg)) + while (sub.Msgs.TryRead(out var msg)) { return msg; } @@ -121,9 +121,9 @@ public async IAsyncEnumerable> RequestManyAsync : IAsyncDisposable private readonly string _stream; private readonly Task _commandTask; - private ulong _sequenceStream; - private ulong _sequenceConsumer; + private long _sequenceStream; + private long _sequenceConsumer; private string _consumer; private volatile NatsKVWatchSub? _sub; private INatsJSConsumer? _initialConsumer; @@ -81,7 +81,7 @@ public NatsKVWatcher( _cancellationToken = cancellationToken; _nats = context.Connection; _stream = $"KV_{_bucket}"; - _hbTimeout = (int)(opts.IdleHeartbeat * 2).TotalMilliseconds; + _hbTimeout = (int)new TimeSpan(opts.IdleHeartbeat.Ticks * 2).TotalMilliseconds; _consumer = NewNuid(); _nats.ConnectionDisconnected += OnDisconnected; @@ -240,7 +240,7 @@ private async Task CommandLoop() var sequence = Interlocked.Increment(ref _sequenceConsumer); - if (sequence != metadata.Sequence.Consumer) + if (sequence != (long)metadata.Sequence.Consumer) { CreateSub("sequence-mismatch"); _logger.LogWarning(NatsKVLogEvents.RecreateConsumer, "Missed messages, recreating consumer"); @@ -267,7 +267,7 @@ private async Task CommandLoop() // Increment the sequence before writing to the channel in case the channel is full // and the writer is waiting for the reader to read the message. This way the sequence // will be correctly incremented in case the timeout kicks in and recreated the consumer. - Interlocked.Exchange(ref _sequenceStream, metadata.Sequence.Stream); + Interlocked.Exchange(ref _sequenceStream, (long)metadata.Sequence.Stream); await _entryChannel.Writer.WriteAsync(entry, _cancellationToken); } @@ -408,7 +408,7 @@ private async ValueTask CreatePushConsumer(string origin) if (sequence > 0) { config.DeliverPolicy = ConsumerConfigDeliverPolicy.ByStartSequence; - config.OptStartSeq = sequence + 1; + config.OptStartSeq = (ulong)sequence + 1; } else if (_opts.ResumeAtRevision > 0) { @@ -434,7 +434,7 @@ private string NewNuid() Span buffer = stackalloc char[22]; if (NuidWriter.TryWriteNuid(buffer)) { - return new string(buffer); + return new string(buffer.ToArray()); } throw new InvalidOperationException("Internal error: can't generate nuid"); diff --git a/src/NATS.Client.KeyValueStore/NATS.Client.KeyValueStore.csproj b/src/NATS.Client.KeyValueStore/NATS.Client.KeyValueStore.csproj index 47dc56a95..4aa9c9706 100644 --- a/src/NATS.Client.KeyValueStore/NATS.Client.KeyValueStore.csproj +++ b/src/NATS.Client.KeyValueStore/NATS.Client.KeyValueStore.csproj @@ -1,11 +1,11 @@ - net6.0;net8.0 + + netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable true - true pubsub;messaging;persistance;key-value;storage @@ -13,6 +13,10 @@ true + + true + + diff --git a/src/NATS.Client.KeyValueStore/NatsKVStore.cs b/src/NATS.Client.KeyValueStore/NatsKVStore.cs index a28f99f92..faf743151 100644 --- a/src/NATS.Client.KeyValueStore/NatsKVStore.cs +++ b/src/NATS.Client.KeyValueStore/NatsKVStore.cs @@ -263,6 +263,19 @@ public async ValueTask> GetEntryAsync(string key, ulong revisi NatsDeserializeException? deserializeException = null; if (response.Message.Data != null) { +#if NETSTANDARD2_0 + var bytes = Convert.FromBase64String(response.Message.Data); + var buffer = new ReadOnlySequence(bytes); + try + { + data = serializer.Deserialize(buffer); + } + catch (Exception e) + { + deserializeException = new NatsDeserializeException(buffer.ToArray(), e); + data = default; + } +#else var bytes = ArrayPool.Shared.Rent(response.Message.Data.Length); try { @@ -289,6 +302,7 @@ public async ValueTask> GetEntryAsync(string key, ulong revisi { ArrayPool.Shared.Return(bytes); } +#endif } else { @@ -323,9 +337,12 @@ public async IAsyncEnumerable> WatchAsync(IEnumerable } } - await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken).ConfigureAwait(false)) + while (await watcher.Entries.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - yield return entry; + while (watcher.Entries.TryRead(out var entry)) + { + yield return entry; + } } } @@ -349,11 +366,14 @@ public async IAsyncEnumerable> HistoryAsync(string key, INatsD await using var watcher = await WatchInternalAsync([key], serializer, opts, cancellationToken); - await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken).ConfigureAwait(false)) + while (await watcher.Entries.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - yield return entry; - if (entry.Delta == 0) - yield break; + while (watcher.Entries.TryRead(out var entry)) + { + yield return entry; + if (entry.Delta == 0) + yield break; + } } } @@ -386,12 +406,15 @@ public async ValueTask PurgeDeletesAsync(NatsKVPurgeOpts? opts = default, Cancel if (watcher.InitialConsumer.Info.NumPending == 0) return; - await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken).ConfigureAwait(false)) + while (await watcher.Entries.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - if (entry.Operation is NatsKVOperation.Purge or NatsKVOperation.Del) - deleted.Add(entry); - if (entry.Delta == 0) - goto PURGE_LOOP_DONE; + while (watcher.Entries.TryRead(out var entry)) + { + if (entry.Operation is NatsKVOperation.Purge or NatsKVOperation.Del) + deleted.Add(entry); + if (entry.Delta == 0) + goto PURGE_LOOP_DONE; + } } } @@ -427,12 +450,15 @@ public async IAsyncEnumerable GetKeysAsync(NatsKVWatchOpts? opts = defau if (watcher.InitialConsumer.Info.NumPending == 0) yield break; - await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken).ConfigureAwait(false)) + while (await watcher.Entries.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - if (entry.Operation is NatsKVOperation.Put) - yield return entry.Key; - if (entry.Delta == 0) - yield break; + while (watcher.Entries.TryRead(out var entry)) + { + if (entry.Operation is NatsKVOperation.Put) + yield return entry.Key; + if (entry.Delta == 0) + yield break; + } } } @@ -467,7 +493,7 @@ private static void ValidateKey(string key) ThrowNatsKVException("Key cannot be empty"); } - if (key.StartsWith('.') || key.EndsWith('.')) + if (key.StartsWith(".") || key.EndsWith(".")) { ThrowNatsKVException("Key cannot start or end with a period"); } From 4c8cae6778591c0c4b6006bcbbc59ede31613c23 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 13:47:28 +0100 Subject: [PATCH 10/47] standardize JetStream tweaks --- .../Internal/NatsJSConsume.cs | 8 ++------ .../Internal/NatsJSFetch.cs | 4 ---- .../Internal/NatsJSOptsDefaults.cs | 4 ---- .../Internal/NatsJSOrderedConsume.cs | 8 ++------ .../Internal/NatsJSOrderedPushConsumer.cs | 17 ----------------- src/NATS.Client.JetStream/NatsJSConsumer.cs | 4 ++-- .../Internal/NatsKVWatcher.cs | 4 ++++ 7 files changed, 10 insertions(+), 39 deletions(-) diff --git a/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs b/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs index d5b23351d..719c76529 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs @@ -81,11 +81,7 @@ public NatsJSConsume( _thresholdBytes = thresholdBytes; _expires = expires; _idle = idle; -#if NETSTANDARD2_0 _hbTimeout = (int)new TimeSpan(idle.Ticks * 2).TotalMilliseconds; -#else - _hbTimeout = (int)(idle * 2).TotalMilliseconds; -#endif if (_debug) { @@ -442,9 +438,9 @@ private void Pull(string origin, long batch, long maxBytes) => _pullRequests.Wri private async Task PullLoop() { - if (await _pullRequests.Reader.WaitToReadAsync().ConfigureAwait(false)) + while (await _pullRequests.Reader.WaitToReadAsync().ConfigureAwait(false)) { - if (_pullRequests.Reader.TryRead(out var pr)) + while (_pullRequests.Reader.TryRead(out var pr)) { var origin = $"pull-loop({pr.Origin})"; await CallMsgNextAsync(origin, pr.Request).ConfigureAwait(false); diff --git a/src/NATS.Client.JetStream/Internal/NatsJSFetch.cs b/src/NATS.Client.JetStream/Internal/NatsJSFetch.cs index 1e9bc3897..b873a8092 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSFetch.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSFetch.cs @@ -64,11 +64,7 @@ public NatsJSFetch( _maxBytes = maxBytes; _expires = expires; _idle = idle; -#if NETSTANDARD2_0 _hbTimeout = (int)new TimeSpan(idle.Ticks * 2).TotalMilliseconds; -#else - _hbTimeout = (int)(idle * 2).TotalMilliseconds; -#endif _pendingMsgs = _maxMsgs; _pendingBytes = _maxBytes; diff --git a/src/NATS.Client.JetStream/Internal/NatsJSOptsDefaults.cs b/src/NATS.Client.JetStream/Internal/NatsJSOptsDefaults.cs index 505b65d9b..da6da792a 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSOptsDefaults.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSOptsDefaults.cs @@ -69,11 +69,7 @@ internal static (TimeSpan Expires, TimeSpan IdleHeartbeat) SetTimeouts( throw new NatsJSException($"{nameof(expires)} must be greater than {ExpiresMin}"); } -#if NETSTANDARD2_0 var idleHeartbeatOut = idleHeartbeat ?? new TimeSpan(expiresOut.Ticks / 2); -#else - var idleHeartbeatOut = idleHeartbeat ?? expiresOut / 2; -#endif if (idleHeartbeatOut > HeartbeatCap) { diff --git a/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs b/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs index 147764bf1..8c5c5cbd9 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs @@ -66,11 +66,7 @@ public NatsJSOrderedConsume( _thresholdBytes = thresholdBytes; _expires = expires; _idle = idle; -#if NETSTANDARD2_0 _hbTimeout = (int)new TimeSpan(idle.Ticks * 2).TotalMilliseconds; -#else - _hbTimeout = (int)(idle * 2).TotalMilliseconds; -#endif if (_debug) { @@ -395,9 +391,9 @@ private void Pull(string origin, long batch, long maxBytes) => _pullRequests.Wri private async Task PullLoop() { - if (await _pullRequests.Reader.WaitToReadAsync().ConfigureAwait(false)) + while (await _pullRequests.Reader.WaitToReadAsync().ConfigureAwait(false)) { - if (_pullRequests.Reader.TryRead(out var pr)) + while (_pullRequests.Reader.TryRead(out var pr)) { var origin = $"pull-loop({pr.Origin})"; await CallMsgNextAsync(origin, pr.Request).ConfigureAwait(false); diff --git a/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs b/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs index be3f41057..18676fee4 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs @@ -61,13 +61,8 @@ internal class NatsJSOrderedPushConsumer private readonly Task _consumerCreateTask; private readonly Task _commandTask; -#if NET6_0_OR_GREATER - private ulong _sequenceStream; - private ulong _sequenceConsumer; -#else private long _sequenceStream; private long _sequenceConsumer; -#endif private string _consumer; private volatile NatsJSOrderedPushConsumerSub? _sub; private int _done; @@ -91,11 +86,7 @@ public NatsJSOrderedPushConsumer( _subOpts = subOpts; _cancellationToken = cancellationToken; _nats = context.Connection; -#if NETSTANDARD2_0 _hbTimeout = (int)new TimeSpan(opts.IdleHeartbeat.Ticks * 2).TotalMilliseconds; -#else - _hbTimeout = (int)(opts.IdleHeartbeat * 2).TotalMilliseconds; -#endif _consumer = NewNuid(); _nats.ConnectionDisconnected += OnDisconnected; @@ -230,11 +221,7 @@ private async Task CommandLoop() var sequence = Interlocked.Increment(ref _sequenceConsumer); -#if NET6_0_OR_GREATER - if (sequence != metadata.Sequence.Consumer) -#else if (sequence != (long)metadata.Sequence.Consumer) -#endif { CreateSub("sequence-mismatch"); _logger.LogWarning(NatsJSLogEvents.RecreateConsumer, "Missed messages, recreating consumer"); @@ -244,11 +231,7 @@ private async Task CommandLoop() // Increment the sequence before writing to the channel in case the channel is full // and the writer is waiting for the reader to read the message. This way the sequence // will be correctly incremented in case the timeout kicks in and recreated the consumer. -#if NET6_0_OR_GREATER - Interlocked.Exchange(ref _sequenceStream, metadata.Sequence.Stream); -#else Interlocked.Exchange(ref _sequenceStream, (long)metadata.Sequence.Stream); -#endif if (!IsDone) { diff --git a/src/NATS.Client.JetStream/NatsJSConsumer.cs b/src/NATS.Client.JetStream/NatsJSConsumer.cs index ad3b4cb68..2cebb5139 100644 --- a/src/NATS.Client.JetStream/NatsJSConsumer.cs +++ b/src/NATS.Client.JetStream/NatsJSConsumer.cs @@ -255,9 +255,9 @@ public async IAsyncEnumerable> FetchNoWaitAsync( await using var fc = await FetchInternalAsync(opts with { NoWait = true }, serializer, cancellationToken).ConfigureAwait(false); - if (await fc.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + while (await fc.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - if (fc.Msgs.TryRead(out var msg)) + while (fc.Msgs.TryRead(out var msg)) { yield return msg; } diff --git a/src/NATS.Client.KeyValueStore/Internal/NatsKVWatcher.cs b/src/NATS.Client.KeyValueStore/Internal/NatsKVWatcher.cs index e384d0835..db79cfd19 100644 --- a/src/NATS.Client.KeyValueStore/Internal/NatsKVWatcher.cs +++ b/src/NATS.Client.KeyValueStore/Internal/NatsKVWatcher.cs @@ -434,7 +434,11 @@ private string NewNuid() Span buffer = stackalloc char[22]; if (NuidWriter.TryWriteNuid(buffer)) { +#if NETSTANDARD2_0 return new string(buffer.ToArray()); +#else + return new string(buffer); +#endif } throw new InvalidOperationException("Internal error: can't generate nuid"); From c50125954004f5620d16cfa1264c2690f689b4b3 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 14:17:36 +0100 Subject: [PATCH 11/47] standardize object store --- .../Internal/Encoder.cs | 5 + .../NATS.Client.ObjectStore.csproj | 8 +- src/NATS.Client.ObjectStore/NatsObjStore.cs | 101 +++++++++++------- 3 files changed, 76 insertions(+), 38 deletions(-) diff --git a/src/NATS.Client.ObjectStore/Internal/Encoder.cs b/src/NATS.Client.ObjectStore/Internal/Encoder.cs index 6cf59f6f6..3dd04658a 100644 --- a/src/NATS.Client.ObjectStore/Internal/Encoder.cs +++ b/src/NATS.Client.ObjectStore/Internal/Encoder.cs @@ -31,7 +31,12 @@ public static string Sha256(ReadOnlySpan value) Span destination = stackalloc byte[256 / 8]; using (var sha256 = SHA256.Create()) { +#if NETSTANDARD2_0 + var hash = sha256.ComputeHash(value.ToArray()); + hash.AsSpan().CopyTo(destination); +#else sha256.TryComputeHash(value, destination, out _); +#endif } return Encode(destination); diff --git a/src/NATS.Client.ObjectStore/NATS.Client.ObjectStore.csproj b/src/NATS.Client.ObjectStore/NATS.Client.ObjectStore.csproj index 3107e47b5..9f9f220d2 100644 --- a/src/NATS.Client.ObjectStore/NATS.Client.ObjectStore.csproj +++ b/src/NATS.Client.ObjectStore/NATS.Client.ObjectStore.csproj @@ -1,11 +1,11 @@ - net6.0;net8.0 + + netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable true - true pubsub;messaging;persistance;storage @@ -13,6 +13,10 @@ true + + true + + diff --git a/src/NATS.Client.ObjectStore/NatsObjStore.cs b/src/NATS.Client.ObjectStore/NatsObjStore.cs index 6059d7885..24a9be87d 100644 --- a/src/NATS.Client.ObjectStore/NatsObjStore.cs +++ b/src/NATS.Client.ObjectStore/NatsObjStore.cs @@ -1,5 +1,6 @@ using System.Buffers; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text.Json; using NATS.Client.Core; @@ -95,31 +96,43 @@ public async ValueTask GetAsync(string key, Stream stream, bool var size = 0; using (var sha256 = SHA256.Create()) { +#if NETSTANDARD2_0 + using (var hashedStream = new CryptoStream(stream, sha256, CryptoStreamMode.Write)) +#else await using (var hashedStream = new CryptoStream(stream, sha256, CryptoStreamMode.Write, leaveOpen)) +#endif { - await foreach (var msg in pushConsumer.Msgs.ReadAllAsync(cancellationToken)) + while (await pushConsumer.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - // We have to make sure to carry on consuming the channel to avoid any blocking: - // e.g. if the channel is full, we would be blocking the reads off the socket (this was intentionally - // done ot avoid bloating the memory with a large backlog of messages or dropping messages at this level - // and signal the server that we are a slow consumer); then when we make an request-reply API call to - // delete the consumer, the socket would be blocked trying to send the response back to us; so we need to - // keep consuming the channel to avoid this. - if (pushConsumer.IsDone) - continue; - - if (msg.Data.Length > 0) + while (pushConsumer.Msgs.TryRead(out var msg)) { - using var memoryOwner = msg.Data; - chunks++; - size += memoryOwner.Memory.Length; - await hashedStream.WriteAsync(memoryOwner.Memory, cancellationToken); - } + // We have to make sure to carry on consuming the channel to avoid any blocking: + // e.g. if the channel is full, we would be blocking the reads off the socket (this was intentionally + // done ot avoid bloating the memory with a large backlog of messages or dropping messages at this level + // and signal the server that we are a slow consumer); then when we make an request-reply API call to + // delete the consumer, the socket would be blocked trying to send the response back to us; so we need to + // keep consuming the channel to avoid this. + if (pushConsumer.IsDone) + continue; + + if (msg.Data.Length > 0) + { + using var memoryOwner = msg.Data; + chunks++; + size += memoryOwner.Memory.Length; +#if NETSTANDARD2_0 + var segment = memoryOwner.DangerousGetArray(); + await hashedStream.WriteAsync(segment.Array, segment.Offset, segment.Count, cancellationToken); +#else + await hashedStream.WriteAsync(memoryOwner.Memory, cancellationToken); +#endif + } - var p = msg.Metadata?.NumPending; - if (p is 0) - { - pushConsumer.Done(); + var p = msg.Metadata?.NumPending; + if (p is 0) + { + pushConsumer.Done(); + } } } } @@ -213,7 +226,11 @@ public async ValueTask PutAsync(ObjectMetadata meta, Stream stre string digest; using (var sha256 = SHA256.Create()) { +#if NETSTANDARD2_0 + using (var hashedStream = new CryptoStream(stream, sha256, CryptoStreamMode.Read)) +#else await using (var hashedStream = new CryptoStream(stream, sha256, CryptoStreamMode.Read, leaveOpen)) +#endif { while (true) { @@ -226,7 +243,12 @@ public async ValueTask PutAsync(ObjectMetadata meta, Stream stre // Fill a chunk while (true) { +#if NETSTANDARD2_0 + MemoryMarshal.TryGetArray((ReadOnlyMemory)memory, out var segment); + var read = await hashedStream.ReadAsync(segment.Array!, segment.Offset, segment.Count, cancellationToken); +#else var read = await hashedStream.ReadAsync(memory, cancellationToken); +#endif // End of stream if (read == 0) @@ -583,28 +605,31 @@ public async IAsyncEnumerable WatchAsync(NatsObjWatchOpts? opts pushConsumer.Init(); - await foreach (var msg in pushConsumer.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) + while (await pushConsumer.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - if (pushConsumer.IsDone) - continue; - using (msg.Data) + while (pushConsumer.Msgs.TryRead(out var msg)) { - if (msg.Metadata is { } metadata) + if (pushConsumer.IsDone) + continue; + using (msg.Data) { - var info = JsonSerializer.Deserialize(msg.Data.Memory.Span, NatsObjJsonSerializerContext.Default.ObjectMetadata); - if (info != null) + if (msg.Metadata is { } metadata) { - if (!opts.IgnoreDeletes || !info.Deleted) + var info = JsonSerializer.Deserialize(msg.Data.Memory.Span, NatsObjJsonSerializerContext.Default.ObjectMetadata); + if (info != null) { - info.MTime = metadata.Timestamp; - yield return info; + if (!opts.IgnoreDeletes || !info.Deleted) + { + info.MTime = metadata.Timestamp; + yield return info; + } } - } - if (opts.InitialSetOnly) - { - if (metadata.NumPending == 0) - break; + if (opts.InitialSetOnly) + { + if (metadata.NumPending == 0) + break; + } } } } @@ -636,7 +661,7 @@ public async ValueTask DeleteAsync(string key, CancellationToken cancellationTok await PublishMeta(meta, cancellationToken); - var response = await _stream.PurgeAsync(new StreamPurgeRequest { Filter = GetChunkSubject(meta.Nuid) }, cancellationToken); + var response = await _stream.PurgeAsync(new StreamPurgeRequest { Filter = GetChunkSubject(meta.Nuid!) }, cancellationToken); if (!response.Success) { throw new NatsObjException("Can't purge object chunks"); @@ -658,7 +683,11 @@ private string NewNuid() Span buffer = stackalloc char[22]; if (NuidWriter.TryWriteNuid(buffer)) { +#if NETSTANDARD2_0 + return new string(buffer.ToArray()); +#else return new string(buffer); +#endif } throw new InvalidOperationException("Internal error: can't generate nuid"); From 76c3bc739abddb7acf3fb491a629c98fb386949c Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 14:30:52 +0100 Subject: [PATCH 12/47] standardize services --- .../NATS.Client.Serializers.Json.csproj | 3 +- .../Internal/SvcListener.cs | 7 +- .../NATS.Client.Services.csproj | 8 +- src/NATS.Client.Services/NatsSvcEndPoint.cs | 77 +++++++++-------- src/NATS.Client.Services/NatsSvcServer.cs | 86 +++++++++---------- 5 files changed, 96 insertions(+), 85 deletions(-) diff --git a/src/NATS.Client.Serializers.Json/NATS.Client.Serializers.Json.csproj b/src/NATS.Client.Serializers.Json/NATS.Client.Serializers.Json.csproj index 3f5150ca1..c6b422035 100644 --- a/src/NATS.Client.Serializers.Json/NATS.Client.Serializers.Json.csproj +++ b/src/NATS.Client.Serializers.Json/NATS.Client.Serializers.Json.csproj @@ -1,7 +1,8 @@ - net6.0;net8.0 + + netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable true diff --git a/src/NATS.Client.Services/Internal/SvcListener.cs b/src/NATS.Client.Services/Internal/SvcListener.cs index 88fdd234d..be8b3371f 100644 --- a/src/NATS.Client.Services/Internal/SvcListener.cs +++ b/src/NATS.Client.Services/Internal/SvcListener.cs @@ -30,9 +30,12 @@ public async ValueTask StartAsync() { await using (sub) { - await foreach (var msg in sub.Msgs.ReadAllAsync()) + while (await sub.Msgs.WaitToReadAsync().ConfigureAwait(false)) { - await _channel.Writer.WriteAsync(new SvcMsg(_type, msg), _cts.Token).ConfigureAwait(false); + while (sub.Msgs.TryRead(out var msg)) + { + await _channel.Writer.WriteAsync(new SvcMsg(_type, msg), _cts.Token).ConfigureAwait(false); + } } } }); diff --git a/src/NATS.Client.Services/NATS.Client.Services.csproj b/src/NATS.Client.Services/NATS.Client.Services.csproj index 447804156..b2f8c78d4 100644 --- a/src/NATS.Client.Services/NATS.Client.Services.csproj +++ b/src/NATS.Client.Services/NATS.Client.Services.csproj @@ -1,11 +1,11 @@ - net6.0;net8.0 + + netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable true - true pubsub;messaging;microservices;services @@ -13,6 +13,10 @@ true + + true + + diff --git a/src/NATS.Client.Services/NatsSvcEndPoint.cs b/src/NATS.Client.Services/NatsSvcEndPoint.cs index b13a722ff..8b087ed3c 100644 --- a/src/NATS.Client.Services/NatsSvcEndPoint.cs +++ b/src/NATS.Client.Services/NatsSvcEndPoint.cs @@ -212,57 +212,60 @@ protected override ValueTask ReceiveInternalAsync( private async Task HandlerLoop() { var stopwatch = new Stopwatch(); - await foreach (var svcMsg in _channel.Reader.ReadAllAsync(_cancellationToken).ConfigureAwait(false)) + while (await _channel.Reader.WaitToReadAsync(_cancellationToken).ConfigureAwait(false)) { - Interlocked.Increment(ref _requests); - stopwatch.Restart(); - try + while (_channel.Reader.TryRead(out var svcMsg)) { - await _handler(svcMsg).ConfigureAwait(false); - } - catch (Exception e) - { - int code; - string message; - string body; - if (e is NatsSvcEndpointException epe) - { - code = epe.Code; - message = epe.Message; - body = epe.Body; - } - else + Interlocked.Increment(ref _requests); + stopwatch.Restart(); + try { - // Do not expose exceptions unless explicitly - // thrown as NatsSvcEndpointException - code = 999; - message = "Handler error"; - body = string.Empty; - - // Only log unknown exceptions - _logger.LogError(NatsSvcLogEvents.Endpoint, e, "Endpoint {Name} error processing message", Name); + await _handler(svcMsg).ConfigureAwait(false); } - - try + catch (Exception e) { - if (string.IsNullOrWhiteSpace(body)) + int code; + string message; + string body; + if (e is NatsSvcEndpointException epe) { - await svcMsg.ReplyErrorAsync(code, message, cancellationToken: _cancellationToken); + code = epe.Code; + message = epe.Message; + body = epe.Body; } else { - await svcMsg.ReplyErrorAsync(code, message, data: Encoding.UTF8.GetBytes(body), cancellationToken: _cancellationToken); + // Do not expose exceptions unless explicitly + // thrown as NatsSvcEndpointException + code = 999; + message = "Handler error"; + body = string.Empty; + + // Only log unknown exceptions + _logger.LogError(NatsSvcLogEvents.Endpoint, e, "Endpoint {Name} error processing message", Name); + } + + try + { + if (string.IsNullOrWhiteSpace(body)) + { + await svcMsg.ReplyErrorAsync(code, message, cancellationToken: _cancellationToken); + } + else + { + await svcMsg.ReplyErrorAsync(code, message, data: Encoding.UTF8.GetBytes(body), cancellationToken: _cancellationToken); + } + } + catch (Exception e1) + { + _logger.LogError(NatsSvcLogEvents.Endpoint, e1, "Endpoint {Name} error responding", Name); } } - catch (Exception e1) + finally { - _logger.LogError(NatsSvcLogEvents.Endpoint, e1, "Endpoint {Name} error responding", Name); + Interlocked.Add(ref _processingTime, ToNanos(stopwatch.Elapsed)); } } - finally - { - Interlocked.Add(ref _processingTime, ToNanos(stopwatch.Elapsed)); - } } } diff --git a/src/NATS.Client.Services/NatsSvcServer.cs b/src/NATS.Client.Services/NatsSvcServer.cs index 43dea5c10..54edab07f 100644 --- a/src/NATS.Client.Services/NatsSvcServer.cs +++ b/src/NATS.Client.Services/NatsSvcServer.cs @@ -111,7 +111,7 @@ public ValueTask AddEndpointAsync(Func, ValueTask> handler, str public ValueTask AddGroupAsync(string name, string? queueGroup = default, CancellationToken cancellationToken = default) { var group = new Group(this, name, queueGroup, cancellationToken); - return ValueTask.FromResult(group); + return new ValueTask(group); } /// @@ -227,60 +227,60 @@ private async ValueTask AddEndpointInternalAsync(Func, ValueTas private async Task MsgLoop() { - await foreach (var svcMsg in _channel.Reader.ReadAllAsync(_cts.Token)) + while (await _channel.Reader.WaitToReadAsync(_cts.Token).ConfigureAwait(false)) { - try + while (_channel.Reader.TryRead(out var svcMsg)) { - var type = svcMsg.MsgType; - var data = svcMsg.Msg.Data; - - if (type == SvcMsgType.Ping) + try { - using (data) - { - // empty request payload - } + var type = svcMsg.MsgType; + var data = svcMsg.Msg.Data; - await svcMsg.Msg.ReplyAsync( - data: new PingResponse + if (type == SvcMsgType.Ping) + { + using (data) { - Name = _config.Name, - Id = _id, - Version = _config.Version, - Metadata = _config.Metadata!, - }, - serializer: NatsSrvJsonSerializer.Default, - cancellationToken: _cts.Token); - } - else if (type == SvcMsgType.Info) - { - using (data) + // empty request payload + } + + await svcMsg.Msg.ReplyAsync( + data: new PingResponse + { + Name = _config.Name, Id = _id, Version = _config.Version, Metadata = _config.Metadata!, + }, + serializer: NatsSrvJsonSerializer.Default, + cancellationToken: _cts.Token); + } + else if (type == SvcMsgType.Info) { - // empty request payload + using (data) + { + // empty request payload + } + + await svcMsg.Msg.ReplyAsync( + data: GetInfo(), + serializer: NatsSrvJsonSerializer.Default, + cancellationToken: _cts.Token); } + else if (type == SvcMsgType.Stats) + { + using (data) + { + // empty request payload + } - await svcMsg.Msg.ReplyAsync( - data: GetInfo(), - serializer: NatsSrvJsonSerializer.Default, - cancellationToken: _cts.Token); + await svcMsg.Msg.ReplyAsync( + data: GetStats(), + serializer: NatsSrvJsonSerializer.Default, + cancellationToken: _cts.Token); + } } - else if (type == SvcMsgType.Stats) + catch (Exception ex) { - using (data) - { - // empty request payload - } - - await svcMsg.Msg.ReplyAsync( - data: GetStats(), - serializer: NatsSrvJsonSerializer.Default, - cancellationToken: _cts.Token); + _logger.LogError(ex, "Message loop error"); } } - catch (Exception ex) - { - _logger.LogError(ex, "Message loop error"); - } } } From 42e82555fbd83266373c9ce263a52646ca869085 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 14:34:47 +0100 Subject: [PATCH 13/47] standardize extensions --- src/NATS.Client.JetStream/Internal/netstandard.cs | 4 ++-- .../NATS.Extensions.Microsoft.DependencyInjection.csproj | 8 ++++++-- src/NATS.Net/NATS.Net.csproj | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/NATS.Client.JetStream/Internal/netstandard.cs b/src/NATS.Client.JetStream/Internal/netstandard.cs index 791d2078c..a3b99ff12 100644 --- a/src/NATS.Client.JetStream/Internal/netstandard.cs +++ b/src/NATS.Client.JetStream/Internal/netstandard.cs @@ -5,11 +5,11 @@ namespace System.Runtime.CompilerServices { - public class ExtensionAttribute : Attribute + internal class ExtensionAttribute : Attribute { } - public sealed class CompilerFeatureRequiredAttribute : Attribute + internal sealed class CompilerFeatureRequiredAttribute : Attribute { public CompilerFeatureRequiredAttribute(string featureName) { diff --git a/src/NATS.Extensions.Microsoft.DependencyInjection/NATS.Extensions.Microsoft.DependencyInjection.csproj b/src/NATS.Extensions.Microsoft.DependencyInjection/NATS.Extensions.Microsoft.DependencyInjection.csproj index e645c3bfd..bf9111209 100644 --- a/src/NATS.Extensions.Microsoft.DependencyInjection/NATS.Extensions.Microsoft.DependencyInjection.csproj +++ b/src/NATS.Extensions.Microsoft.DependencyInjection/NATS.Extensions.Microsoft.DependencyInjection.csproj @@ -1,10 +1,10 @@  - net6.0;net8.0 + + netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable - true pubsub;messaging @@ -12,6 +12,10 @@ true + + true + + diff --git a/src/NATS.Net/NATS.Net.csproj b/src/NATS.Net/NATS.Net.csproj index ae82f3a5a..1c5ca9148 100644 --- a/src/NATS.Net/NATS.Net.csproj +++ b/src/NATS.Net/NATS.Net.csproj @@ -1,7 +1,8 @@ - net6.0;net8.0 + + netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable true From 91ffcda99d11476d564cabfd925961fe42ea2386 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 14:47:22 +0100 Subject: [PATCH 14/47] fixed code for failing tests --- src/NATS.Client.JetStream/NatsJSConsumer.cs | 10 +++++++++- src/NATS.Client.JetStream/NatsJSContext.cs | 15 ++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/NATS.Client.JetStream/NatsJSConsumer.cs b/src/NATS.Client.JetStream/NatsJSConsumer.cs index 2cebb5139..8de5e708e 100644 --- a/src/NATS.Client.JetStream/NatsJSConsumer.cs +++ b/src/NATS.Client.JetStream/NatsJSConsumer.cs @@ -148,7 +148,15 @@ public async IAsyncEnumerable> ConsumeAsync( serializer, cancellationToken: cancellationToken).ConfigureAwait(false); - return await f.Msgs.ReadAsync(cancellationToken).ConfigureAwait(false); + while (await f.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + while (f.Msgs.TryRead(out var msg)) + { + return msg; + } + } + + return null; } /// diff --git a/src/NATS.Client.JetStream/NatsJSContext.cs b/src/NATS.Client.JetStream/NatsJSContext.cs index 55607de9c..54761b1b6 100644 --- a/src/NATS.Client.JetStream/NatsJSContext.cs +++ b/src/NATS.Client.JetStream/NatsJSContext.cs @@ -144,13 +144,18 @@ public async ValueTask PublishAsync( try { - var msg = await sub.Msgs.ReadAsync(cancellationToken).ConfigureAwait(false); - if (msg.Data == null) + while (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - throw new NatsJSException("No response data received"); - } + while (sub.Msgs.TryRead(out var msg)) + { + if (msg.Data == null) + { + throw new NatsJSException("No response data received"); + } - return msg.Data; + return msg.Data; + } + } } catch (NatsNoRespondersException) { From 2013c0a367067b3d20531e06d78c81750af4fec5 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 14:54:12 +0100 Subject: [PATCH 15/47] dotnet format --- src/NATS.Client.Services/NatsSvcServer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/NATS.Client.Services/NatsSvcServer.cs b/src/NATS.Client.Services/NatsSvcServer.cs index 54edab07f..ee7718741 100644 --- a/src/NATS.Client.Services/NatsSvcServer.cs +++ b/src/NATS.Client.Services/NatsSvcServer.cs @@ -246,7 +246,10 @@ private async Task MsgLoop() await svcMsg.Msg.ReplyAsync( data: new PingResponse { - Name = _config.Name, Id = _id, Version = _config.Version, Metadata = _config.Metadata!, + Name = _config.Name, + Id = _id, + Version = _config.Version, + Metadata = _config.Metadata!, }, serializer: NatsSrvJsonSerializer.Default, cancellationToken: _cts.Token); From 99c05c81497c988cc1604a16a02545f3a4ac8934 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 15:56:01 +0100 Subject: [PATCH 16/47] framework core tests --- tests/NATS.Client.Core.Tests/AuthErrorTest.cs | 12 ++--- .../ConnectionRetryTest.cs | 30 ++++++----- .../NATS.Client.Core.Tests/LowLevelApiTest.cs | 2 +- .../NATS.Client.Core.Tests.csproj | 9 +++- tests/NATS.Client.Core.Tests/NKeyTests.cs | 13 +++-- .../NatsConnectionTest.QueueGroups.cs | 50 ++++++++++-------- .../NATS.Client.Core.Tests/NatsHeaderTest.cs | 4 +- .../NATS.Client.Core.Tests/NuidWriterTests.cs | 12 +++-- tests/NATS.Client.Core.Tests/ProtocolTest.cs | 20 ++++--- .../RequestReplyTest.cs | 36 +++++++++---- .../NATS.Client.Core.Tests/SendBufferTest.cs | 3 ++ .../NATS.Client.Core.Tests/SerializerTest.cs | 4 +- .../SubscriptionTest.cs | 52 +++++++++++++------ tests/NATS.Client.Core.Tests/TlsOptsTest.cs | 4 ++ 14 files changed, 157 insertions(+), 94 deletions(-) diff --git a/tests/NATS.Client.Core.Tests/AuthErrorTest.cs b/tests/NATS.Client.Core.Tests/AuthErrorTest.cs index bc2e38030..69b4484c4 100644 --- a/tests/NATS.Client.Core.Tests/AuthErrorTest.cs +++ b/tests/NATS.Client.Core.Tests/AuthErrorTest.cs @@ -65,9 +65,9 @@ public async Task Auth_err_twice_will_stop_retries() // Reload config with different password { - var conf = (await File.ReadAllTextAsync(server.ConfigFile!, cts.Token)) + var conf = File.ReadAllText(server.ConfigFile!) .Replace("password: b", "password: c"); - await File.WriteAllTextAsync(server.ConfigFile!, conf, cts.Token); + File.WriteAllText(server.ConfigFile!, conf); await Task.Delay(1000, cts.Token); Process.Start("kill", $"-HUP {server.ServerProcess!.Id}"); } @@ -133,9 +133,9 @@ public async Task Auth_err_can_be_ignored_for_retires() // Reload config with different password { - var conf = (await File.ReadAllTextAsync(server.ConfigFile!, cts.Token)) + var conf = File.ReadAllText(server.ConfigFile!) .Replace("password: b", "password: c"); - await File.WriteAllTextAsync(server.ConfigFile!, conf, cts.Token); + File.WriteAllText(server.ConfigFile!, conf); await Task.Delay(1000, cts.Token); Process.Start("kill", $"-HUP {server.ServerProcess!.Id}"); } @@ -147,9 +147,9 @@ public async Task Auth_err_can_be_ignored_for_retires() // Reload config with correct password { - var conf = (await File.ReadAllTextAsync(server.ConfigFile!, cts.Token)) + var conf = File.ReadAllText(server.ConfigFile!) .Replace("password: c", "password: b"); - await File.WriteAllTextAsync(server.ConfigFile!, conf, cts.Token); + File.WriteAllText(server.ConfigFile!, conf); await Task.Delay(1000, cts.Token); Process.Start("kill", $"-HUP {server.ServerProcess!.Id}"); } diff --git a/tests/NATS.Client.Core.Tests/ConnectionRetryTest.cs b/tests/NATS.Client.Core.Tests/ConnectionRetryTest.cs index 132b727c3..72120ce00 100644 --- a/tests/NATS.Client.Core.Tests/ConnectionRetryTest.cs +++ b/tests/NATS.Client.Core.Tests/ConnectionRetryTest.cs @@ -73,22 +73,26 @@ public async Task Reconnect_doesnt_drop_partially_sent_msgs() { await using var subConn = server.CreateClientConnection(); await using var sub = await subConn.SubscribeCoreAsync>("test", cancellationToken: timeoutCts.Token); - await foreach (var msg in sub.Msgs.ReadAllAsync(timeoutCts.Token)) + + while (await sub.Msgs.WaitToReadAsync(timeoutCts.Token).ConfigureAwait(false)) { - using (msg.Data) + while (sub.Msgs.TryRead(out var msg)) { - if (msg.Data.Length == 1) - { - Interlocked.Increment(ref subActive); - } - else if (msg.Data.Length == 2) - { - break; - } - else + using (msg.Data) { - Assert.Equal(msgSize, msg.Data.Length); - Interlocked.Increment(ref received); + if (msg.Data.Length == 1) + { + Interlocked.Increment(ref subActive); + } + else if (msg.Data.Length == 2) + { + break; + } + else + { + Assert.Equal(msgSize, msg.Data.Length); + Interlocked.Increment(ref received); + } } } } diff --git a/tests/NATS.Client.Core.Tests/LowLevelApiTest.cs b/tests/NATS.Client.Core.Tests/LowLevelApiTest.cs index 3d7efe29b..b43db9af4 100644 --- a/tests/NATS.Client.Core.Tests/LowLevelApiTest.cs +++ b/tests/NATS.Client.Core.Tests/LowLevelApiTest.cs @@ -80,7 +80,7 @@ protected override ValueTask ReceiveInternalAsync(string subject, string? replyT _builder.MessageReceived(sb.ToString()); } - return ValueTask.CompletedTask; + return default; } protected override void TryComplete() diff --git a/tests/NATS.Client.Core.Tests/NATS.Client.Core.Tests.csproj b/tests/NATS.Client.Core.Tests/NATS.Client.Core.Tests.csproj index 420d82e11..07b265f03 100644 --- a/tests/NATS.Client.Core.Tests/NATS.Client.Core.Tests.csproj +++ b/tests/NATS.Client.Core.Tests/NATS.Client.Core.Tests.csproj @@ -1,10 +1,10 @@  - net6.0;net8.0 + net481;net6.0;net8.0 enable false - $(NoWarn);CS8002 + $(NoWarn);CS8002;VSTHRD111;VSTHRD200;VSTHRD110 enable $(MSBuildProjectDirectory)\test.runsettings false @@ -26,6 +26,11 @@ + + + + + diff --git a/tests/NATS.Client.Core.Tests/NKeyTests.cs b/tests/NATS.Client.Core.Tests/NKeyTests.cs index ef7f75ff5..2437dd6a6 100644 --- a/tests/NATS.Client.Core.Tests/NKeyTests.cs +++ b/tests/NATS.Client.Core.Tests/NKeyTests.cs @@ -8,19 +8,19 @@ public class NKeyTests public void NKey_Seed_And_Sign() { const string seed = "SUAAVWRZG6M5FA5VRRGWSCIHKTOJC7EWNIT4JV3FTOIPO4OBFR5WA7X5TE"; - const string expectedSignedResult = "49C5CC75BAA40FAFFD1B8FD9FE5B008A781EC4FF6C7DE18B64C7457B80BDA2A1B2DB556DCA02F8989469088D0D08A0BC3A07E05765CE6EA141F199C2F9EC5D03"; - const string expectedPublicKey = "170932F19001FCDD0618D214E92EC26B04599B5C2A36755843CDC23F650193CF"; + const string expectedSignedResult = "ScXMdbqkD6/9G4/Z/lsAingexP9sfeGLZMdFe4C9oqGy21VtygL4mJRpCI0NCKC8OgfgV2XObqFB8ZnC+exdAw=="; + const string expectedPublicKey = "Fwky8ZAB/N0GGNIU6S7CawRZm1wqNnVYQ83CP2UBk88="; var dataToSign = "Hello World"u8; var nKey = NKeys.FromSeed(seed); // Sanity check public key - var actualPublicKey = Convert.ToHexString(nKey.PublicKey); + var actualPublicKey = Convert.ToBase64String(nKey.PublicKey); Assert.Equal(expectedPublicKey, actualPublicKey); // Sign and verify var signedData = nKey.Sign(dataToSign.ToArray()); - var actual = Convert.ToHexString(signedData); + var actual = Convert.ToBase64String(signedData); Assert.Equal(expectedSignedResult, actual); } @@ -29,11 +29,10 @@ public void NKey_Seed_And_Sign() public void Sha512_Hash() { var dataToHash = "Can I Haz Hash please?"u8; - const string ExpectedHash = "B8B57504AD522AC43AF52CB86BB10D315840C7D1B80BDF3A2524654F7C2C3B07C601ADD320E9F870A6FA8DA3003CFA1BE330133D0ABED7CE49F9251D2BB97421"; + const string ExpectedHash = "uLV1BK1SKsQ69Sy4a7ENMVhAx9G4C986JSRlT3wsOwfGAa3TIOn4cKb6jaMAPPob4zATPQq+185J+SUdK7l0IQ=="; var dataArray = dataToHash.ToArray(); - var actual = Convert.ToHexString(Sha512.Hash(dataArray, 0, dataArray.Length)); - + var actual = Convert.ToBase64String(Sha512.Hash(dataArray, 0, dataArray.Length)); Assert.Equal(ExpectedHash, actual); } } diff --git a/tests/NATS.Client.Core.Tests/NatsConnectionTest.QueueGroups.cs b/tests/NATS.Client.Core.Tests/NatsConnectionTest.QueueGroups.cs index 17f47ae1a..90bc972ee 100644 --- a/tests/NATS.Client.Core.Tests/NatsConnectionTest.QueueGroups.cs +++ b/tests/NATS.Client.Core.Tests/NatsConnectionTest.QueueGroups.cs @@ -27,20 +27,23 @@ public async Task QueueGroupsTest() var reader1 = Task.Run( async () => { - await foreach (var msg in sub1.Msgs.ReadAllAsync(cts.Token)) + while (await sub1.Msgs.WaitToReadAsync(cts.Token).ConfigureAwait(false)) { - if (msg.Subject == "foo.sync") + while (sub1.Msgs.TryRead(out var msg)) { - Interlocked.Exchange(ref sync1, 1); - continue; + if (msg.Subject == "foo.sync") + { + Interlocked.Exchange(ref sync1, 1); + continue; + } + + Assert.Equal($"foo.xyz{msg.Data}", msg.Subject); + lock (messages1) + messages1.Add(msg.Data); + var total = Interlocked.Increment(ref count); + if (total == messageCount) + cts.Cancel(); } - - Assert.Equal($"foo.xyz{msg.Data}", msg.Subject); - lock (messages1) - messages1.Add(msg.Data); - var total = Interlocked.Increment(ref count); - if (total == messageCount) - cts.Cancel(); } }, cts.Token); @@ -50,20 +53,23 @@ public async Task QueueGroupsTest() var reader2 = Task.Run( async () => { - await foreach (var msg in sub2.Msgs.ReadAllAsync(cts.Token)) + while (await sub2.Msgs.WaitToReadAsync(cts.Token).ConfigureAwait(false)) { - if (msg.Subject == "foo.sync") + while (sub2.Msgs.TryRead(out var msg)) { - Interlocked.Exchange(ref sync2, 1); - continue; + if (msg.Subject == "foo.sync") + { + Interlocked.Exchange(ref sync2, 1); + continue; + } + + Assert.Equal($"foo.xyz{msg.Data}", msg.Subject); + lock (messages2) + messages2.Add(msg.Data); + var total = Interlocked.Increment(ref count); + if (total == messageCount) + cts.Cancel(); } - - Assert.Equal($"foo.xyz{msg.Data}", msg.Subject); - lock (messages2) - messages2.Add(msg.Data); - var total = Interlocked.Increment(ref count); - if (total == messageCount) - cts.Cancel(); } }, cts.Token); diff --git a/tests/NATS.Client.Core.Tests/NatsHeaderTest.cs b/tests/NATS.Client.Core.Tests/NatsHeaderTest.cs index 3ae66433f..240a0700e 100644 --- a/tests/NATS.Client.Core.Tests/NatsHeaderTest.cs +++ b/tests/NATS.Client.Core.Tests/NatsHeaderTest.cs @@ -31,7 +31,7 @@ public async Task WriterTests() await pipe.Writer.FlushAsync(); var result = await pipe.Reader.ReadAtLeastAsync((int)written); Assert.True(expected.ToSpan().SequenceEqual(result.Buffer.ToSpan())); - _output.WriteLine($"Buffer:\n{result.Buffer.FirstSpan.Dump()}"); + _output.WriteLine($"Buffer:\n{result.Buffer.First.Span.Dump()}"); } [Fact] @@ -49,7 +49,7 @@ public async Task WriterEmptyTests() await pipe.Writer.FlushAsync(); var result = await pipe.Reader.ReadAtLeastAsync((int)written); Assert.True(expected.ToSpan().SequenceEqual(result.Buffer.ToSpan())); - _output.WriteLine($"Buffer:\n{result.Buffer.FirstSpan.Dump()}"); + _output.WriteLine($"Buffer:\n{result.Buffer.First.Span.Dump()}"); } [Fact] diff --git a/tests/NATS.Client.Core.Tests/NuidWriterTests.cs b/tests/NATS.Client.Core.Tests/NuidWriterTests.cs index bf133a920..ea8f25f5a 100644 --- a/tests/NATS.Client.Core.Tests/NuidWriterTests.cs +++ b/tests/NATS.Client.Core.Tests/NuidWriterTests.cs @@ -47,7 +47,7 @@ public void GetNextNuid_ReturnsNuidOfLength22_Char() // Assert ReadOnlySpan lower = buffer.Slice(0, 22); - string resultAsString = new(lower); + string resultAsString = new(lower.ToArray()); ReadOnlySpan upper = buffer.Slice(22); Assert.True(result); @@ -113,7 +113,7 @@ public void GetNextNuid_ContainsOnlyValidCharacters_Char() // Assert Assert.True(result); - string resultAsString = new(nuid); + string resultAsString = new(nuid.ToArray()); Assert.Matches("[A-z0-9]{22}", resultAsString); } @@ -142,6 +142,7 @@ public void GetNextNuid_PrefixRenewed_Char() Assert.False(firstNuid.AsSpan(0, 12).SequenceEqual(secondNuid.AsSpan(0, 12))); } +#if !NETFRAMEWORK [Fact] public void GetPrefix_PrefixAsExpected() { @@ -159,6 +160,7 @@ public void GetPrefix_PrefixAsExpected() Assert.Equal(12, prefix.Length); Assert.True("01234567B567".AsSpan().SequenceEqual(prefix)); } +#endif [Fact] public void InitAndWrite_Char() @@ -208,7 +210,7 @@ public void DifferentThreads_DifferentPrefixes() foreach (var (nuid, threadId) in nuids.ToList()) { - var prefix = new string(nuid.AsSpan(0, prefixLength)); + var prefix = new string(nuid.AsSpan(0, prefixLength).ToArray()); Assert.True(uniquePrefixes.Add(prefix), $"Unique prefix {prefix}"); Assert.True(uniqueThreadIds.Add(threadId), $"Unique thread id {threadId}"); } @@ -266,7 +268,7 @@ public void AllNuidsAreUnique_SmallSequentials() return; } - var nuid = new string(buffer); + var nuid = new string(buffer.ToArray()); if (!nuids.Add(nuid)) { @@ -308,7 +310,7 @@ public void AllNuidsAreUnique_ZeroSequential() return; } - var nuid = new string(buffer); + var nuid = new string(buffer.ToArray()); if (!nuids.Add(nuid)) { diff --git a/tests/NATS.Client.Core.Tests/ProtocolTest.cs b/tests/NATS.Client.Core.Tests/ProtocolTest.cs index 6490a9976..5f42f6dac 100644 --- a/tests/NATS.Client.Core.Tests/ProtocolTest.cs +++ b/tests/NATS.Client.Core.Tests/ProtocolTest.cs @@ -227,10 +227,13 @@ void Log(string text) var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var cancellationToken = cts.Token; var count = 0; - await foreach (var natsMsg in sub.Msgs.ReadAllAsync(cancellationToken)) + while (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - Assert.Equal(count, natsMsg.Data); - count++; + while (sub.Msgs.TryRead(out var natsMsg)) + { + Assert.Equal(count, natsMsg.Data); + count++; + } } Assert.Equal(maxMsgs, count); @@ -261,9 +264,12 @@ void Log(string text) var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var cancellationToken = cts.Token; var count = 0; - await foreach (var unused in sub.Msgs.ReadAllAsync(cancellationToken)) + while (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - count++; + while (sub.Msgs.TryRead(out _)) + { + count++; + } } Assert.Equal(0, count); @@ -572,9 +578,9 @@ internal override async ValueTask WriteReconnectCommandsAsync(CommandWriter comm protected override ValueTask ReceiveInternalAsync(string subject, string? replyTo, ReadOnlySequence? headersBuffer, ReadOnlySequence payloadBuffer) { - _callback(int.Parse(Encoding.UTF8.GetString(payloadBuffer))); + _callback(int.Parse(Encoding.UTF8.GetString(payloadBuffer.ToArray()))); DecrementMaxMsgs(); - return ValueTask.CompletedTask; + return default; } protected override void TryComplete() diff --git a/tests/NATS.Client.Core.Tests/RequestReplyTest.cs b/tests/NATS.Client.Core.Tests/RequestReplyTest.cs index 83846cdf5..992e8ba30 100644 --- a/tests/NATS.Client.Core.Tests/RequestReplyTest.cs +++ b/tests/NATS.Client.Core.Tests/RequestReplyTest.cs @@ -151,9 +151,13 @@ public async Task Request_reply_many_test_overall_timeout() var opts = new NatsSubOpts { Timeout = TimeSpan.FromSeconds(4) }; await using var rep = await nats.RequestSubAsync("foo", 4, replyOpts: opts, cancellationToken: cts.Token); - await foreach (var msg in rep.Msgs.ReadAllAsync(cts.Token)) + + while (await rep.Msgs.WaitToReadAsync(cts.Token).ConfigureAwait(false)) { - Assert.Equal(results[count++], msg.Data); + while (rep.Msgs.TryRead(out var msg)) + { + Assert.Equal(results[count++], msg.Data); + } } Assert.Equal(2, count); @@ -185,9 +189,13 @@ public async Task Request_reply_many_test_idle_timeout() var opts = new NatsSubOpts { IdleTimeout = TimeSpan.FromSeconds(3) }; await using var rep = await nats.RequestSubAsync("foo", 3, replyOpts: opts, cancellationToken: cts.Token); - await foreach (var msg in rep.Msgs.ReadAllAsync(cts.Token)) + + while (await rep.Msgs.WaitToReadAsync(cts.Token).ConfigureAwait(false)) { - Assert.Equal(results[count++], msg.Data); + while (rep.Msgs.TryRead(out var msg)) + { + Assert.Equal(results[count++], msg.Data); + } } Assert.Equal(2, count); @@ -215,9 +223,13 @@ public async Task Request_reply_many_test_start_up_timeout() var opts = new NatsSubOpts { StartUpTimeout = TimeSpan.FromSeconds(1) }; await using var rep = await nats.RequestSubAsync("foo", 2, replyOpts: opts, cancellationToken: cts.Token); - await foreach (var msg in rep.Msgs.ReadAllAsync(cts.Token)) + + while (await rep.Msgs.WaitToReadAsync(cts.Token).ConfigureAwait(false)) { - count++; + while (rep.Msgs.TryRead(out var msg)) + { + count++; + } } Assert.Equal(0, count); @@ -248,9 +260,13 @@ public async Task Request_reply_many_test_max_count() var opts = new NatsSubOpts { MaxMsgs = 2 }; await using var rep = await nats.RequestSubAsync("foo", 1, replyOpts: opts, cancellationToken: cts.Token); - await foreach (var msg in rep.Msgs.ReadAllAsync(cts.Token)) + + while (await rep.Msgs.WaitToReadAsync(cts.Token).ConfigureAwait(false)) { - Assert.Equal(results[count++], msg.Data); + while (rep.Msgs.TryRead(out var msg)) + { + Assert.Equal(results[count++], msg.Data); + } } Assert.Equal(2, count); @@ -294,7 +310,7 @@ public async Task Request_reply_binary_test() { static string ToStr(ReadOnlyMemory input) { - return Encoding.ASCII.GetString(input.Span); + return Encoding.ASCII.GetString(input.Span.ToArray()); } var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); @@ -313,7 +329,7 @@ static string ToStr(ReadOnlyMemory input) } }); - var writer = new ArrayBufferWriter(); + var writer = new NatsBufferWriter(); await foreach (var msg in nats.RequestManyAsync("foo", "1", cancellationToken: cts.Token)) { writer.Write(Encoding.UTF8.GetBytes(msg.Data!)); diff --git a/tests/NATS.Client.Core.Tests/SendBufferTest.cs b/tests/NATS.Client.Core.Tests/SendBufferTest.cs index 973f94c16..0c09ae162 100644 --- a/tests/NATS.Client.Core.Tests/SendBufferTest.cs +++ b/tests/NATS.Client.Core.Tests/SendBufferTest.cs @@ -2,6 +2,9 @@ using System.Net.Sockets; using Microsoft.Extensions.Logging; using NATS.Client.TestUtilities; +#if NETFRAMEWORK +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Core.Tests; diff --git a/tests/NATS.Client.Core.Tests/SerializerTest.cs b/tests/NATS.Client.Core.Tests/SerializerTest.cs index 45f3aeb81..1e12f154b 100644 --- a/tests/NATS.Client.Core.Tests/SerializerTest.cs +++ b/tests/NATS.Client.Core.Tests/SerializerTest.cs @@ -97,7 +97,7 @@ void Serialize(INatsSerialize serializer, T value, string expected) { var buffer = new NatsBufferWriter(); serializer.Serialize(buffer, value); - var actual = Encoding.UTF8.GetString(buffer.WrittenMemory.Span); + var actual = Encoding.UTF8.GetString(buffer.WrittenMemory.Span.ToArray()); Assert.Equal(expected, actual); } @@ -307,7 +307,7 @@ public class TestSerializerWithEmpty : INatsSerializer { public T? Deserialize(in ReadOnlySequence buffer) => (T)(object)(buffer.IsEmpty ? new TestData("__EMPTY__") - : new TestData(Encoding.ASCII.GetString(buffer))); + : new TestData(Encoding.ASCII.GetString(buffer.ToArray()))); public void Serialize(IBufferWriter bufferWriter, T value) => throw new Exception("not used"); } diff --git a/tests/NATS.Client.Core.Tests/SubscriptionTest.cs b/tests/NATS.Client.Core.Tests/SubscriptionTest.cs index 1c12f50cf..798af5c5b 100644 --- a/tests/NATS.Client.Core.Tests/SubscriptionTest.cs +++ b/tests/NATS.Client.Core.Tests/SubscriptionTest.cs @@ -102,20 +102,26 @@ public async Task Auto_unsubscribe_on_max_messages_with_inbox_subscription_test( var cancellationToken = cts.Token; var count1 = 0; - await foreach (var natsMsg in sub1.Msgs.ReadAllAsync(cancellationToken)) + while (await sub1.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - Assert.Equal(count1, natsMsg.Data); - count1++; + while (sub1.Msgs.TryRead(out var natsMsg)) + { + Assert.Equal(count1, natsMsg.Data); + count1++; + } } Assert.Equal(1, count1); Assert.Equal(NatsSubEndReason.MaxMsgs, ((NatsSubBase)sub1).EndReason); var count2 = 0; - await foreach (var natsMsg in sub2.Msgs.ReadAllAsync(cancellationToken)) + while (await sub2.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - Assert.Equal(count2, natsMsg.Data); - count2++; + while (sub2.Msgs.TryRead(out var natsMsg)) + { + Assert.Equal(count2, natsMsg.Data); + count2++; + } } Assert.Equal(2, count2); @@ -142,10 +148,13 @@ public async Task Auto_unsubscribe_on_max_messages_test() var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var cancellationToken = cts.Token; var count = 0; - await foreach (var natsMsg in sub.Msgs.ReadAllAsync(cancellationToken)) + while (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - Assert.Equal(count, natsMsg.Data); - count++; + while (sub.Msgs.TryRead(out var natsMsg)) + { + Assert.Equal(count, natsMsg.Data); + count++; + } } Assert.Equal(maxMsgs, count); @@ -166,9 +175,12 @@ public async Task Auto_unsubscribe_on_timeout_test() var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var cancellationToken = cts.Token; var count = 0; - await foreach (var unused in sub.Msgs.ReadAllAsync(cancellationToken)) + while (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - count++; + while (sub.Msgs.TryRead(out _)) + { + count++; + } } Assert.Equal(NatsSubEndReason.Timeout, ((NatsSubBase)sub).EndReason); @@ -196,10 +208,13 @@ public async Task Auto_unsubscribe_on_idle_timeout_test() var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var cancellationToken = cts.Token; var count = 0; - await foreach (var natsMsg in sub.Msgs.ReadAllAsync(cancellationToken)) + while (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - Assert.Equal(count, natsMsg.Data); - count++; + while (sub.Msgs.TryRead(out var natsMsg)) + { + Assert.Equal(count, natsMsg.Data); + count++; + } } Assert.Equal(NatsSubEndReason.IdleTimeout, ((NatsSubBase)sub).EndReason); @@ -224,10 +239,13 @@ public async Task Manual_unsubscribe_test() var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var cancellationToken = cts.Token; var count = 0; - await foreach (var natsMsg in sub.Msgs.ReadAllAsync(cancellationToken)) + while (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { - Assert.Equal(count, natsMsg.Data); - count++; + while (sub.Msgs.TryRead(out var natsMsg)) + { + Assert.Equal(count, natsMsg.Data); + count++; + } } Assert.Equal(0, count); diff --git a/tests/NATS.Client.Core.Tests/TlsOptsTest.cs b/tests/NATS.Client.Core.Tests/TlsOptsTest.cs index 16a747043..faf74127d 100644 --- a/tests/NATS.Client.Core.Tests/TlsOptsTest.cs +++ b/tests/NATS.Client.Core.Tests/TlsOptsTest.cs @@ -1,3 +1,5 @@ +#if !NETFRAMEWORK + using System.Net.Security; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -199,3 +201,5 @@ static async Task ValidateAsync(NatsServer natsServer, NatsTlsOpts opts) } } } + +#endif From 055d693aae4ee25608810d658e5e833f38ddb2f7 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 16:04:48 +0100 Subject: [PATCH 17/47] test fix --- src/NATS.Client.ObjectStore/NatsObjStore.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/NATS.Client.ObjectStore/NatsObjStore.cs b/src/NATS.Client.ObjectStore/NatsObjStore.cs index 24a9be87d..72b76b0e7 100644 --- a/src/NATS.Client.ObjectStore/NatsObjStore.cs +++ b/src/NATS.Client.ObjectStore/NatsObjStore.cs @@ -605,9 +605,13 @@ public async IAsyncEnumerable WatchAsync(NatsObjWatchOpts? opts pushConsumer.Init(); +#if NET6_0_OR_GREATER + await foreach (var msg in pushConsumer.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) +#else while (await pushConsumer.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { while (pushConsumer.Msgs.TryRead(out var msg)) +#endif { if (pushConsumer.IsDone) continue; @@ -633,7 +637,9 @@ public async IAsyncEnumerable WatchAsync(NatsObjWatchOpts? opts } } } +#if !NET6_0_OR_GREATER } +#endif } /// From abc6b02d0d4494e4842b95f27cbe48caf5b91d2e Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 17:21:04 +0100 Subject: [PATCH 18/47] other tests --- src/NATS.Client.Core/Internal/netstandard.cs | 17 +++++++++++ .../ConsumerConsumeTest.cs | 23 ++++++++++++++ .../ConsumerFetchTest.cs | 11 +++++++ .../EnumJsonTests.cs | 3 ++ .../JetStreamTest.cs | 7 +++++ .../NATS.Client.JetStream.Tests.csproj | 4 ++- .../ParseJsonTests.cs | 3 ++ .../TimeSpanJsonTests.cs | 7 +++-- .../NATS.Client.KeyValueStore.Tests.csproj | 4 ++- .../NatsKVWatcherTest.cs | 22 ++++++++++++-- .../CompatTest.cs | 2 +- .../NATS.Client.ObjectStore.Tests.csproj | 4 ++- .../ObjectStoreTest.cs | 30 +++++++++++++++---- .../NATS.Client.Services.Tests.csproj | 3 +- .../ServicesTests.cs | 12 ++++---- 15 files changed, 132 insertions(+), 20 deletions(-) diff --git a/src/NATS.Client.Core/Internal/netstandard.cs b/src/NATS.Client.Core/Internal/netstandard.cs index 148763fd4..0319da02b 100644 --- a/src/NATS.Client.Core/Internal/netstandard.cs +++ b/src/NATS.Client.Core/Internal/netstandard.cs @@ -23,12 +23,27 @@ namespace NATS.Client.Core.Internal.NetStandardExtensions using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; + using System.Threading.Channels; [StructLayout(LayoutKind.Sequential, Size = 1)] internal readonly struct VoidResult { } + internal static class ChannelReaderExtensions + { + public static async IAsyncEnumerable ReadAllLoopAsync(this ChannelReader reader, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + while (reader.TryRead(out var msg)) + { + yield return msg; + } + } + } + } + internal sealed class TaskCompletionSource : TaskCompletionSource { public TaskCompletionSource(TaskCreationOptions creationOptions) @@ -114,6 +129,8 @@ internal long NextInt64(long minValue, long maxValue) return (long)(scaled + minValue); } + internal void NextBytes(byte[] data) => LocalRandom.NextBytes(data); + [MethodImpl(MethodImplOptions.NoInlining)] private static System.Random Create() => _internal = new System.Random(); } diff --git a/tests/NATS.Client.JetStream.Tests/ConsumerConsumeTest.cs b/tests/NATS.Client.JetStream.Tests/ConsumerConsumeTest.cs index d01d6831a..58d7c2019 100644 --- a/tests/NATS.Client.JetStream.Tests/ConsumerConsumeTest.cs +++ b/tests/NATS.Client.JetStream.Tests/ConsumerConsumeTest.cs @@ -2,6 +2,9 @@ using Microsoft.Extensions.Logging; using NATS.Client.Core.Tests; using NATS.Client.JetStream.Models; +#if NETFRAMEWORK +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.JetStream.Tests; @@ -77,7 +80,11 @@ public async Task Consume_msgs_test() var consumer = (NatsJSConsumer)await js.GetConsumerAsync("s1", "c1", cts.Token); var count = 0; await using var cc = await consumer.ConsumeInternalAsync(serializer: TestDataJsonSerializer.Default, consumerOpts, cancellationToken: cts.Token); +#if NETFRAMEWORK + await foreach (var msg in cc.Msgs.ReadAllLoopAsync(cts.Token)) +#else await foreach (var msg in cc.Msgs.ReadAllAsync(cts.Token)) +#endif { await msg.AckAsync(cancellationToken: cts.Token); Assert.Equal(count, msg.Data!.Test); @@ -150,7 +157,11 @@ public async Task Consume_idle_heartbeat_test() var consumer = (NatsJSConsumer)await js.GetConsumerAsync("s1", "c1", cts.Token); var count = 0; var cc = await consumer.ConsumeInternalAsync(serializer: TestDataJsonSerializer.Default, consumerOpts, cancellationToken: cts.Token); +#if NETFRAMEWORK + await foreach (var msg in cc.Msgs.ReadAllLoopAsync(cts.Token)) +#else await foreach (var msg in cc.Msgs.ReadAllAsync(cts.Token)) +#endif { await msg.AckAsync(cancellationToken: cts.Token); Assert.Equal(count, msg.Data!.Test); @@ -220,7 +231,11 @@ public async Task Consume_reconnect_test() var readerTask = Task.Run(async () => { var count = 0; +#if NETFRAMEWORK + await foreach (var msg in cc.Msgs.ReadAllLoopAsync(cts.Token)) +#else await foreach (var msg in cc.Msgs.ReadAllAsync(cts.Token)) +#endif { await msg.AckAsync(cancellationToken: cts.Token); Assert.Equal(count, msg.Data!.Test); @@ -297,7 +312,11 @@ public async Task Consume_dispose_test() var signal2 = new WaitSignal(); var reader = Task.Run(async () => { +#if NETFRAMEWORK + await foreach (var msg in cc.Msgs.ReadAllLoopAsync(cts.Token)) +#else await foreach (var msg in cc.Msgs.ReadAllAsync(cts.Token)) +#endif { await msg.AckAsync(cancellationToken: cts.Token); signal1.Pulse(); @@ -375,7 +394,11 @@ public async Task Consume_stop_test() var signal2 = new WaitSignal(); var reader = Task.Run(async () => { +#if NETFRAMEWORK + await foreach (var msg in cc.Msgs.ReadAllLoopAsync(cts.Token)) +#else await foreach (var msg in cc.Msgs.ReadAllAsync(cts.Token)) +#endif { await msg.AckAsync(cancellationToken: cts.Token); signal1.Pulse(); diff --git a/tests/NATS.Client.JetStream.Tests/ConsumerFetchTest.cs b/tests/NATS.Client.JetStream.Tests/ConsumerFetchTest.cs index fb14c5904..70115f803 100644 --- a/tests/NATS.Client.JetStream.Tests/ConsumerFetchTest.cs +++ b/tests/NATS.Client.JetStream.Tests/ConsumerFetchTest.cs @@ -1,5 +1,8 @@ using NATS.Client.Core.Tests; using NATS.Client.JetStream.Models; +#if NETFRAMEWORK +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.JetStream.Tests; @@ -29,7 +32,11 @@ public async Task Fetch_test() var count = 0; await using var fc = await consumer.FetchInternalAsync(serializer: TestDataJsonSerializer.Default, opts: new NatsJSFetchOpts { MaxMsgs = 10 }, cancellationToken: cts.Token); +#if NETFRAMEWORK + await foreach (var msg in fc.Msgs.ReadAllLoopAsync(cts.Token)) +#else await foreach (var msg in fc.Msgs.ReadAllAsync(cts.Token)) +#endif { await msg.AckAsync(cancellationToken: cts.Token); Assert.Equal(count, msg.Data!.Test); @@ -98,7 +105,11 @@ public async Task Fetch_dispose_test() var signal2 = new WaitSignal(); var reader = Task.Run(async () => { +#if NETFRAMEWORK + await foreach (var msg in fc.Msgs.ReadAllLoopAsync(cts.Token)) +#else await foreach (var msg in fc.Msgs.ReadAllAsync(cts.Token)) +#endif { await msg.AckAsync(cancellationToken: cts.Token); signal1.Pulse(); diff --git a/tests/NATS.Client.JetStream.Tests/EnumJsonTests.cs b/tests/NATS.Client.JetStream.Tests/EnumJsonTests.cs index 94bc50299..aed6ffc21 100644 --- a/tests/NATS.Client.JetStream.Tests/EnumJsonTests.cs +++ b/tests/NATS.Client.JetStream.Tests/EnumJsonTests.cs @@ -2,6 +2,9 @@ using System.Text; using NATS.Client.JetStream.Internal; using NATS.Client.JetStream.Models; +#if NETFRAMEWORK +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.JetStream.Tests; diff --git a/tests/NATS.Client.JetStream.Tests/JetStreamTest.cs b/tests/NATS.Client.JetStream.Tests/JetStreamTest.cs index 4cbc9cd6f..4201d5653 100644 --- a/tests/NATS.Client.JetStream.Tests/JetStreamTest.cs +++ b/tests/NATS.Client.JetStream.Tests/JetStreamTest.cs @@ -1,5 +1,8 @@ using NATS.Client.Core.Tests; using NATS.Client.JetStream.Models; +#if NETFRAMEWORK +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.JetStream.Tests; @@ -119,7 +122,11 @@ public async Task Create_stream_test() serializer: TestDataJsonSerializer.Default, opts: new NatsJSConsumeOpts { MaxMsgs = 100 }, cancellationToken: cts2.Token); +#if NETFRAMEWORK + await foreach (var msg in cc.Msgs.ReadAllLoopAsync(cts2.Token)) +#else await foreach (var msg in cc.Msgs.ReadAllAsync(cts2.Token)) +#endif { messages.Add(msg); diff --git a/tests/NATS.Client.JetStream.Tests/NATS.Client.JetStream.Tests.csproj b/tests/NATS.Client.JetStream.Tests/NATS.Client.JetStream.Tests.csproj index be86630aa..8f368ca3d 100644 --- a/tests/NATS.Client.JetStream.Tests/NATS.Client.JetStream.Tests.csproj +++ b/tests/NATS.Client.JetStream.Tests/NATS.Client.JetStream.Tests.csproj @@ -1,9 +1,10 @@ - net6.0;net8.0 + net481;net6.0;net8.0 enable enable + $(NoWarn);CS8002;CS1685;VSTHRD111;VSTHRD200;VSTHRD110 false true @@ -29,6 +30,7 @@ + diff --git a/tests/NATS.Client.JetStream.Tests/ParseJsonTests.cs b/tests/NATS.Client.JetStream.Tests/ParseJsonTests.cs index 6e57aaf28..d49641db8 100644 --- a/tests/NATS.Client.JetStream.Tests/ParseJsonTests.cs +++ b/tests/NATS.Client.JetStream.Tests/ParseJsonTests.cs @@ -2,6 +2,9 @@ using System.Text; using NATS.Client.JetStream.Internal; using NATS.Client.JetStream.Models; +#if NETFRAMEWORK +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.JetStream.Tests; diff --git a/tests/NATS.Client.JetStream.Tests/TimeSpanJsonTests.cs b/tests/NATS.Client.JetStream.Tests/TimeSpanJsonTests.cs index 08a927a2f..c103b1274 100644 --- a/tests/NATS.Client.JetStream.Tests/TimeSpanJsonTests.cs +++ b/tests/NATS.Client.JetStream.Tests/TimeSpanJsonTests.cs @@ -2,6 +2,9 @@ using System.Text; using NATS.Client.JetStream.Internal; using NATS.Client.JetStream.Models; +#if NETFRAMEWORK +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.JetStream.Tests; @@ -19,7 +22,7 @@ public void ConsumerConfigAckWait_test(string value, string expected) var bw = new NatsBufferWriter(); serializer.Serialize(bw, new ConsumerConfig { AckWait = time }); - var json = Encoding.UTF8.GetString(bw.WrittenSpan); + var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); @@ -39,7 +42,7 @@ public void ConsumerConfigIdleHeartbeat_test(string value, string expected) var bw = new NatsBufferWriter(); serializer.Serialize(bw, new ConsumerConfig { IdleHeartbeat = time }); - var json = Encoding.UTF8.GetString(bw.WrittenSpan); + var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); Assert.Matches(expected, json); var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); diff --git a/tests/NATS.Client.KeyValueStore.Tests/NATS.Client.KeyValueStore.Tests.csproj b/tests/NATS.Client.KeyValueStore.Tests/NATS.Client.KeyValueStore.Tests.csproj index 63bcf2bc1..053574b86 100644 --- a/tests/NATS.Client.KeyValueStore.Tests/NATS.Client.KeyValueStore.Tests.csproj +++ b/tests/NATS.Client.KeyValueStore.Tests/NATS.Client.KeyValueStore.Tests.csproj @@ -1,9 +1,10 @@ - net6.0;net8.0 + net481;net6.0;net8.0 enable enable + $(NoWarn);CS8002;CS1685;VSTHRD111;VSTHRD200;VSTHRD110 false true @@ -30,6 +31,7 @@ + diff --git a/tests/NATS.Client.KeyValueStore.Tests/NatsKVWatcherTest.cs b/tests/NATS.Client.KeyValueStore.Tests/NatsKVWatcherTest.cs index a66ca1f5f..76fc521c6 100644 --- a/tests/NATS.Client.KeyValueStore.Tests/NatsKVWatcherTest.cs +++ b/tests/NATS.Client.KeyValueStore.Tests/NatsKVWatcherTest.cs @@ -2,6 +2,9 @@ using Microsoft.Extensions.Logging; using NATS.Client.Core.Tests; using NATS.Client.KeyValueStore.Internal; +#if NETFRAMEWORK +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.KeyValueStore.Tests; @@ -38,7 +41,11 @@ public async Task Watcher_reconnect_with_history() var count = 0; +#if NETFRAMEWORK + await foreach (var entry in watcher.Entries.ReadAllLoopAsync(cancellationToken)) +#else await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken)) +#endif { using (entry.Value) { @@ -70,8 +77,11 @@ public async Task Watcher_reconnect_with_history() await store1.PutAsync("k1.p1", 4, cancellationToken: cancellationToken); await store1.PutAsync("k1.p1", 5, cancellationToken: cancellationToken); await store1.PutAsync("k1.p1", 6, cancellationToken: cancellationToken); - +#if NETFRAMEWORK + await foreach (var entry in watcher.Entries.ReadAllLoopAsync(cancellationToken)) +#else await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken)) +#endif { if (entry.Value is { } memoryOwner) { @@ -239,7 +249,11 @@ public async Task Watcher_timeout_reconnect() var consumer1 = ((NatsKVWatcher>)watcher).Consumer; +#if NETFRAMEWORK + await foreach (var entry in watcher.Entries.ReadAllLoopAsync(cancellationToken)) +#else await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken)) +#endif { using (entry.Value) { @@ -280,7 +294,11 @@ await Retry.Until( retryDelay: TimeSpan.FromSeconds(1), timeout: timeout); +#if NETFRAMEWORK + await foreach (var entry in watcher.Entries.ReadAllLoopAsync(cancellationToken)) +#else await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken)) +#endif { if (entry.Value is { } memoryOwner) { @@ -528,7 +546,7 @@ public async Task Watch_resume_at_revision() OnNoData = (_) => { noData = true; - return ValueTask.FromResult(true); + return new ValueTask(true); }, }; diff --git a/tests/NATS.Client.ObjectStore.Tests/CompatTest.cs b/tests/NATS.Client.ObjectStore.Tests/CompatTest.cs index 5f984c383..9232ef9c6 100644 --- a/tests/NATS.Client.ObjectStore.Tests/CompatTest.cs +++ b/tests/NATS.Client.ObjectStore.Tests/CompatTest.cs @@ -29,7 +29,7 @@ public async Task Headers_serialization() // Verify PUT { - await using var stream = new MemoryStream("Hello, World!"u8.ToArray()); + using var stream = new MemoryStream("Hello, World!"u8.ToArray()); await store.PutAsync(objMeta, stream, cancellationToken: cancellationToken); // nats obj info b1 o1 diff --git a/tests/NATS.Client.ObjectStore.Tests/NATS.Client.ObjectStore.Tests.csproj b/tests/NATS.Client.ObjectStore.Tests/NATS.Client.ObjectStore.Tests.csproj index 0139097b5..02701bfa8 100644 --- a/tests/NATS.Client.ObjectStore.Tests/NATS.Client.ObjectStore.Tests.csproj +++ b/tests/NATS.Client.ObjectStore.Tests/NATS.Client.ObjectStore.Tests.csproj @@ -1,9 +1,10 @@ - net6.0;net8.0 + net481;net6.0;net8.0 enable enable + $(NoWarn);CS8002;CS1685;VSTHRD111;VSTHRD200;VSTHRD110 false true @@ -30,6 +31,7 @@ + diff --git a/tests/NATS.Client.ObjectStore.Tests/ObjectStoreTest.cs b/tests/NATS.Client.ObjectStore.Tests/ObjectStoreTest.cs index 0f8250d1b..e4dc47f00 100644 --- a/tests/NATS.Client.ObjectStore.Tests/ObjectStoreTest.cs +++ b/tests/NATS.Client.ObjectStore.Tests/ObjectStoreTest.cs @@ -5,6 +5,9 @@ using NATS.Client.ObjectStore.Internal; using NATS.Client.ObjectStore.Models; using NATS.Client.Serializers.Json; +#if NETFRAMEWORK +using Random = NATS.Client.Core.Internal.NetStandardExtensions.Random; +#endif namespace NATS.Client.ObjectStore.Tests; @@ -73,7 +76,7 @@ public async Task Put_chunks() var data = await store.GetInfoAsync("k1", cancellationToken: cancellationToken); - var sha = Base64UrlEncoder.Encode(SHA256.HashData(buffer)); + var sha = Base64UrlEncoder.Encode(Hash256(buffer)); var size = buffer.Length; var chunks = Math.Ceiling(size / 10.0); @@ -93,7 +96,7 @@ public async Task Put_chunks() var data = await store.GetInfoAsync("k2", cancellationToken: cancellationToken); - var sha = Base64UrlEncoder.Encode(SHA256.HashData(buffer)); + var sha = Base64UrlEncoder.Encode(Hash256(buffer)); var size = buffer.Length; var chunks = Math.Ceiling(size / 10.0); @@ -234,14 +237,14 @@ public async Task Put_and_get_large_file() const string filename = $"_tmp_test_file_{nameof(Put_and_get_large_file)}.bin"; var filename1 = $"{filename}.1"; - await File.WriteAllBytesAsync(filename, data, cancellationToken); + File.WriteAllBytes(filename, data); await store.PutAsync("my/random/data.bin", File.OpenRead(filename), cancellationToken: cancellationToken); await store.GetAsync("my/random/data.bin", File.OpenWrite(filename1), cancellationToken: cancellationToken); - var hash = Convert.ToBase64String(SHA256.HashData(await File.ReadAllBytesAsync(filename, cancellationToken))); - var hash1 = Convert.ToBase64String(SHA256.HashData(await File.ReadAllBytesAsync(filename1, cancellationToken))); + var hash = Convert.ToBase64String(Hash256(File.ReadAllBytes(filename))); + var hash1 = Convert.ToBase64String(Hash256(File.ReadAllBytes(filename1))); Assert.Equal(hash, hash1); } @@ -441,4 +444,21 @@ public async Task Put_get_serialization_when_default_serializer_is_not_used() var info = await store.GetInfoAsync("k1", cancellationToken: cancellationToken); Assert.Equal("k1", info.Name); } + + private static byte[] Hash256(byte[] data) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(data); + ReadOnlySpan dataSpan = data; + return SHA256.HashData(dataSpan); +#else + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + using var s = SHA256.Create(); + return s.ComputeHash(data); +#endif + } } diff --git a/tests/NATS.Client.Services.Tests/NATS.Client.Services.Tests.csproj b/tests/NATS.Client.Services.Tests/NATS.Client.Services.Tests.csproj index 0f784d731..c9aa45633 100644 --- a/tests/NATS.Client.Services.Tests/NATS.Client.Services.Tests.csproj +++ b/tests/NATS.Client.Services.Tests/NATS.Client.Services.Tests.csproj @@ -1,9 +1,10 @@ - net6.0;net8.0 + net481;net6.0;net8.0 enable enable + $(NoWarn);CS8002;CS1685;VSTHRD111;VSTHRD200;VSTHRD110 false true diff --git a/tests/NATS.Client.Services.Tests/ServicesTests.cs b/tests/NATS.Client.Services.Tests/ServicesTests.cs index 63f3a0a1d..bdb1b6416 100644 --- a/tests/NATS.Client.Services.Tests/ServicesTests.cs +++ b/tests/NATS.Client.Services.Tests/ServicesTests.cs @@ -144,25 +144,25 @@ public async Task Add_groups_metadata_and_stats() await s1.AddEndpointAsync( name: "baz", subject: "foo.baz", - handler: _ => ValueTask.CompletedTask, + handler: _ => default, cancellationToken: cancellationToken); await s1.AddEndpointAsync( subject: "foo.bar1", - handler: _ => ValueTask.CompletedTask, + handler: _ => default, cancellationToken: cancellationToken); var grp1 = await s1.AddGroupAsync("grp1", cancellationToken: cancellationToken); await grp1.AddEndpointAsync( name: "e1", - handler: _ => ValueTask.CompletedTask, + handler: _ => default, cancellationToken: cancellationToken); await grp1.AddEndpointAsync( name: "e2", subject: "foo.bar2", - handler: _ => ValueTask.CompletedTask, + handler: _ => default, cancellationToken: cancellationToken); var grp2 = await s1.AddGroupAsync(string.Empty, queueGroup: "q_empty", cancellationToken: cancellationToken); @@ -170,7 +170,7 @@ await grp1.AddEndpointAsync( await grp2.AddEndpointAsync( name: "empty1", subject: "foo.empty1", - handler: _ => ValueTask.CompletedTask, + handler: _ => default, cancellationToken: cancellationToken); // Check that the endpoints are registered correctly @@ -207,7 +207,7 @@ await grp2.AddEndpointAsync( await s2.AddEndpointAsync( name: "s2baz", subject: "s2foo.baz", - handler: _ => ValueTask.CompletedTask, + handler: _ => default, metadata: new Dictionary { { "ep-k1", "ep-v1" } }, cancellationToken: cancellationToken); From f6b55ee87e36a54f887aac50f5a0fbe9c0d5f053 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 17:28:41 +0100 Subject: [PATCH 19/47] revert net481 tests --- tests/NATS.Client.Core.Tests/AuthErrorTest.cs | 12 ++--- .../ConnectionRetryTest.cs | 30 +++++------ .../NATS.Client.Core.Tests/LowLevelApiTest.cs | 2 +- .../NATS.Client.Core.Tests.csproj | 9 +--- tests/NATS.Client.Core.Tests/NKeyTests.cs | 13 ++--- .../NatsConnectionTest.QueueGroups.cs | 50 ++++++++---------- .../NATS.Client.Core.Tests/NatsHeaderTest.cs | 22 +------- .../NATS.Client.Core.Tests/NuidWriterTests.cs | 12 ++--- tests/NATS.Client.Core.Tests/ProtocolTest.cs | 20 +++---- .../RequestReplyTest.cs | 36 ++++--------- .../NATS.Client.Core.Tests/SendBufferTest.cs | 3 -- .../NATS.Client.Core.Tests/SerializerTest.cs | 4 +- .../SubscriptionTest.cs | 52 ++++++------------- tests/NATS.Client.Core.Tests/TlsOptsTest.cs | 4 -- .../ConsumerConsumeTest.cs | 23 -------- .../ConsumerFetchTest.cs | 11 ---- .../EnumJsonTests.cs | 3 -- .../JetStreamTest.cs | 7 --- .../NATS.Client.JetStream.Tests.csproj | 4 +- .../ParseJsonTests.cs | 3 -- .../TimeSpanJsonTests.cs | 7 +-- .../KeyValueStoreTest.cs | 41 --------------- .../NATS.Client.KeyValueStore.Tests.csproj | 4 +- .../NatsKVWatcherTest.cs | 22 +------- .../CompatTest.cs | 2 +- .../NATS.Client.ObjectStore.Tests.csproj | 4 +- .../ObjectStoreTest.cs | 30 ++--------- .../NATS.Client.Services.Tests.csproj | 3 +- .../ServicesTests.cs | 12 ++--- 29 files changed, 114 insertions(+), 331 deletions(-) diff --git a/tests/NATS.Client.Core.Tests/AuthErrorTest.cs b/tests/NATS.Client.Core.Tests/AuthErrorTest.cs index 69b4484c4..bc2e38030 100644 --- a/tests/NATS.Client.Core.Tests/AuthErrorTest.cs +++ b/tests/NATS.Client.Core.Tests/AuthErrorTest.cs @@ -65,9 +65,9 @@ public async Task Auth_err_twice_will_stop_retries() // Reload config with different password { - var conf = File.ReadAllText(server.ConfigFile!) + var conf = (await File.ReadAllTextAsync(server.ConfigFile!, cts.Token)) .Replace("password: b", "password: c"); - File.WriteAllText(server.ConfigFile!, conf); + await File.WriteAllTextAsync(server.ConfigFile!, conf, cts.Token); await Task.Delay(1000, cts.Token); Process.Start("kill", $"-HUP {server.ServerProcess!.Id}"); } @@ -133,9 +133,9 @@ public async Task Auth_err_can_be_ignored_for_retires() // Reload config with different password { - var conf = File.ReadAllText(server.ConfigFile!) + var conf = (await File.ReadAllTextAsync(server.ConfigFile!, cts.Token)) .Replace("password: b", "password: c"); - File.WriteAllText(server.ConfigFile!, conf); + await File.WriteAllTextAsync(server.ConfigFile!, conf, cts.Token); await Task.Delay(1000, cts.Token); Process.Start("kill", $"-HUP {server.ServerProcess!.Id}"); } @@ -147,9 +147,9 @@ public async Task Auth_err_can_be_ignored_for_retires() // Reload config with correct password { - var conf = File.ReadAllText(server.ConfigFile!) + var conf = (await File.ReadAllTextAsync(server.ConfigFile!, cts.Token)) .Replace("password: c", "password: b"); - File.WriteAllText(server.ConfigFile!, conf); + await File.WriteAllTextAsync(server.ConfigFile!, conf, cts.Token); await Task.Delay(1000, cts.Token); Process.Start("kill", $"-HUP {server.ServerProcess!.Id}"); } diff --git a/tests/NATS.Client.Core.Tests/ConnectionRetryTest.cs b/tests/NATS.Client.Core.Tests/ConnectionRetryTest.cs index 72120ce00..132b727c3 100644 --- a/tests/NATS.Client.Core.Tests/ConnectionRetryTest.cs +++ b/tests/NATS.Client.Core.Tests/ConnectionRetryTest.cs @@ -73,26 +73,22 @@ public async Task Reconnect_doesnt_drop_partially_sent_msgs() { await using var subConn = server.CreateClientConnection(); await using var sub = await subConn.SubscribeCoreAsync>("test", cancellationToken: timeoutCts.Token); - - while (await sub.Msgs.WaitToReadAsync(timeoutCts.Token).ConfigureAwait(false)) + await foreach (var msg in sub.Msgs.ReadAllAsync(timeoutCts.Token)) { - while (sub.Msgs.TryRead(out var msg)) + using (msg.Data) { - using (msg.Data) + if (msg.Data.Length == 1) + { + Interlocked.Increment(ref subActive); + } + else if (msg.Data.Length == 2) + { + break; + } + else { - if (msg.Data.Length == 1) - { - Interlocked.Increment(ref subActive); - } - else if (msg.Data.Length == 2) - { - break; - } - else - { - Assert.Equal(msgSize, msg.Data.Length); - Interlocked.Increment(ref received); - } + Assert.Equal(msgSize, msg.Data.Length); + Interlocked.Increment(ref received); } } } diff --git a/tests/NATS.Client.Core.Tests/LowLevelApiTest.cs b/tests/NATS.Client.Core.Tests/LowLevelApiTest.cs index b43db9af4..3d7efe29b 100644 --- a/tests/NATS.Client.Core.Tests/LowLevelApiTest.cs +++ b/tests/NATS.Client.Core.Tests/LowLevelApiTest.cs @@ -80,7 +80,7 @@ protected override ValueTask ReceiveInternalAsync(string subject, string? replyT _builder.MessageReceived(sb.ToString()); } - return default; + return ValueTask.CompletedTask; } protected override void TryComplete() diff --git a/tests/NATS.Client.Core.Tests/NATS.Client.Core.Tests.csproj b/tests/NATS.Client.Core.Tests/NATS.Client.Core.Tests.csproj index 07b265f03..420d82e11 100644 --- a/tests/NATS.Client.Core.Tests/NATS.Client.Core.Tests.csproj +++ b/tests/NATS.Client.Core.Tests/NATS.Client.Core.Tests.csproj @@ -1,10 +1,10 @@  - net481;net6.0;net8.0 + net6.0;net8.0 enable false - $(NoWarn);CS8002;VSTHRD111;VSTHRD200;VSTHRD110 + $(NoWarn);CS8002 enable $(MSBuildProjectDirectory)\test.runsettings false @@ -26,11 +26,6 @@ - - - - - diff --git a/tests/NATS.Client.Core.Tests/NKeyTests.cs b/tests/NATS.Client.Core.Tests/NKeyTests.cs index 2437dd6a6..ef7f75ff5 100644 --- a/tests/NATS.Client.Core.Tests/NKeyTests.cs +++ b/tests/NATS.Client.Core.Tests/NKeyTests.cs @@ -8,19 +8,19 @@ public class NKeyTests public void NKey_Seed_And_Sign() { const string seed = "SUAAVWRZG6M5FA5VRRGWSCIHKTOJC7EWNIT4JV3FTOIPO4OBFR5WA7X5TE"; - const string expectedSignedResult = "ScXMdbqkD6/9G4/Z/lsAingexP9sfeGLZMdFe4C9oqGy21VtygL4mJRpCI0NCKC8OgfgV2XObqFB8ZnC+exdAw=="; - const string expectedPublicKey = "Fwky8ZAB/N0GGNIU6S7CawRZm1wqNnVYQ83CP2UBk88="; + const string expectedSignedResult = "49C5CC75BAA40FAFFD1B8FD9FE5B008A781EC4FF6C7DE18B64C7457B80BDA2A1B2DB556DCA02F8989469088D0D08A0BC3A07E05765CE6EA141F199C2F9EC5D03"; + const string expectedPublicKey = "170932F19001FCDD0618D214E92EC26B04599B5C2A36755843CDC23F650193CF"; var dataToSign = "Hello World"u8; var nKey = NKeys.FromSeed(seed); // Sanity check public key - var actualPublicKey = Convert.ToBase64String(nKey.PublicKey); + var actualPublicKey = Convert.ToHexString(nKey.PublicKey); Assert.Equal(expectedPublicKey, actualPublicKey); // Sign and verify var signedData = nKey.Sign(dataToSign.ToArray()); - var actual = Convert.ToBase64String(signedData); + var actual = Convert.ToHexString(signedData); Assert.Equal(expectedSignedResult, actual); } @@ -29,10 +29,11 @@ public void NKey_Seed_And_Sign() public void Sha512_Hash() { var dataToHash = "Can I Haz Hash please?"u8; - const string ExpectedHash = "uLV1BK1SKsQ69Sy4a7ENMVhAx9G4C986JSRlT3wsOwfGAa3TIOn4cKb6jaMAPPob4zATPQq+185J+SUdK7l0IQ=="; + const string ExpectedHash = "B8B57504AD522AC43AF52CB86BB10D315840C7D1B80BDF3A2524654F7C2C3B07C601ADD320E9F870A6FA8DA3003CFA1BE330133D0ABED7CE49F9251D2BB97421"; var dataArray = dataToHash.ToArray(); - var actual = Convert.ToBase64String(Sha512.Hash(dataArray, 0, dataArray.Length)); + var actual = Convert.ToHexString(Sha512.Hash(dataArray, 0, dataArray.Length)); + Assert.Equal(ExpectedHash, actual); } } diff --git a/tests/NATS.Client.Core.Tests/NatsConnectionTest.QueueGroups.cs b/tests/NATS.Client.Core.Tests/NatsConnectionTest.QueueGroups.cs index 90bc972ee..17f47ae1a 100644 --- a/tests/NATS.Client.Core.Tests/NatsConnectionTest.QueueGroups.cs +++ b/tests/NATS.Client.Core.Tests/NatsConnectionTest.QueueGroups.cs @@ -27,23 +27,20 @@ public async Task QueueGroupsTest() var reader1 = Task.Run( async () => { - while (await sub1.Msgs.WaitToReadAsync(cts.Token).ConfigureAwait(false)) + await foreach (var msg in sub1.Msgs.ReadAllAsync(cts.Token)) { - while (sub1.Msgs.TryRead(out var msg)) + if (msg.Subject == "foo.sync") { - if (msg.Subject == "foo.sync") - { - Interlocked.Exchange(ref sync1, 1); - continue; - } - - Assert.Equal($"foo.xyz{msg.Data}", msg.Subject); - lock (messages1) - messages1.Add(msg.Data); - var total = Interlocked.Increment(ref count); - if (total == messageCount) - cts.Cancel(); + Interlocked.Exchange(ref sync1, 1); + continue; } + + Assert.Equal($"foo.xyz{msg.Data}", msg.Subject); + lock (messages1) + messages1.Add(msg.Data); + var total = Interlocked.Increment(ref count); + if (total == messageCount) + cts.Cancel(); } }, cts.Token); @@ -53,23 +50,20 @@ public async Task QueueGroupsTest() var reader2 = Task.Run( async () => { - while (await sub2.Msgs.WaitToReadAsync(cts.Token).ConfigureAwait(false)) + await foreach (var msg in sub2.Msgs.ReadAllAsync(cts.Token)) { - while (sub2.Msgs.TryRead(out var msg)) + if (msg.Subject == "foo.sync") { - if (msg.Subject == "foo.sync") - { - Interlocked.Exchange(ref sync2, 1); - continue; - } - - Assert.Equal($"foo.xyz{msg.Data}", msg.Subject); - lock (messages2) - messages2.Add(msg.Data); - var total = Interlocked.Increment(ref count); - if (total == messageCount) - cts.Cancel(); + Interlocked.Exchange(ref sync2, 1); + continue; } + + Assert.Equal($"foo.xyz{msg.Data}", msg.Subject); + lock (messages2) + messages2.Add(msg.Data); + var total = Interlocked.Increment(ref count); + if (total == messageCount) + cts.Cancel(); } }, cts.Token); diff --git a/tests/NATS.Client.Core.Tests/NatsHeaderTest.cs b/tests/NATS.Client.Core.Tests/NatsHeaderTest.cs index 240a0700e..e2081e740 100644 --- a/tests/NATS.Client.Core.Tests/NatsHeaderTest.cs +++ b/tests/NATS.Client.Core.Tests/NatsHeaderTest.cs @@ -31,7 +31,7 @@ public async Task WriterTests() await pipe.Writer.FlushAsync(); var result = await pipe.Reader.ReadAtLeastAsync((int)written); Assert.True(expected.ToSpan().SequenceEqual(result.Buffer.ToSpan())); - _output.WriteLine($"Buffer:\n{result.Buffer.First.Span.Dump()}"); + _output.WriteLine($"Buffer:\n{result.Buffer.FirstSpan.Dump()}"); } [Fact] @@ -49,7 +49,7 @@ public async Task WriterEmptyTests() await pipe.Writer.FlushAsync(); var result = await pipe.Reader.ReadAtLeastAsync((int)written); Assert.True(expected.ToSpan().SequenceEqual(result.Buffer.ToSpan())); - _output.WriteLine($"Buffer:\n{result.Buffer.First.Span.Dump()}"); + _output.WriteLine($"Buffer:\n{result.Buffer.FirstSpan.Dump()}"); } [Fact] @@ -177,22 +177,4 @@ public void ParserMultiSpanTests() Assert.Equal(123, headers.Code); Assert.Equal("Test Message", headers.MessageText); } - - [Theory] - [InlineData("NATS/1.0 123 Test Message\r\nk2: v2-0\r\nk2: v2-1\r\n\r\n", true, "v2-1")] - [InlineData("NATS/1.0 123 Test Message\r\nk2: v2-0\r\n\r\n", true, "v2-0")] - [InlineData("NATS/1.0 123 Test Message\r\nk3: v2-0\r\n\r\n", false, null)] - [InlineData("NATS/1.0 123 Test Message\r\n\r\n", false, null)] - public void GetLastValueTests(string text, bool expectedResult, string? expectedLastValue) - { - var parser = new NatsHeaderParser(Encoding.UTF8); - var input = new SequenceReader(new ReadOnlySequence(Encoding.UTF8.GetBytes(text))); - var headers = new NatsHeaders(); - parser.ParseHeaders(input, headers); - - var result = headers.TryGetLastValue("k2", out var lastValue); - - Assert.Equal(result, expectedResult); - Assert.Equal(lastValue, expectedLastValue); - } } diff --git a/tests/NATS.Client.Core.Tests/NuidWriterTests.cs b/tests/NATS.Client.Core.Tests/NuidWriterTests.cs index ea8f25f5a..bf133a920 100644 --- a/tests/NATS.Client.Core.Tests/NuidWriterTests.cs +++ b/tests/NATS.Client.Core.Tests/NuidWriterTests.cs @@ -47,7 +47,7 @@ public void GetNextNuid_ReturnsNuidOfLength22_Char() // Assert ReadOnlySpan lower = buffer.Slice(0, 22); - string resultAsString = new(lower.ToArray()); + string resultAsString = new(lower); ReadOnlySpan upper = buffer.Slice(22); Assert.True(result); @@ -113,7 +113,7 @@ public void GetNextNuid_ContainsOnlyValidCharacters_Char() // Assert Assert.True(result); - string resultAsString = new(nuid.ToArray()); + string resultAsString = new(nuid); Assert.Matches("[A-z0-9]{22}", resultAsString); } @@ -142,7 +142,6 @@ public void GetNextNuid_PrefixRenewed_Char() Assert.False(firstNuid.AsSpan(0, 12).SequenceEqual(secondNuid.AsSpan(0, 12))); } -#if !NETFRAMEWORK [Fact] public void GetPrefix_PrefixAsExpected() { @@ -160,7 +159,6 @@ public void GetPrefix_PrefixAsExpected() Assert.Equal(12, prefix.Length); Assert.True("01234567B567".AsSpan().SequenceEqual(prefix)); } -#endif [Fact] public void InitAndWrite_Char() @@ -210,7 +208,7 @@ public void DifferentThreads_DifferentPrefixes() foreach (var (nuid, threadId) in nuids.ToList()) { - var prefix = new string(nuid.AsSpan(0, prefixLength).ToArray()); + var prefix = new string(nuid.AsSpan(0, prefixLength)); Assert.True(uniquePrefixes.Add(prefix), $"Unique prefix {prefix}"); Assert.True(uniqueThreadIds.Add(threadId), $"Unique thread id {threadId}"); } @@ -268,7 +266,7 @@ public void AllNuidsAreUnique_SmallSequentials() return; } - var nuid = new string(buffer.ToArray()); + var nuid = new string(buffer); if (!nuids.Add(nuid)) { @@ -310,7 +308,7 @@ public void AllNuidsAreUnique_ZeroSequential() return; } - var nuid = new string(buffer.ToArray()); + var nuid = new string(buffer); if (!nuids.Add(nuid)) { diff --git a/tests/NATS.Client.Core.Tests/ProtocolTest.cs b/tests/NATS.Client.Core.Tests/ProtocolTest.cs index 5f42f6dac..6490a9976 100644 --- a/tests/NATS.Client.Core.Tests/ProtocolTest.cs +++ b/tests/NATS.Client.Core.Tests/ProtocolTest.cs @@ -227,13 +227,10 @@ void Log(string text) var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var cancellationToken = cts.Token; var count = 0; - while (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + await foreach (var natsMsg in sub.Msgs.ReadAllAsync(cancellationToken)) { - while (sub.Msgs.TryRead(out var natsMsg)) - { - Assert.Equal(count, natsMsg.Data); - count++; - } + Assert.Equal(count, natsMsg.Data); + count++; } Assert.Equal(maxMsgs, count); @@ -264,12 +261,9 @@ void Log(string text) var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var cancellationToken = cts.Token; var count = 0; - while (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + await foreach (var unused in sub.Msgs.ReadAllAsync(cancellationToken)) { - while (sub.Msgs.TryRead(out _)) - { - count++; - } + count++; } Assert.Equal(0, count); @@ -578,9 +572,9 @@ internal override async ValueTask WriteReconnectCommandsAsync(CommandWriter comm protected override ValueTask ReceiveInternalAsync(string subject, string? replyTo, ReadOnlySequence? headersBuffer, ReadOnlySequence payloadBuffer) { - _callback(int.Parse(Encoding.UTF8.GetString(payloadBuffer.ToArray()))); + _callback(int.Parse(Encoding.UTF8.GetString(payloadBuffer))); DecrementMaxMsgs(); - return default; + return ValueTask.CompletedTask; } protected override void TryComplete() diff --git a/tests/NATS.Client.Core.Tests/RequestReplyTest.cs b/tests/NATS.Client.Core.Tests/RequestReplyTest.cs index 992e8ba30..83846cdf5 100644 --- a/tests/NATS.Client.Core.Tests/RequestReplyTest.cs +++ b/tests/NATS.Client.Core.Tests/RequestReplyTest.cs @@ -151,13 +151,9 @@ public async Task Request_reply_many_test_overall_timeout() var opts = new NatsSubOpts { Timeout = TimeSpan.FromSeconds(4) }; await using var rep = await nats.RequestSubAsync("foo", 4, replyOpts: opts, cancellationToken: cts.Token); - - while (await rep.Msgs.WaitToReadAsync(cts.Token).ConfigureAwait(false)) + await foreach (var msg in rep.Msgs.ReadAllAsync(cts.Token)) { - while (rep.Msgs.TryRead(out var msg)) - { - Assert.Equal(results[count++], msg.Data); - } + Assert.Equal(results[count++], msg.Data); } Assert.Equal(2, count); @@ -189,13 +185,9 @@ public async Task Request_reply_many_test_idle_timeout() var opts = new NatsSubOpts { IdleTimeout = TimeSpan.FromSeconds(3) }; await using var rep = await nats.RequestSubAsync("foo", 3, replyOpts: opts, cancellationToken: cts.Token); - - while (await rep.Msgs.WaitToReadAsync(cts.Token).ConfigureAwait(false)) + await foreach (var msg in rep.Msgs.ReadAllAsync(cts.Token)) { - while (rep.Msgs.TryRead(out var msg)) - { - Assert.Equal(results[count++], msg.Data); - } + Assert.Equal(results[count++], msg.Data); } Assert.Equal(2, count); @@ -223,13 +215,9 @@ public async Task Request_reply_many_test_start_up_timeout() var opts = new NatsSubOpts { StartUpTimeout = TimeSpan.FromSeconds(1) }; await using var rep = await nats.RequestSubAsync("foo", 2, replyOpts: opts, cancellationToken: cts.Token); - - while (await rep.Msgs.WaitToReadAsync(cts.Token).ConfigureAwait(false)) + await foreach (var msg in rep.Msgs.ReadAllAsync(cts.Token)) { - while (rep.Msgs.TryRead(out var msg)) - { - count++; - } + count++; } Assert.Equal(0, count); @@ -260,13 +248,9 @@ public async Task Request_reply_many_test_max_count() var opts = new NatsSubOpts { MaxMsgs = 2 }; await using var rep = await nats.RequestSubAsync("foo", 1, replyOpts: opts, cancellationToken: cts.Token); - - while (await rep.Msgs.WaitToReadAsync(cts.Token).ConfigureAwait(false)) + await foreach (var msg in rep.Msgs.ReadAllAsync(cts.Token)) { - while (rep.Msgs.TryRead(out var msg)) - { - Assert.Equal(results[count++], msg.Data); - } + Assert.Equal(results[count++], msg.Data); } Assert.Equal(2, count); @@ -310,7 +294,7 @@ public async Task Request_reply_binary_test() { static string ToStr(ReadOnlyMemory input) { - return Encoding.ASCII.GetString(input.Span.ToArray()); + return Encoding.ASCII.GetString(input.Span); } var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); @@ -329,7 +313,7 @@ static string ToStr(ReadOnlyMemory input) } }); - var writer = new NatsBufferWriter(); + var writer = new ArrayBufferWriter(); await foreach (var msg in nats.RequestManyAsync("foo", "1", cancellationToken: cts.Token)) { writer.Write(Encoding.UTF8.GetBytes(msg.Data!)); diff --git a/tests/NATS.Client.Core.Tests/SendBufferTest.cs b/tests/NATS.Client.Core.Tests/SendBufferTest.cs index 0c09ae162..973f94c16 100644 --- a/tests/NATS.Client.Core.Tests/SendBufferTest.cs +++ b/tests/NATS.Client.Core.Tests/SendBufferTest.cs @@ -2,9 +2,6 @@ using System.Net.Sockets; using Microsoft.Extensions.Logging; using NATS.Client.TestUtilities; -#if NETFRAMEWORK -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core.Tests; diff --git a/tests/NATS.Client.Core.Tests/SerializerTest.cs b/tests/NATS.Client.Core.Tests/SerializerTest.cs index 1e12f154b..45f3aeb81 100644 --- a/tests/NATS.Client.Core.Tests/SerializerTest.cs +++ b/tests/NATS.Client.Core.Tests/SerializerTest.cs @@ -97,7 +97,7 @@ void Serialize(INatsSerialize serializer, T value, string expected) { var buffer = new NatsBufferWriter(); serializer.Serialize(buffer, value); - var actual = Encoding.UTF8.GetString(buffer.WrittenMemory.Span.ToArray()); + var actual = Encoding.UTF8.GetString(buffer.WrittenMemory.Span); Assert.Equal(expected, actual); } @@ -307,7 +307,7 @@ public class TestSerializerWithEmpty : INatsSerializer { public T? Deserialize(in ReadOnlySequence buffer) => (T)(object)(buffer.IsEmpty ? new TestData("__EMPTY__") - : new TestData(Encoding.ASCII.GetString(buffer.ToArray()))); + : new TestData(Encoding.ASCII.GetString(buffer))); public void Serialize(IBufferWriter bufferWriter, T value) => throw new Exception("not used"); } diff --git a/tests/NATS.Client.Core.Tests/SubscriptionTest.cs b/tests/NATS.Client.Core.Tests/SubscriptionTest.cs index 798af5c5b..1c12f50cf 100644 --- a/tests/NATS.Client.Core.Tests/SubscriptionTest.cs +++ b/tests/NATS.Client.Core.Tests/SubscriptionTest.cs @@ -102,26 +102,20 @@ public async Task Auto_unsubscribe_on_max_messages_with_inbox_subscription_test( var cancellationToken = cts.Token; var count1 = 0; - while (await sub1.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + await foreach (var natsMsg in sub1.Msgs.ReadAllAsync(cancellationToken)) { - while (sub1.Msgs.TryRead(out var natsMsg)) - { - Assert.Equal(count1, natsMsg.Data); - count1++; - } + Assert.Equal(count1, natsMsg.Data); + count1++; } Assert.Equal(1, count1); Assert.Equal(NatsSubEndReason.MaxMsgs, ((NatsSubBase)sub1).EndReason); var count2 = 0; - while (await sub2.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + await foreach (var natsMsg in sub2.Msgs.ReadAllAsync(cancellationToken)) { - while (sub2.Msgs.TryRead(out var natsMsg)) - { - Assert.Equal(count2, natsMsg.Data); - count2++; - } + Assert.Equal(count2, natsMsg.Data); + count2++; } Assert.Equal(2, count2); @@ -148,13 +142,10 @@ public async Task Auto_unsubscribe_on_max_messages_test() var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var cancellationToken = cts.Token; var count = 0; - while (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + await foreach (var natsMsg in sub.Msgs.ReadAllAsync(cancellationToken)) { - while (sub.Msgs.TryRead(out var natsMsg)) - { - Assert.Equal(count, natsMsg.Data); - count++; - } + Assert.Equal(count, natsMsg.Data); + count++; } Assert.Equal(maxMsgs, count); @@ -175,12 +166,9 @@ public async Task Auto_unsubscribe_on_timeout_test() var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var cancellationToken = cts.Token; var count = 0; - while (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + await foreach (var unused in sub.Msgs.ReadAllAsync(cancellationToken)) { - while (sub.Msgs.TryRead(out _)) - { - count++; - } + count++; } Assert.Equal(NatsSubEndReason.Timeout, ((NatsSubBase)sub).EndReason); @@ -208,13 +196,10 @@ public async Task Auto_unsubscribe_on_idle_timeout_test() var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var cancellationToken = cts.Token; var count = 0; - while (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + await foreach (var natsMsg in sub.Msgs.ReadAllAsync(cancellationToken)) { - while (sub.Msgs.TryRead(out var natsMsg)) - { - Assert.Equal(count, natsMsg.Data); - count++; - } + Assert.Equal(count, natsMsg.Data); + count++; } Assert.Equal(NatsSubEndReason.IdleTimeout, ((NatsSubBase)sub).EndReason); @@ -239,13 +224,10 @@ public async Task Manual_unsubscribe_test() var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); var cancellationToken = cts.Token; var count = 0; - while (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + await foreach (var natsMsg in sub.Msgs.ReadAllAsync(cancellationToken)) { - while (sub.Msgs.TryRead(out var natsMsg)) - { - Assert.Equal(count, natsMsg.Data); - count++; - } + Assert.Equal(count, natsMsg.Data); + count++; } Assert.Equal(0, count); diff --git a/tests/NATS.Client.Core.Tests/TlsOptsTest.cs b/tests/NATS.Client.Core.Tests/TlsOptsTest.cs index faf74127d..16a747043 100644 --- a/tests/NATS.Client.Core.Tests/TlsOptsTest.cs +++ b/tests/NATS.Client.Core.Tests/TlsOptsTest.cs @@ -1,5 +1,3 @@ -#if !NETFRAMEWORK - using System.Net.Security; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -201,5 +199,3 @@ static async Task ValidateAsync(NatsServer natsServer, NatsTlsOpts opts) } } } - -#endif diff --git a/tests/NATS.Client.JetStream.Tests/ConsumerConsumeTest.cs b/tests/NATS.Client.JetStream.Tests/ConsumerConsumeTest.cs index 58d7c2019..d01d6831a 100644 --- a/tests/NATS.Client.JetStream.Tests/ConsumerConsumeTest.cs +++ b/tests/NATS.Client.JetStream.Tests/ConsumerConsumeTest.cs @@ -2,9 +2,6 @@ using Microsoft.Extensions.Logging; using NATS.Client.Core.Tests; using NATS.Client.JetStream.Models; -#if NETFRAMEWORK -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.JetStream.Tests; @@ -80,11 +77,7 @@ public async Task Consume_msgs_test() var consumer = (NatsJSConsumer)await js.GetConsumerAsync("s1", "c1", cts.Token); var count = 0; await using var cc = await consumer.ConsumeInternalAsync(serializer: TestDataJsonSerializer.Default, consumerOpts, cancellationToken: cts.Token); -#if NETFRAMEWORK - await foreach (var msg in cc.Msgs.ReadAllLoopAsync(cts.Token)) -#else await foreach (var msg in cc.Msgs.ReadAllAsync(cts.Token)) -#endif { await msg.AckAsync(cancellationToken: cts.Token); Assert.Equal(count, msg.Data!.Test); @@ -157,11 +150,7 @@ public async Task Consume_idle_heartbeat_test() var consumer = (NatsJSConsumer)await js.GetConsumerAsync("s1", "c1", cts.Token); var count = 0; var cc = await consumer.ConsumeInternalAsync(serializer: TestDataJsonSerializer.Default, consumerOpts, cancellationToken: cts.Token); -#if NETFRAMEWORK - await foreach (var msg in cc.Msgs.ReadAllLoopAsync(cts.Token)) -#else await foreach (var msg in cc.Msgs.ReadAllAsync(cts.Token)) -#endif { await msg.AckAsync(cancellationToken: cts.Token); Assert.Equal(count, msg.Data!.Test); @@ -231,11 +220,7 @@ public async Task Consume_reconnect_test() var readerTask = Task.Run(async () => { var count = 0; -#if NETFRAMEWORK - await foreach (var msg in cc.Msgs.ReadAllLoopAsync(cts.Token)) -#else await foreach (var msg in cc.Msgs.ReadAllAsync(cts.Token)) -#endif { await msg.AckAsync(cancellationToken: cts.Token); Assert.Equal(count, msg.Data!.Test); @@ -312,11 +297,7 @@ public async Task Consume_dispose_test() var signal2 = new WaitSignal(); var reader = Task.Run(async () => { -#if NETFRAMEWORK - await foreach (var msg in cc.Msgs.ReadAllLoopAsync(cts.Token)) -#else await foreach (var msg in cc.Msgs.ReadAllAsync(cts.Token)) -#endif { await msg.AckAsync(cancellationToken: cts.Token); signal1.Pulse(); @@ -394,11 +375,7 @@ public async Task Consume_stop_test() var signal2 = new WaitSignal(); var reader = Task.Run(async () => { -#if NETFRAMEWORK - await foreach (var msg in cc.Msgs.ReadAllLoopAsync(cts.Token)) -#else await foreach (var msg in cc.Msgs.ReadAllAsync(cts.Token)) -#endif { await msg.AckAsync(cancellationToken: cts.Token); signal1.Pulse(); diff --git a/tests/NATS.Client.JetStream.Tests/ConsumerFetchTest.cs b/tests/NATS.Client.JetStream.Tests/ConsumerFetchTest.cs index 70115f803..fb14c5904 100644 --- a/tests/NATS.Client.JetStream.Tests/ConsumerFetchTest.cs +++ b/tests/NATS.Client.JetStream.Tests/ConsumerFetchTest.cs @@ -1,8 +1,5 @@ using NATS.Client.Core.Tests; using NATS.Client.JetStream.Models; -#if NETFRAMEWORK -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.JetStream.Tests; @@ -32,11 +29,7 @@ public async Task Fetch_test() var count = 0; await using var fc = await consumer.FetchInternalAsync(serializer: TestDataJsonSerializer.Default, opts: new NatsJSFetchOpts { MaxMsgs = 10 }, cancellationToken: cts.Token); -#if NETFRAMEWORK - await foreach (var msg in fc.Msgs.ReadAllLoopAsync(cts.Token)) -#else await foreach (var msg in fc.Msgs.ReadAllAsync(cts.Token)) -#endif { await msg.AckAsync(cancellationToken: cts.Token); Assert.Equal(count, msg.Data!.Test); @@ -105,11 +98,7 @@ public async Task Fetch_dispose_test() var signal2 = new WaitSignal(); var reader = Task.Run(async () => { -#if NETFRAMEWORK - await foreach (var msg in fc.Msgs.ReadAllLoopAsync(cts.Token)) -#else await foreach (var msg in fc.Msgs.ReadAllAsync(cts.Token)) -#endif { await msg.AckAsync(cancellationToken: cts.Token); signal1.Pulse(); diff --git a/tests/NATS.Client.JetStream.Tests/EnumJsonTests.cs b/tests/NATS.Client.JetStream.Tests/EnumJsonTests.cs index aed6ffc21..94bc50299 100644 --- a/tests/NATS.Client.JetStream.Tests/EnumJsonTests.cs +++ b/tests/NATS.Client.JetStream.Tests/EnumJsonTests.cs @@ -2,9 +2,6 @@ using System.Text; using NATS.Client.JetStream.Internal; using NATS.Client.JetStream.Models; -#if NETFRAMEWORK -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.JetStream.Tests; diff --git a/tests/NATS.Client.JetStream.Tests/JetStreamTest.cs b/tests/NATS.Client.JetStream.Tests/JetStreamTest.cs index 4201d5653..4cbc9cd6f 100644 --- a/tests/NATS.Client.JetStream.Tests/JetStreamTest.cs +++ b/tests/NATS.Client.JetStream.Tests/JetStreamTest.cs @@ -1,8 +1,5 @@ using NATS.Client.Core.Tests; using NATS.Client.JetStream.Models; -#if NETFRAMEWORK -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.JetStream.Tests; @@ -122,11 +119,7 @@ public async Task Create_stream_test() serializer: TestDataJsonSerializer.Default, opts: new NatsJSConsumeOpts { MaxMsgs = 100 }, cancellationToken: cts2.Token); -#if NETFRAMEWORK - await foreach (var msg in cc.Msgs.ReadAllLoopAsync(cts2.Token)) -#else await foreach (var msg in cc.Msgs.ReadAllAsync(cts2.Token)) -#endif { messages.Add(msg); diff --git a/tests/NATS.Client.JetStream.Tests/NATS.Client.JetStream.Tests.csproj b/tests/NATS.Client.JetStream.Tests/NATS.Client.JetStream.Tests.csproj index 8f368ca3d..be86630aa 100644 --- a/tests/NATS.Client.JetStream.Tests/NATS.Client.JetStream.Tests.csproj +++ b/tests/NATS.Client.JetStream.Tests/NATS.Client.JetStream.Tests.csproj @@ -1,10 +1,9 @@ - net481;net6.0;net8.0 + net6.0;net8.0 enable enable - $(NoWarn);CS8002;CS1685;VSTHRD111;VSTHRD200;VSTHRD110 false true @@ -30,7 +29,6 @@ - diff --git a/tests/NATS.Client.JetStream.Tests/ParseJsonTests.cs b/tests/NATS.Client.JetStream.Tests/ParseJsonTests.cs index d49641db8..6e57aaf28 100644 --- a/tests/NATS.Client.JetStream.Tests/ParseJsonTests.cs +++ b/tests/NATS.Client.JetStream.Tests/ParseJsonTests.cs @@ -2,9 +2,6 @@ using System.Text; using NATS.Client.JetStream.Internal; using NATS.Client.JetStream.Models; -#if NETFRAMEWORK -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.JetStream.Tests; diff --git a/tests/NATS.Client.JetStream.Tests/TimeSpanJsonTests.cs b/tests/NATS.Client.JetStream.Tests/TimeSpanJsonTests.cs index c103b1274..08a927a2f 100644 --- a/tests/NATS.Client.JetStream.Tests/TimeSpanJsonTests.cs +++ b/tests/NATS.Client.JetStream.Tests/TimeSpanJsonTests.cs @@ -2,9 +2,6 @@ using System.Text; using NATS.Client.JetStream.Internal; using NATS.Client.JetStream.Models; -#if NETFRAMEWORK -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.JetStream.Tests; @@ -22,7 +19,7 @@ public void ConsumerConfigAckWait_test(string value, string expected) var bw = new NatsBufferWriter(); serializer.Serialize(bw, new ConsumerConfig { AckWait = time }); - var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); + var json = Encoding.UTF8.GetString(bw.WrittenSpan); Assert.Matches(expected, json); var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); @@ -42,7 +39,7 @@ public void ConsumerConfigIdleHeartbeat_test(string value, string expected) var bw = new NatsBufferWriter(); serializer.Serialize(bw, new ConsumerConfig { IdleHeartbeat = time }); - var json = Encoding.UTF8.GetString(bw.WrittenSpan.ToArray()); + var json = Encoding.UTF8.GetString(bw.WrittenSpan); Assert.Matches(expected, json); var result = serializer.Deserialize(new ReadOnlySequence(bw.WrittenMemory)); diff --git a/tests/NATS.Client.KeyValueStore.Tests/KeyValueStoreTest.cs b/tests/NATS.Client.KeyValueStore.Tests/KeyValueStoreTest.cs index 0f608099d..00dc5aa19 100644 --- a/tests/NATS.Client.KeyValueStore.Tests/KeyValueStoreTest.cs +++ b/tests/NATS.Client.KeyValueStore.Tests/KeyValueStoreTest.cs @@ -607,45 +607,4 @@ public async Task Validate_keys() await Assert.ThrowsAsync(async () => await store.PurgeAsync(key)); } } - - [Fact] - public async Task TestDirectMessageRepublishedSubject() - { - var streamBucketName = "sb-" + NuidWriter.NewNuid(); - var subject = "test"; - var streamSubject = subject + ".>"; - var publishSubject1 = subject + ".one"; - var publishSubject2 = subject + ".two"; - var publishSubject3 = subject + ".three"; - var republishDest = "$KV." + streamBucketName + ".>"; - - var streamConfig = new StreamConfig(streamBucketName, new[] { streamSubject }) { Republish = new Republish { Src = ">", Dest = republishDest } }; - - await using var server = NatsServer.StartJS(); - await using var nats = server.CreateClientConnection(); - var js = new NatsJSContext(nats); - var kv = new NatsKVContext(js); - - var store = await kv.CreateStoreAsync(streamBucketName); - await js.CreateStreamAsync(streamConfig); - - await nats.PublishAsync(publishSubject1, "uno"); - await js.PublishAsync(publishSubject2, "dos"); - await store.PutAsync(publishSubject3, "tres"); - - var kve1 = await store.GetEntryAsync(publishSubject1); - Assert.Equal(streamBucketName, kve1.Bucket); - Assert.Equal(publishSubject1, kve1.Key); - Assert.Equal("uno", kve1.Value); - - var kve2 = await store.GetEntryAsync(publishSubject2); - Assert.Equal(streamBucketName, kve2.Bucket); - Assert.Equal(publishSubject2, kve2.Key); - Assert.Equal("dos", kve2.Value); - - var kve3 = await store.GetEntryAsync(publishSubject3); - Assert.Equal(streamBucketName, kve3.Bucket); - Assert.Equal(publishSubject3, kve3.Key); - Assert.Equal("tres", kve3.Value); - } } diff --git a/tests/NATS.Client.KeyValueStore.Tests/NATS.Client.KeyValueStore.Tests.csproj b/tests/NATS.Client.KeyValueStore.Tests/NATS.Client.KeyValueStore.Tests.csproj index 053574b86..63bcf2bc1 100644 --- a/tests/NATS.Client.KeyValueStore.Tests/NATS.Client.KeyValueStore.Tests.csproj +++ b/tests/NATS.Client.KeyValueStore.Tests/NATS.Client.KeyValueStore.Tests.csproj @@ -1,10 +1,9 @@ - net481;net6.0;net8.0 + net6.0;net8.0 enable enable - $(NoWarn);CS8002;CS1685;VSTHRD111;VSTHRD200;VSTHRD110 false true @@ -31,7 +30,6 @@ - diff --git a/tests/NATS.Client.KeyValueStore.Tests/NatsKVWatcherTest.cs b/tests/NATS.Client.KeyValueStore.Tests/NatsKVWatcherTest.cs index 76fc521c6..a66ca1f5f 100644 --- a/tests/NATS.Client.KeyValueStore.Tests/NatsKVWatcherTest.cs +++ b/tests/NATS.Client.KeyValueStore.Tests/NatsKVWatcherTest.cs @@ -2,9 +2,6 @@ using Microsoft.Extensions.Logging; using NATS.Client.Core.Tests; using NATS.Client.KeyValueStore.Internal; -#if NETFRAMEWORK -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.KeyValueStore.Tests; @@ -41,11 +38,7 @@ public async Task Watcher_reconnect_with_history() var count = 0; -#if NETFRAMEWORK - await foreach (var entry in watcher.Entries.ReadAllLoopAsync(cancellationToken)) -#else await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken)) -#endif { using (entry.Value) { @@ -77,11 +70,8 @@ public async Task Watcher_reconnect_with_history() await store1.PutAsync("k1.p1", 4, cancellationToken: cancellationToken); await store1.PutAsync("k1.p1", 5, cancellationToken: cancellationToken); await store1.PutAsync("k1.p1", 6, cancellationToken: cancellationToken); -#if NETFRAMEWORK - await foreach (var entry in watcher.Entries.ReadAllLoopAsync(cancellationToken)) -#else + await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken)) -#endif { if (entry.Value is { } memoryOwner) { @@ -249,11 +239,7 @@ public async Task Watcher_timeout_reconnect() var consumer1 = ((NatsKVWatcher>)watcher).Consumer; -#if NETFRAMEWORK - await foreach (var entry in watcher.Entries.ReadAllLoopAsync(cancellationToken)) -#else await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken)) -#endif { using (entry.Value) { @@ -294,11 +280,7 @@ await Retry.Until( retryDelay: TimeSpan.FromSeconds(1), timeout: timeout); -#if NETFRAMEWORK - await foreach (var entry in watcher.Entries.ReadAllLoopAsync(cancellationToken)) -#else await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken)) -#endif { if (entry.Value is { } memoryOwner) { @@ -546,7 +528,7 @@ public async Task Watch_resume_at_revision() OnNoData = (_) => { noData = true; - return new ValueTask(true); + return ValueTask.FromResult(true); }, }; diff --git a/tests/NATS.Client.ObjectStore.Tests/CompatTest.cs b/tests/NATS.Client.ObjectStore.Tests/CompatTest.cs index 9232ef9c6..5f984c383 100644 --- a/tests/NATS.Client.ObjectStore.Tests/CompatTest.cs +++ b/tests/NATS.Client.ObjectStore.Tests/CompatTest.cs @@ -29,7 +29,7 @@ public async Task Headers_serialization() // Verify PUT { - using var stream = new MemoryStream("Hello, World!"u8.ToArray()); + await using var stream = new MemoryStream("Hello, World!"u8.ToArray()); await store.PutAsync(objMeta, stream, cancellationToken: cancellationToken); // nats obj info b1 o1 diff --git a/tests/NATS.Client.ObjectStore.Tests/NATS.Client.ObjectStore.Tests.csproj b/tests/NATS.Client.ObjectStore.Tests/NATS.Client.ObjectStore.Tests.csproj index 02701bfa8..0139097b5 100644 --- a/tests/NATS.Client.ObjectStore.Tests/NATS.Client.ObjectStore.Tests.csproj +++ b/tests/NATS.Client.ObjectStore.Tests/NATS.Client.ObjectStore.Tests.csproj @@ -1,10 +1,9 @@ - net481;net6.0;net8.0 + net6.0;net8.0 enable enable - $(NoWarn);CS8002;CS1685;VSTHRD111;VSTHRD200;VSTHRD110 false true @@ -31,7 +30,6 @@ - diff --git a/tests/NATS.Client.ObjectStore.Tests/ObjectStoreTest.cs b/tests/NATS.Client.ObjectStore.Tests/ObjectStoreTest.cs index e4dc47f00..0f8250d1b 100644 --- a/tests/NATS.Client.ObjectStore.Tests/ObjectStoreTest.cs +++ b/tests/NATS.Client.ObjectStore.Tests/ObjectStoreTest.cs @@ -5,9 +5,6 @@ using NATS.Client.ObjectStore.Internal; using NATS.Client.ObjectStore.Models; using NATS.Client.Serializers.Json; -#if NETFRAMEWORK -using Random = NATS.Client.Core.Internal.NetStandardExtensions.Random; -#endif namespace NATS.Client.ObjectStore.Tests; @@ -76,7 +73,7 @@ public async Task Put_chunks() var data = await store.GetInfoAsync("k1", cancellationToken: cancellationToken); - var sha = Base64UrlEncoder.Encode(Hash256(buffer)); + var sha = Base64UrlEncoder.Encode(SHA256.HashData(buffer)); var size = buffer.Length; var chunks = Math.Ceiling(size / 10.0); @@ -96,7 +93,7 @@ public async Task Put_chunks() var data = await store.GetInfoAsync("k2", cancellationToken: cancellationToken); - var sha = Base64UrlEncoder.Encode(Hash256(buffer)); + var sha = Base64UrlEncoder.Encode(SHA256.HashData(buffer)); var size = buffer.Length; var chunks = Math.Ceiling(size / 10.0); @@ -237,14 +234,14 @@ public async Task Put_and_get_large_file() const string filename = $"_tmp_test_file_{nameof(Put_and_get_large_file)}.bin"; var filename1 = $"{filename}.1"; - File.WriteAllBytes(filename, data); + await File.WriteAllBytesAsync(filename, data, cancellationToken); await store.PutAsync("my/random/data.bin", File.OpenRead(filename), cancellationToken: cancellationToken); await store.GetAsync("my/random/data.bin", File.OpenWrite(filename1), cancellationToken: cancellationToken); - var hash = Convert.ToBase64String(Hash256(File.ReadAllBytes(filename))); - var hash1 = Convert.ToBase64String(Hash256(File.ReadAllBytes(filename1))); + var hash = Convert.ToBase64String(SHA256.HashData(await File.ReadAllBytesAsync(filename, cancellationToken))); + var hash1 = Convert.ToBase64String(SHA256.HashData(await File.ReadAllBytesAsync(filename1, cancellationToken))); Assert.Equal(hash, hash1); } @@ -444,21 +441,4 @@ public async Task Put_get_serialization_when_default_serializer_is_not_used() var info = await store.GetInfoAsync("k1", cancellationToken: cancellationToken); Assert.Equal("k1", info.Name); } - - private static byte[] Hash256(byte[] data) - { -#if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(data); - ReadOnlySpan dataSpan = data; - return SHA256.HashData(dataSpan); -#else - if (data == null) - { - throw new ArgumentNullException(nameof(data)); - } - - using var s = SHA256.Create(); - return s.ComputeHash(data); -#endif - } } diff --git a/tests/NATS.Client.Services.Tests/NATS.Client.Services.Tests.csproj b/tests/NATS.Client.Services.Tests/NATS.Client.Services.Tests.csproj index c9aa45633..0f784d731 100644 --- a/tests/NATS.Client.Services.Tests/NATS.Client.Services.Tests.csproj +++ b/tests/NATS.Client.Services.Tests/NATS.Client.Services.Tests.csproj @@ -1,10 +1,9 @@ - net481;net6.0;net8.0 + net6.0;net8.0 enable enable - $(NoWarn);CS8002;CS1685;VSTHRD111;VSTHRD200;VSTHRD110 false true diff --git a/tests/NATS.Client.Services.Tests/ServicesTests.cs b/tests/NATS.Client.Services.Tests/ServicesTests.cs index bdb1b6416..63f3a0a1d 100644 --- a/tests/NATS.Client.Services.Tests/ServicesTests.cs +++ b/tests/NATS.Client.Services.Tests/ServicesTests.cs @@ -144,25 +144,25 @@ public async Task Add_groups_metadata_and_stats() await s1.AddEndpointAsync( name: "baz", subject: "foo.baz", - handler: _ => default, + handler: _ => ValueTask.CompletedTask, cancellationToken: cancellationToken); await s1.AddEndpointAsync( subject: "foo.bar1", - handler: _ => default, + handler: _ => ValueTask.CompletedTask, cancellationToken: cancellationToken); var grp1 = await s1.AddGroupAsync("grp1", cancellationToken: cancellationToken); await grp1.AddEndpointAsync( name: "e1", - handler: _ => default, + handler: _ => ValueTask.CompletedTask, cancellationToken: cancellationToken); await grp1.AddEndpointAsync( name: "e2", subject: "foo.bar2", - handler: _ => default, + handler: _ => ValueTask.CompletedTask, cancellationToken: cancellationToken); var grp2 = await s1.AddGroupAsync(string.Empty, queueGroup: "q_empty", cancellationToken: cancellationToken); @@ -170,7 +170,7 @@ await grp1.AddEndpointAsync( await grp2.AddEndpointAsync( name: "empty1", subject: "foo.empty1", - handler: _ => default, + handler: _ => ValueTask.CompletedTask, cancellationToken: cancellationToken); // Check that the endpoints are registered correctly @@ -207,7 +207,7 @@ await grp2.AddEndpointAsync( await s2.AddEndpointAsync( name: "s2baz", subject: "s2foo.baz", - handler: _ => default, + handler: _ => ValueTask.CompletedTask, metadata: new Dictionary { { "ep-k1", "ep-v1" } }, cancellationToken: cancellationToken); From 3168197eaa877530f0d8487dfd2d650aefdff4e8 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 17:34:16 +0100 Subject: [PATCH 20/47] use read loop --- .../NatsConnection.RequestReply.cs | 34 ++++++------------- .../NatsConnection.Subscribe.cs | 13 +++---- 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/src/NATS.Client.Core/NatsConnection.RequestReply.cs b/src/NATS.Client.Core/NatsConnection.RequestReply.cs index e8a474451..41f177a59 100644 --- a/src/NATS.Client.Core/NatsConnection.RequestReply.cs +++ b/src/NATS.Client.Core/NatsConnection.RequestReply.cs @@ -2,6 +2,9 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using NATS.Client.Core.Internal; +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Core; @@ -43,20 +46,15 @@ public async ValueTask> RequestAsync( .ConfigureAwait(false); #if NETSTANDARD2_0 - while (await sub1.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) - { - while (sub1.Msgs.TryRead(out var msg)) - { - return msg; - } - } + await foreach (var msg in sub1.Msgs.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) #else // Prefer ReadAllAsync() since underlying ActivityEndingMsgReader maintains GCHandle for the subscription more efficiently. await foreach (var msg in sub1.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) +#endif { return msg; } -#endif + throw new NatsNoReplyException(); } catch (Exception e) @@ -71,20 +69,14 @@ public async ValueTask> RequestAsync( .ConfigureAwait(false); #if NETSTANDARD2_0 - while (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) - { - while (sub.Msgs.TryRead(out var msg)) - { - return msg; - } - } + await foreach (var msg in sub.Msgs.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) #else // Prefer ReadAllAsync() since underlying ActivityEndingMsgReader maintains GCHandle for the subscription more efficiently. await foreach (var msg in sub.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) +#endif { return msg; } -#endif throw new NatsNoReplyException(); } @@ -121,20 +113,14 @@ public async IAsyncEnumerable> RequestManyAsync> SubscribeAsync(string subject, stri // We don't cancel the channel reader here because we want to keep reading until the subscription // channel writer completes so that messages left in the channel can be consumed before exit the loop. #if NETSTANDARD2_0 - while (await sub.Msgs.WaitToReadAsync(CancellationToken.None).ConfigureAwait(false)) - { - while (sub.Msgs.TryRead(out var msg)) - { - yield return msg; - } - } + await foreach (var msg in sub.Msgs.ReadAllLoopAsync(CancellationToken.None).ConfigureAwait(false)) #else // Prefer ReadAllAsync() since underlying ActivityEndingMsgReader maintains GCHandle for the subscription more efficiently. await foreach (var msg in sub.Msgs.ReadAllAsync(CancellationToken.None).ConfigureAwait(false)) +#endif { yield return msg; } -#endif } /// From 123514f41dbcf321b7c24d858403d54307b914fa Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 20:24:54 +0100 Subject: [PATCH 21/47] tidy-up --- .../Commands/CommandWriter.cs | 3 +- src/NATS.Client.Core/INatsSerialize.cs | 2 +- .../Internal/NatsReadProtocolProcessor.cs | 2 +- src/NATS.Client.Core/Internal/NuidWriter.cs | 2 +- src/NATS.Client.Core/Internal/SocketReader.cs | 2 +- .../Internal/SslStreamConnection.cs | 2 +- .../Internal/TcpConnection.cs | 2 +- .../Internal/WebSocketConnection.cs | 2 +- src/NATS.Client.Core/Internal/netstandard.cs | 32 ++++--- src/NATS.Client.Core/NATS.Client.Core.csproj | 39 ++++---- src/NATS.Client.Core/NatsBufferWriter.cs | 8 +- .../NatsConnection.LowLevelApi.cs | 2 +- src/NATS.Client.Core/NatsConnection.Ping.cs | 3 +- .../NatsConnection.Publish.cs | 2 +- src/NATS.Client.Core/NatsConnection.cs | 6 +- src/NATS.Client.Core/NatsException.cs | 4 +- src/NATS.Client.Core/NatsHeaderParser.cs | 2 +- .../NATS.Client.Hosting.csproj | 6 +- .../Internal/NatsJSConsume.cs | 20 ++-- .../Internal/NatsJSOrderedConsume.cs | 20 ++-- .../NATS.Client.JetStream.csproj | 15 ++- src/NATS.Client.JetStream/NatsJSConsumer.cs | 26 ++--- src/NATS.Client.JetStream/NatsJSContext.cs | 50 ++++++---- .../NATS.Client.KeyValueStore.csproj | 9 +- src/NATS.Client.KeyValueStore/NatsKVStore.cs | 63 ++++++------ .../NATS.Client.ObjectStore.csproj | 9 +- src/NATS.Client.ObjectStore/NatsObjStore.cs | 95 +++++++++---------- .../NATS.Client.Serializers.Json.csproj | 3 +- .../Internal/SvcListener.cs | 14 ++- .../NATS.Client.Services.csproj | 9 +- src/NATS.Client.Services/NatsSvcEndPoint.cs | 84 ++++++++-------- src/NATS.Client.Services/NatsSvcServer.cs | 94 +++++++++--------- ...sions.Microsoft.DependencyInjection.csproj | 1 - src/NATS.Net/NATS.Net.csproj | 3 +- .../NATS.Client.Core.Tests/NatsHeaderTest.cs | 18 ++++ .../KeyValueStoreTest.cs | 41 ++++++++ .../NATS.Client.TestUtilities.csproj | 33 ++++--- tests/NATS.Client.TestUtilities/Utils.cs | 27 +++--- 38 files changed, 427 insertions(+), 328 deletions(-) diff --git a/src/NATS.Client.Core/Commands/CommandWriter.cs b/src/NATS.Client.Core/Commands/CommandWriter.cs index 2f280d7e5..6f12d944c 100644 --- a/src/NATS.Client.Core/Commands/CommandWriter.cs +++ b/src/NATS.Client.Core/Commands/CommandWriter.cs @@ -4,7 +4,7 @@ using System.Threading.Channels; using Microsoft.Extensions.Logging; using NATS.Client.Core.Internal; -#if !NET6_0_OR_GREATER +#if NETSTANDARD2_0 || NETSTANDARD2_1 using NATS.Client.Core.Internal.NetStandardExtensions; #endif @@ -764,6 +764,7 @@ private async ValueTask PongStateMachineAsync(bool lockHeld, CancellationToken c await _flushTask.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); } #endif + _protocolWriter.WritePong(_pipeWriter); EnqueueCommand(); } diff --git a/src/NATS.Client.Core/INatsSerialize.cs b/src/NATS.Client.Core/INatsSerialize.cs index 0739c912d..ff1958a20 100644 --- a/src/NATS.Client.Core/INatsSerialize.cs +++ b/src/NATS.Client.Core/INatsSerialize.cs @@ -4,7 +4,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; -#if !NET6_0_OR_GREATER +#if NETSTANDARD2_0 || NETSTANDARD2_1 using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs b/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs index da999ff07..460179f78 100644 --- a/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs +++ b/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs @@ -8,7 +8,7 @@ using System.Text.Json; using Microsoft.Extensions.Logging; using NATS.Client.Core.Commands; -#if !NET6_0_OR_GREATER +#if NETSTANDARD2_0 || NETSTANDARD2_1 using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Core/Internal/NuidWriter.cs b/src/NATS.Client.Core/Internal/NuidWriter.cs index 892115910..894175624 100644 --- a/src/NATS.Client.Core/Internal/NuidWriter.cs +++ b/src/NATS.Client.Core/Internal/NuidWriter.cs @@ -2,7 +2,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; -#if !NET6_0_OR_GREATER +#if NETSTANDARD2_0 || NETSTANDARD2_1 using Random = NATS.Client.Core.Internal.NetStandardExtensions.Random; #endif diff --git a/src/NATS.Client.Core/Internal/SocketReader.cs b/src/NATS.Client.Core/Internal/SocketReader.cs index 7802c2037..e9e1cc9b1 100644 --- a/src/NATS.Client.Core/Internal/SocketReader.cs +++ b/src/NATS.Client.Core/Internal/SocketReader.cs @@ -2,7 +2,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; -#if !NET6_0_OR_GREATER +#if NETSTANDARD2_0 || NETSTANDARD2_1 using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Core/Internal/SslStreamConnection.cs b/src/NATS.Client.Core/Internal/SslStreamConnection.cs index 1c3df8e0d..cfb416b0f 100644 --- a/src/NATS.Client.Core/Internal/SslStreamConnection.cs +++ b/src/NATS.Client.Core/Internal/SslStreamConnection.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Logging; #if NETSTANDARD2_0 -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously +#pragma warning disable CS1998 #endif namespace NATS.Client.Core.Internal; diff --git a/src/NATS.Client.Core/Internal/TcpConnection.cs b/src/NATS.Client.Core/Internal/TcpConnection.cs index e4080e39f..8ea3763a8 100644 --- a/src/NATS.Client.Core/Internal/TcpConnection.cs +++ b/src/NATS.Client.Core/Internal/TcpConnection.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; -#if !NET6_0_OR_GREATER +#if NETSTANDARD2_0 || NETSTANDARD2_1 using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Core/Internal/WebSocketConnection.cs b/src/NATS.Client.Core/Internal/WebSocketConnection.cs index 799c2fba4..e430d0d88 100644 --- a/src/NATS.Client.Core/Internal/WebSocketConnection.cs +++ b/src/NATS.Client.Core/Internal/WebSocketConnection.cs @@ -1,7 +1,7 @@ using System.Net.Sockets; using System.Net.WebSockets; using System.Runtime.CompilerServices; -#if !NET6_0_OR_GREATER +#if NETSTANDARD2_0 || NETSTANDARD2_1 using System.Runtime.InteropServices; #endif diff --git a/src/NATS.Client.Core/Internal/netstandard.cs b/src/NATS.Client.Core/Internal/netstandard.cs index 0319da02b..7e12e07fc 100644 --- a/src/NATS.Client.Core/Internal/netstandard.cs +++ b/src/NATS.Client.Core/Internal/netstandard.cs @@ -9,6 +9,7 @@ #if NETSTANDARD2_0 || NETSTANDARD2_1 +// Enable init only setters namespace System.Runtime.CompilerServices { internal static class IsExternalInit @@ -23,27 +24,12 @@ namespace NATS.Client.Core.Internal.NetStandardExtensions using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; - using System.Threading.Channels; [StructLayout(LayoutKind.Sequential, Size = 1)] internal readonly struct VoidResult { } - internal static class ChannelReaderExtensions - { - public static async IAsyncEnumerable ReadAllLoopAsync(this ChannelReader reader, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) - { - while (reader.TryRead(out var msg)) - { - yield return msg; - } - } - } - } - internal sealed class TaskCompletionSource : TaskCompletionSource { public TaskCompletionSource(TaskCreationOptions creationOptions) @@ -257,7 +243,9 @@ internal static void GetBytes(this Encoding encoding, string chars, IBufferWrite namespace NATS.Client.Core.Internal.NetStandardExtensions { + using System.Runtime.CompilerServices; using System.Text; + using System.Threading.Channels; internal static class EncodingExtensions { @@ -290,6 +278,20 @@ internal static void Deconstruct(this KeyValuePair k value = kv.Value; } } + + internal static class ChannelReaderExtensions + { + public static async IAsyncEnumerable ReadAllLoopAsync(this ChannelReader reader, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) + { + while (reader.TryRead(out var msg)) + { + yield return msg; + } + } + } + } } #endif diff --git a/src/NATS.Client.Core/NATS.Client.Core.csproj b/src/NATS.Client.Core/NATS.Client.Core.csproj index 9f2e122d9..1f8757290 100644 --- a/src/NATS.Client.Core/NATS.Client.Core.csproj +++ b/src/NATS.Client.Core/NATS.Client.Core.csproj @@ -1,9 +1,6 @@  - - - netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable @@ -15,26 +12,24 @@ true - - true - + + true + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/src/NATS.Client.Core/NatsBufferWriter.cs b/src/NATS.Client.Core/NatsBufferWriter.cs index dffe2be0c..f74c00a2c 100644 --- a/src/NATS.Client.Core/NatsBufferWriter.cs +++ b/src/NATS.Client.Core/NatsBufferWriter.cs @@ -1,5 +1,4 @@ // adapted from https://github.com/CommunityToolkit/dotnet/blob/main/src/CommunityToolkit.HighPerformance/Buffers/NatsBufferWriter%7BT%7D.cs -#pragma warning disable SA1116, SA1117 using System.Buffers; using System.Diagnostics.CodeAnalysis; @@ -392,11 +391,14 @@ internal static class NatsBufferWriterExtensions /// Indicates whether the contents of the array should be cleared before reuse. /// Thrown when is less than 0. /// When this method returns, the caller must not use any references to the old array anymore. - public static void Resize(this ArrayPool pool, + public static void Resize( + this ArrayPool pool, #if !NETSTANDARD2_0 [NotNull] #endif - ref T[]? array, int newSize, bool clearArray = false) + ref T[]? array, + int newSize, + bool clearArray = false) { // If the old array is null, just create a new one with the requested size if (array is null) diff --git a/src/NATS.Client.Core/NatsConnection.LowLevelApi.cs b/src/NATS.Client.Core/NatsConnection.LowLevelApi.cs index ba085818d..c67aac5e2 100644 --- a/src/NATS.Client.Core/NatsConnection.LowLevelApi.cs +++ b/src/NATS.Client.Core/NatsConnection.LowLevelApi.cs @@ -1,4 +1,4 @@ -#if !NET6_0_OR_GREATER +#if NETSTANDARD2_0 || NETSTANDARD2_1 using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Core/NatsConnection.Ping.cs b/src/NATS.Client.Core/NatsConnection.Ping.cs index f518a0334..c570bb5c1 100644 --- a/src/NATS.Client.Core/NatsConnection.Ping.cs +++ b/src/NATS.Client.Core/NatsConnection.Ping.cs @@ -1,7 +1,6 @@ using System.Runtime.CompilerServices; using NATS.Client.Core.Commands; -using NATS.Client.Core.Internal; -#if !NET6_0_OR_GREATER +#if NETSTANDARD2_0 || NETSTANDARD2_1 using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Core/NatsConnection.Publish.cs b/src/NATS.Client.Core/NatsConnection.Publish.cs index 254ffbe6d..1df47bfc1 100644 --- a/src/NATS.Client.Core/NatsConnection.Publish.cs +++ b/src/NATS.Client.Core/NatsConnection.Publish.cs @@ -1,6 +1,6 @@ using System.Diagnostics; using NATS.Client.Core.Internal; -#if !NET6_0_OR_GREATER +#if NETSTANDARD2_0 || NETSTANDARD2_1 using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Core/NatsConnection.cs b/src/NATS.Client.Core/NatsConnection.cs index 1de5d3503..f5f73ff41 100644 --- a/src/NATS.Client.Core/NatsConnection.cs +++ b/src/NATS.Client.Core/NatsConnection.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.Logging; using NATS.Client.Core.Commands; using NATS.Client.Core.Internal; -#if !NET6_0_OR_GREATER +#if NETSTANDARD2_0 || NETSTANDARD2_1 using NATS.Client.Core.Internal.NetStandardExtensions; using Random = NATS.Client.Core.Internal.NetStandardExtensions.Random; #endif @@ -752,11 +752,7 @@ private async Task WaitWithJitterAsync() } else { -#if NETSTANDARD2_0 _backoff = new TimeSpan(_backoff.Ticks * 2); -#else - _backoff *= 2; -#endif if (_backoff > Opts.ReconnectWaitMax) { _backoff = Opts.ReconnectWaitMax; diff --git a/src/NATS.Client.Core/NatsException.cs b/src/NATS.Client.Core/NatsException.cs index 4f7d2be78..8b8b2b872 100644 --- a/src/NATS.Client.Core/NatsException.cs +++ b/src/NATS.Client.Core/NatsException.cs @@ -1,3 +1,5 @@ +using System.Globalization; + namespace NATS.Client.Core; public class NatsException : Exception @@ -34,7 +36,7 @@ public sealed class NatsServerException : NatsException public NatsServerException(string error) : base($"Server error: {error}") { - Error = error.ToLower(); + Error = error.ToLower(CultureInfo.InvariantCulture); IsAuthError = Error.Contains("authorization violation") || Error.Contains("user authentication expired") || Error.Contains("user authentication revoked") diff --git a/src/NATS.Client.Core/NatsHeaderParser.cs b/src/NATS.Client.Core/NatsHeaderParser.cs index e2ced4c6b..7e8d1e031 100644 --- a/src/NATS.Client.Core/NatsHeaderParser.cs +++ b/src/NATS.Client.Core/NatsHeaderParser.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Primitives; using NATS.Client.Core.Commands; using NATS.Client.Core.Internal; -#if !NET6_0_OR_GREATER +#if NETSTANDARD2_0 || NETSTANDARD2_1 using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Hosting/NATS.Client.Hosting.csproj b/src/NATS.Client.Hosting/NATS.Client.Hosting.csproj index 4f32e256f..abe33b4fa 100644 --- a/src/NATS.Client.Hosting/NATS.Client.Hosting.csproj +++ b/src/NATS.Client.Hosting/NATS.Client.Hosting.csproj @@ -12,9 +12,9 @@ true - - true - + + true + diff --git a/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs b/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs index 719c76529..3cbdb0d74 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs @@ -6,6 +6,9 @@ using NATS.Client.Core.Commands; using NATS.Client.Core.Internal; using NATS.Client.JetStream.Models; +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.JetStream.Internal; @@ -438,16 +441,17 @@ private void Pull(string origin, long batch, long maxBytes) => _pullRequests.Wri private async Task PullLoop() { - while (await _pullRequests.Reader.WaitToReadAsync().ConfigureAwait(false)) +#if NETSTANDARD2_0 + await foreach (var pr in _pullRequests.Reader.ReadAllLoopAsync().ConfigureAwait(false)) +#else + await foreach (var pr in _pullRequests.Reader.ReadAllAsync().ConfigureAwait(false)) +#endif { - while (_pullRequests.Reader.TryRead(out var pr)) + var origin = $"pull-loop({pr.Origin})"; + await CallMsgNextAsync(origin, pr.Request).ConfigureAwait(false); + if (_debug) { - var origin = $"pull-loop({pr.Origin})"; - await CallMsgNextAsync(origin, pr.Request).ConfigureAwait(false); - if (_debug) - { - _logger.LogDebug(NatsJSLogEvents.PullRequest, "Pull request issued for {Origin} {Batch}, {MaxBytes}", origin, pr.Request.Batch, pr.Request.MaxBytes); - } + _logger.LogDebug(NatsJSLogEvents.PullRequest, "Pull request issued for {Origin} {Batch}, {MaxBytes}", origin, pr.Request.Batch, pr.Request.MaxBytes); } } } diff --git a/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs b/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs index 8c5c5cbd9..85a6cdd00 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs @@ -5,6 +5,9 @@ using NATS.Client.Core; using NATS.Client.Core.Commands; using NATS.Client.JetStream.Models; +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.JetStream.Internal; @@ -391,16 +394,17 @@ private void Pull(string origin, long batch, long maxBytes) => _pullRequests.Wri private async Task PullLoop() { - while (await _pullRequests.Reader.WaitToReadAsync().ConfigureAwait(false)) +#if NETSTANDARD2_0 + await foreach (var pr in _pullRequests.Reader.ReadAllLoopAsync().ConfigureAwait(false)) +#else + await foreach (var pr in _pullRequests.Reader.ReadAllAsync().ConfigureAwait(false)) +#endif { - while (_pullRequests.Reader.TryRead(out var pr)) + var origin = $"pull-loop({pr.Origin})"; + await CallMsgNextAsync(origin, pr.Request).ConfigureAwait(false); + if (_debug) { - var origin = $"pull-loop({pr.Origin})"; - await CallMsgNextAsync(origin, pr.Request).ConfigureAwait(false); - if (_debug) - { - _logger.LogDebug(NatsJSLogEvents.PullRequest, "Pull request issued for {Origin} {Batch}, {MaxBytes}", origin, pr.Request.Batch, pr.Request.MaxBytes); - } + _logger.LogDebug(NatsJSLogEvents.PullRequest, "Pull request issued for {Origin} {Batch}, {MaxBytes}", origin, pr.Request.Batch, pr.Request.MaxBytes); } } } diff --git a/src/NATS.Client.JetStream/NATS.Client.JetStream.csproj b/src/NATS.Client.JetStream/NATS.Client.JetStream.csproj index d44ba1812..345d0c430 100644 --- a/src/NATS.Client.JetStream/NATS.Client.JetStream.csproj +++ b/src/NATS.Client.JetStream/NATS.Client.JetStream.csproj @@ -1,7 +1,6 @@ - netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable @@ -13,14 +12,14 @@ true - - true - + + true + - - - - + + + + diff --git a/src/NATS.Client.JetStream/NatsJSConsumer.cs b/src/NATS.Client.JetStream/NatsJSConsumer.cs index 8de5e708e..a264186ba 100644 --- a/src/NATS.Client.JetStream/NatsJSConsumer.cs +++ b/src/NATS.Client.JetStream/NatsJSConsumer.cs @@ -3,6 +3,9 @@ using NATS.Client.Core; using NATS.Client.JetStream.Internal; using NATS.Client.JetStream.Models; +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.JetStream; @@ -148,12 +151,13 @@ public async IAsyncEnumerable> ConsumeAsync( serializer, cancellationToken: cancellationToken).ConfigureAwait(false); - while (await f.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) +#if NETSTANDARD2_0 + await foreach (var natsJSMsg in f.Msgs.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) +#else + await foreach (var natsJSMsg in f.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) +#endif { - while (f.Msgs.TryRead(out var msg)) - { - return msg; - } + return natsJSMsg; } return null; @@ -262,13 +266,13 @@ public async IAsyncEnumerable> FetchNoWaitAsync( serializer ??= _context.Connection.Opts.SerializerRegistry.GetDeserializer(); await using var fc = await FetchInternalAsync(opts with { NoWait = true }, serializer, cancellationToken).ConfigureAwait(false); - - while (await fc.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) +#if NETSTANDARD2_0 + await foreach (var jsMsg in fc.Msgs.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) +#else + await foreach (var jsMsg in fc.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) +#endif { - while (fc.Msgs.TryRead(out var msg)) - { - yield return msg; - } + yield return jsMsg; } } diff --git a/src/NATS.Client.JetStream/NatsJSContext.cs b/src/NATS.Client.JetStream/NatsJSContext.cs index 54761b1b6..df1e4a3d1 100644 --- a/src/NATS.Client.JetStream/NatsJSContext.cs +++ b/src/NATS.Client.JetStream/NatsJSContext.cs @@ -4,6 +4,9 @@ using NATS.Client.Core; using NATS.Client.JetStream.Internal; using NATS.Client.JetStream.Models; +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.JetStream; @@ -144,17 +147,18 @@ public async ValueTask PublishAsync( try { - while (await sub.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) +#if NETSTANDARD2_0 + await foreach (var msg in sub.Msgs.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) +#else + await foreach (var msg in sub.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) +#endif { - while (sub.Msgs.TryRead(out var msg)) + if (msg.Data == null) { - if (msg.Data == null) - { - throw new NatsJSException("No response data received"); - } - - return msg.Data; + throw new NatsJSException("No response data received"); } + + return msg.Data; } } catch (NatsNoRespondersException) @@ -243,24 +247,36 @@ internal async ValueTask> JSRequestAsync(default, jsError.Error); + } + + throw error; + } + + if (msg.Data == null) { - return new NatsJSResponse(default, jsError.Error); + throw new NatsJSException("No response data received"); } - throw error; + return new NatsJSResponse(msg.Data, default); } - if (msg.Data == null) + if (sub is NatsSubBase { EndReason: NatsSubEndReason.Exception, Exception: not null } sb) { - throw new NatsJSException("No response data received"); + throw sb.Exception; } - return new NatsJSResponse(msg.Data, default); + throw new NatsJSApiNoResponseException(); } #if NETSTANDARD2_1 || NET6_0_OR_GREATER diff --git a/src/NATS.Client.KeyValueStore/NATS.Client.KeyValueStore.csproj b/src/NATS.Client.KeyValueStore/NATS.Client.KeyValueStore.csproj index 4aa9c9706..02d4a0f28 100644 --- a/src/NATS.Client.KeyValueStore/NATS.Client.KeyValueStore.csproj +++ b/src/NATS.Client.KeyValueStore/NATS.Client.KeyValueStore.csproj @@ -1,8 +1,7 @@ - - netstandard2.0;netstandard2.1;net6.0;net8.0 + netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable true @@ -13,9 +12,9 @@ true - - true - + + true + diff --git a/src/NATS.Client.KeyValueStore/NatsKVStore.cs b/src/NATS.Client.KeyValueStore/NatsKVStore.cs index faf743151..372851708 100644 --- a/src/NATS.Client.KeyValueStore/NatsKVStore.cs +++ b/src/NATS.Client.KeyValueStore/NatsKVStore.cs @@ -5,6 +5,9 @@ using NATS.Client.JetStream; using NATS.Client.JetStream.Models; using NATS.Client.KeyValueStore.Internal; +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.KeyValueStore; @@ -337,12 +340,13 @@ public async IAsyncEnumerable> WatchAsync(IEnumerable } } - while (await watcher.Entries.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) +#if NETSTANDARD2_0 + await foreach (var entry in watcher.Entries.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) +#else + await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken).ConfigureAwait(false)) +#endif { - while (watcher.Entries.TryRead(out var entry)) - { - yield return entry; - } + yield return entry; } } @@ -366,14 +370,15 @@ public async IAsyncEnumerable> HistoryAsync(string key, INatsD await using var watcher = await WatchInternalAsync([key], serializer, opts, cancellationToken); - while (await watcher.Entries.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) +#if NETSTANDARD2_0 + await foreach (var entry in watcher.Entries.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) +#else + await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken).ConfigureAwait(false)) +#endif { - while (watcher.Entries.TryRead(out var entry)) - { - yield return entry; - if (entry.Delta == 0) - yield break; - } + yield return entry; + if (entry.Delta == 0) + yield break; } } @@ -406,15 +411,16 @@ public async ValueTask PurgeDeletesAsync(NatsKVPurgeOpts? opts = default, Cancel if (watcher.InitialConsumer.Info.NumPending == 0) return; - while (await watcher.Entries.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) +#if NETSTANDARD2_0 + await foreach (var entry in watcher.Entries.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) +#else + await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken).ConfigureAwait(false)) +#endif { - while (watcher.Entries.TryRead(out var entry)) - { - if (entry.Operation is NatsKVOperation.Purge or NatsKVOperation.Del) - deleted.Add(entry); - if (entry.Delta == 0) - goto PURGE_LOOP_DONE; - } + if (entry.Operation is NatsKVOperation.Purge or NatsKVOperation.Del) + deleted.Add(entry); + if (entry.Delta == 0) + goto PURGE_LOOP_DONE; } } @@ -450,15 +456,16 @@ public async IAsyncEnumerable GetKeysAsync(NatsKVWatchOpts? opts = defau if (watcher.InitialConsumer.Info.NumPending == 0) yield break; - while (await watcher.Entries.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) +#if NETSTANDARD2_0 + await foreach (var entry in watcher.Entries.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) +#else + await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken).ConfigureAwait(false)) +#endif { - while (watcher.Entries.TryRead(out var entry)) - { - if (entry.Operation is NatsKVOperation.Put) - yield return entry.Key; - if (entry.Delta == 0) - yield break; - } + if (entry.Operation is NatsKVOperation.Put) + yield return entry.Key; + if (entry.Delta == 0) + yield break; } } diff --git a/src/NATS.Client.ObjectStore/NATS.Client.ObjectStore.csproj b/src/NATS.Client.ObjectStore/NATS.Client.ObjectStore.csproj index 9f9f220d2..cc9c5134e 100644 --- a/src/NATS.Client.ObjectStore/NATS.Client.ObjectStore.csproj +++ b/src/NATS.Client.ObjectStore/NATS.Client.ObjectStore.csproj @@ -1,8 +1,7 @@ - - netstandard2.0;netstandard2.1;net6.0;net8.0 + netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable true @@ -13,9 +12,9 @@ true - - true - + + true + diff --git a/src/NATS.Client.ObjectStore/NatsObjStore.cs b/src/NATS.Client.ObjectStore/NatsObjStore.cs index 72b76b0e7..8b163e1f5 100644 --- a/src/NATS.Client.ObjectStore/NatsObjStore.cs +++ b/src/NATS.Client.ObjectStore/NatsObjStore.cs @@ -10,6 +10,9 @@ using NATS.Client.JetStream.Models; using NATS.Client.ObjectStore.Internal; using NATS.Client.ObjectStore.Models; +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.ObjectStore; @@ -102,37 +105,38 @@ public async ValueTask GetAsync(string key, Stream stream, bool await using (var hashedStream = new CryptoStream(stream, sha256, CryptoStreamMode.Write, leaveOpen)) #endif { - while (await pushConsumer.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) +#if NETSTANDARD2_0 + await foreach (var msg in pushConsumer.Msgs.ReadAllLoopAsync(cancellationToken)) +#else + await foreach (var msg in pushConsumer.Msgs.ReadAllAsync(cancellationToken)) +#endif { - while (pushConsumer.Msgs.TryRead(out var msg)) + // We have to make sure to carry on consuming the channel to avoid any blocking: + // e.g. if the channel is full, we would be blocking the reads off the socket (this was intentionally + // done ot avoid bloating the memory with a large backlog of messages or dropping messages at this level + // and signal the server that we are a slow consumer); then when we make an request-reply API call to + // delete the consumer, the socket would be blocked trying to send the response back to us; so we need to + // keep consuming the channel to avoid this. + if (pushConsumer.IsDone) + continue; + + if (msg.Data.Length > 0) { - // We have to make sure to carry on consuming the channel to avoid any blocking: - // e.g. if the channel is full, we would be blocking the reads off the socket (this was intentionally - // done ot avoid bloating the memory with a large backlog of messages or dropping messages at this level - // and signal the server that we are a slow consumer); then when we make an request-reply API call to - // delete the consumer, the socket would be blocked trying to send the response back to us; so we need to - // keep consuming the channel to avoid this. - if (pushConsumer.IsDone) - continue; - - if (msg.Data.Length > 0) - { - using var memoryOwner = msg.Data; - chunks++; - size += memoryOwner.Memory.Length; + using var memoryOwner = msg.Data; + chunks++; + size += memoryOwner.Memory.Length; #if NETSTANDARD2_0 - var segment = memoryOwner.DangerousGetArray(); - await hashedStream.WriteAsync(segment.Array, segment.Offset, segment.Count, cancellationToken); + var segment = memoryOwner.DangerousGetArray(); + await hashedStream.WriteAsync(segment.Array, segment.Offset, segment.Count, cancellationToken); #else - await hashedStream.WriteAsync(memoryOwner.Memory, cancellationToken); + await hashedStream.WriteAsync(memoryOwner.Memory, cancellationToken); #endif - } + } - var p = msg.Metadata?.NumPending; - if (p is 0) - { - pushConsumer.Done(); - } + var p = msg.Metadata?.NumPending; + if (p is 0) + { + pushConsumer.Done(); } } } @@ -605,41 +609,36 @@ public async IAsyncEnumerable WatchAsync(NatsObjWatchOpts? opts pushConsumer.Init(); -#if NET6_0_OR_GREATER - await foreach (var msg in pushConsumer.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) +#if NETSTANDARD2_0 + await foreach (var msg in pushConsumer.Msgs.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) #else - while (await pushConsumer.Msgs.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) - { - while (pushConsumer.Msgs.TryRead(out var msg)) + await foreach (var msg in pushConsumer.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) #endif + { + if (pushConsumer.IsDone) + continue; + using (msg.Data) { - if (pushConsumer.IsDone) - continue; - using (msg.Data) + if (msg.Metadata is { } metadata) { - if (msg.Metadata is { } metadata) + var info = JsonSerializer.Deserialize(msg.Data.Memory.Span, NatsObjJsonSerializerContext.Default.ObjectMetadata); + if (info != null) { - var info = JsonSerializer.Deserialize(msg.Data.Memory.Span, NatsObjJsonSerializerContext.Default.ObjectMetadata); - if (info != null) + if (!opts.IgnoreDeletes || !info.Deleted) { - if (!opts.IgnoreDeletes || !info.Deleted) - { - info.MTime = metadata.Timestamp; - yield return info; - } + info.MTime = metadata.Timestamp; + yield return info; } + } - if (opts.InitialSetOnly) - { - if (metadata.NumPending == 0) - break; - } + if (opts.InitialSetOnly) + { + if (metadata.NumPending == 0) + break; } } } -#if !NET6_0_OR_GREATER } -#endif } /// diff --git a/src/NATS.Client.Serializers.Json/NATS.Client.Serializers.Json.csproj b/src/NATS.Client.Serializers.Json/NATS.Client.Serializers.Json.csproj index c6b422035..2af1421ff 100644 --- a/src/NATS.Client.Serializers.Json/NATS.Client.Serializers.Json.csproj +++ b/src/NATS.Client.Serializers.Json/NATS.Client.Serializers.Json.csproj @@ -1,8 +1,7 @@ - - netstandard2.0;netstandard2.1;net6.0;net8.0 + netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable true diff --git a/src/NATS.Client.Services/Internal/SvcListener.cs b/src/NATS.Client.Services/Internal/SvcListener.cs index be8b3371f..73159ca24 100644 --- a/src/NATS.Client.Services/Internal/SvcListener.cs +++ b/src/NATS.Client.Services/Internal/SvcListener.cs @@ -1,5 +1,8 @@ using System.Threading.Channels; using NATS.Client.Core; +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Services.Internal; @@ -30,12 +33,13 @@ public async ValueTask StartAsync() { await using (sub) { - while (await sub.Msgs.WaitToReadAsync().ConfigureAwait(false)) +#if NETSTANDARD2_0 + await foreach (var msg in sub.Msgs.ReadAllLoopAsync()) +#else + await foreach (var msg in sub.Msgs.ReadAllAsync()) +#endif { - while (sub.Msgs.TryRead(out var msg)) - { - await _channel.Writer.WriteAsync(new SvcMsg(_type, msg), _cts.Token).ConfigureAwait(false); - } + await _channel.Writer.WriteAsync(new SvcMsg(_type, msg), _cts.Token).ConfigureAwait(false); } } }); diff --git a/src/NATS.Client.Services/NATS.Client.Services.csproj b/src/NATS.Client.Services/NATS.Client.Services.csproj index b2f8c78d4..f8a5baac7 100644 --- a/src/NATS.Client.Services/NATS.Client.Services.csproj +++ b/src/NATS.Client.Services/NATS.Client.Services.csproj @@ -1,8 +1,7 @@ - - netstandard2.0;netstandard2.1;net6.0;net8.0 + netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable true @@ -13,9 +12,9 @@ true - - true - + + true + diff --git a/src/NATS.Client.Services/NatsSvcEndPoint.cs b/src/NATS.Client.Services/NatsSvcEndPoint.cs index 8b087ed3c..f160d8e34 100644 --- a/src/NATS.Client.Services/NatsSvcEndPoint.cs +++ b/src/NATS.Client.Services/NatsSvcEndPoint.cs @@ -5,6 +5,9 @@ using Microsoft.Extensions.Logging; using NATS.Client.Core; using NATS.Client.Core.Internal; +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Services; @@ -212,60 +215,61 @@ protected override ValueTask ReceiveInternalAsync( private async Task HandlerLoop() { var stopwatch = new Stopwatch(); - while (await _channel.Reader.WaitToReadAsync(_cancellationToken).ConfigureAwait(false)) +#if NETSTANDARD2_0 + await foreach (var svcMsg in _channel.Reader.ReadAllLoopAsync(_cancellationToken).ConfigureAwait(false)) +#else + await foreach (var svcMsg in _channel.Reader.ReadAllAsync(_cancellationToken).ConfigureAwait(false)) +#endif { - while (_channel.Reader.TryRead(out var svcMsg)) + Interlocked.Increment(ref _requests); + stopwatch.Restart(); + try { - Interlocked.Increment(ref _requests); - stopwatch.Restart(); - try + await _handler(svcMsg).ConfigureAwait(false); + } + catch (Exception e) + { + int code; + string message; + string body; + if (e is NatsSvcEndpointException epe) { - await _handler(svcMsg).ConfigureAwait(false); + code = epe.Code; + message = epe.Message; + body = epe.Body; } - catch (Exception e) + else { - int code; - string message; - string body; - if (e is NatsSvcEndpointException epe) - { - code = epe.Code; - message = epe.Message; - body = epe.Body; - } - else - { - // Do not expose exceptions unless explicitly - // thrown as NatsSvcEndpointException - code = 999; - message = "Handler error"; - body = string.Empty; - - // Only log unknown exceptions - _logger.LogError(NatsSvcLogEvents.Endpoint, e, "Endpoint {Name} error processing message", Name); - } + // Do not expose exceptions unless explicitly + // thrown as NatsSvcEndpointException + code = 999; + message = "Handler error"; + body = string.Empty; + + // Only log unknown exceptions + _logger.LogError(NatsSvcLogEvents.Endpoint, e, "Endpoint {Name} error processing message", Name); + } - try + try + { + if (string.IsNullOrWhiteSpace(body)) { - if (string.IsNullOrWhiteSpace(body)) - { - await svcMsg.ReplyErrorAsync(code, message, cancellationToken: _cancellationToken); - } - else - { - await svcMsg.ReplyErrorAsync(code, message, data: Encoding.UTF8.GetBytes(body), cancellationToken: _cancellationToken); - } + await svcMsg.ReplyErrorAsync(code, message, cancellationToken: _cancellationToken); } - catch (Exception e1) + else { - _logger.LogError(NatsSvcLogEvents.Endpoint, e1, "Endpoint {Name} error responding", Name); + await svcMsg.ReplyErrorAsync(code, message, data: Encoding.UTF8.GetBytes(body), cancellationToken: _cancellationToken); } } - finally + catch (Exception e1) { - Interlocked.Add(ref _processingTime, ToNanos(stopwatch.Elapsed)); + _logger.LogError(NatsSvcLogEvents.Endpoint, e1, "Endpoint {Name} error responding", Name); } } + finally + { + Interlocked.Add(ref _processingTime, ToNanos(stopwatch.Elapsed)); + } } } diff --git a/src/NATS.Client.Services/NatsSvcServer.cs b/src/NATS.Client.Services/NatsSvcServer.cs index ee7718741..e3e136a76 100644 --- a/src/NATS.Client.Services/NatsSvcServer.cs +++ b/src/NATS.Client.Services/NatsSvcServer.cs @@ -6,6 +6,9 @@ using NATS.Client.Core.Internal; using NATS.Client.Services.Internal; using NATS.Client.Services.Models; +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Services; @@ -227,63 +230,64 @@ private async ValueTask AddEndpointInternalAsync(Func, ValueTas private async Task MsgLoop() { - while (await _channel.Reader.WaitToReadAsync(_cts.Token).ConfigureAwait(false)) +#if NETSTANDARD2_0 + await foreach (var svcMsg in _channel.Reader.ReadAllLoopAsync(_cts.Token)) +#else + await foreach (var svcMsg in _channel.Reader.ReadAllAsync(_cts.Token)) +#endif { - while (_channel.Reader.TryRead(out var svcMsg)) + try { - try - { - var type = svcMsg.MsgType; - var data = svcMsg.Msg.Data; + var type = svcMsg.MsgType; + var data = svcMsg.Msg.Data; - if (type == SvcMsgType.Ping) + if (type == SvcMsgType.Ping) + { + using (data) { - using (data) - { - // empty request payload - } - - await svcMsg.Msg.ReplyAsync( - data: new PingResponse - { - Name = _config.Name, - Id = _id, - Version = _config.Version, - Metadata = _config.Metadata!, - }, - serializer: NatsSrvJsonSerializer.Default, - cancellationToken: _cts.Token); + // empty request payload } - else if (type == SvcMsgType.Info) - { - using (data) - { - // empty request payload - } - await svcMsg.Msg.ReplyAsync( - data: GetInfo(), - serializer: NatsSrvJsonSerializer.Default, - cancellationToken: _cts.Token); - } - else if (type == SvcMsgType.Stats) - { - using (data) + await svcMsg.Msg.ReplyAsync( + data: new PingResponse { - // empty request payload - } - - await svcMsg.Msg.ReplyAsync( - data: GetStats(), - serializer: NatsSrvJsonSerializer.Default, - cancellationToken: _cts.Token); + Name = _config.Name, + Id = _id, + Version = _config.Version, + Metadata = _config.Metadata!, + }, + serializer: NatsSrvJsonSerializer.Default, + cancellationToken: _cts.Token); + } + else if (type == SvcMsgType.Info) + { + using (data) + { + // empty request payload } + + await svcMsg.Msg.ReplyAsync( + data: GetInfo(), + serializer: NatsSrvJsonSerializer.Default, + cancellationToken: _cts.Token); } - catch (Exception ex) + else if (type == SvcMsgType.Stats) { - _logger.LogError(ex, "Message loop error"); + using (data) + { + // empty request payload + } + + await svcMsg.Msg.ReplyAsync( + data: GetStats(), + serializer: NatsSrvJsonSerializer.Default, + cancellationToken: _cts.Token); } } + catch (Exception ex) + { + _logger.LogError(ex, "Message loop error"); + } } } diff --git a/src/NATS.Extensions.Microsoft.DependencyInjection/NATS.Extensions.Microsoft.DependencyInjection.csproj b/src/NATS.Extensions.Microsoft.DependencyInjection/NATS.Extensions.Microsoft.DependencyInjection.csproj index bf9111209..2dda4f7ff 100644 --- a/src/NATS.Extensions.Microsoft.DependencyInjection/NATS.Extensions.Microsoft.DependencyInjection.csproj +++ b/src/NATS.Extensions.Microsoft.DependencyInjection/NATS.Extensions.Microsoft.DependencyInjection.csproj @@ -1,7 +1,6 @@  - netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable diff --git a/src/NATS.Net/NATS.Net.csproj b/src/NATS.Net/NATS.Net.csproj index 1c5ca9148..8e0a9b422 100644 --- a/src/NATS.Net/NATS.Net.csproj +++ b/src/NATS.Net/NATS.Net.csproj @@ -1,8 +1,7 @@ - - netstandard2.0;netstandard2.1;net6.0;net8.0 + netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable true diff --git a/tests/NATS.Client.Core.Tests/NatsHeaderTest.cs b/tests/NATS.Client.Core.Tests/NatsHeaderTest.cs index e2081e740..3ae66433f 100644 --- a/tests/NATS.Client.Core.Tests/NatsHeaderTest.cs +++ b/tests/NATS.Client.Core.Tests/NatsHeaderTest.cs @@ -177,4 +177,22 @@ public void ParserMultiSpanTests() Assert.Equal(123, headers.Code); Assert.Equal("Test Message", headers.MessageText); } + + [Theory] + [InlineData("NATS/1.0 123 Test Message\r\nk2: v2-0\r\nk2: v2-1\r\n\r\n", true, "v2-1")] + [InlineData("NATS/1.0 123 Test Message\r\nk2: v2-0\r\n\r\n", true, "v2-0")] + [InlineData("NATS/1.0 123 Test Message\r\nk3: v2-0\r\n\r\n", false, null)] + [InlineData("NATS/1.0 123 Test Message\r\n\r\n", false, null)] + public void GetLastValueTests(string text, bool expectedResult, string? expectedLastValue) + { + var parser = new NatsHeaderParser(Encoding.UTF8); + var input = new SequenceReader(new ReadOnlySequence(Encoding.UTF8.GetBytes(text))); + var headers = new NatsHeaders(); + parser.ParseHeaders(input, headers); + + var result = headers.TryGetLastValue("k2", out var lastValue); + + Assert.Equal(result, expectedResult); + Assert.Equal(lastValue, expectedLastValue); + } } diff --git a/tests/NATS.Client.KeyValueStore.Tests/KeyValueStoreTest.cs b/tests/NATS.Client.KeyValueStore.Tests/KeyValueStoreTest.cs index 00dc5aa19..0f608099d 100644 --- a/tests/NATS.Client.KeyValueStore.Tests/KeyValueStoreTest.cs +++ b/tests/NATS.Client.KeyValueStore.Tests/KeyValueStoreTest.cs @@ -607,4 +607,45 @@ public async Task Validate_keys() await Assert.ThrowsAsync(async () => await store.PurgeAsync(key)); } } + + [Fact] + public async Task TestDirectMessageRepublishedSubject() + { + var streamBucketName = "sb-" + NuidWriter.NewNuid(); + var subject = "test"; + var streamSubject = subject + ".>"; + var publishSubject1 = subject + ".one"; + var publishSubject2 = subject + ".two"; + var publishSubject3 = subject + ".three"; + var republishDest = "$KV." + streamBucketName + ".>"; + + var streamConfig = new StreamConfig(streamBucketName, new[] { streamSubject }) { Republish = new Republish { Src = ">", Dest = republishDest } }; + + await using var server = NatsServer.StartJS(); + await using var nats = server.CreateClientConnection(); + var js = new NatsJSContext(nats); + var kv = new NatsKVContext(js); + + var store = await kv.CreateStoreAsync(streamBucketName); + await js.CreateStreamAsync(streamConfig); + + await nats.PublishAsync(publishSubject1, "uno"); + await js.PublishAsync(publishSubject2, "dos"); + await store.PutAsync(publishSubject3, "tres"); + + var kve1 = await store.GetEntryAsync(publishSubject1); + Assert.Equal(streamBucketName, kve1.Bucket); + Assert.Equal(publishSubject1, kve1.Key); + Assert.Equal("uno", kve1.Value); + + var kve2 = await store.GetEntryAsync(publishSubject2); + Assert.Equal(streamBucketName, kve2.Bucket); + Assert.Equal(publishSubject2, kve2.Key); + Assert.Equal("dos", kve2.Value); + + var kve3 = await store.GetEntryAsync(publishSubject3); + Assert.Equal(streamBucketName, kve3.Bucket); + Assert.Equal(publishSubject3, kve3.Key); + Assert.Equal("tres", kve3.Value); + } } diff --git a/tests/NATS.Client.TestUtilities/NATS.Client.TestUtilities.csproj b/tests/NATS.Client.TestUtilities/NATS.Client.TestUtilities.csproj index c6f460605..054019aee 100644 --- a/tests/NATS.Client.TestUtilities/NATS.Client.TestUtilities.csproj +++ b/tests/NATS.Client.TestUtilities/NATS.Client.TestUtilities.csproj @@ -1,9 +1,7 @@ - - - netstandard2.0;net6.0;net8.0 + netstandard2.0;net6.0;net8.0 enable enable false @@ -14,20 +12,21 @@ true - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/tests/NATS.Client.TestUtilities/Utils.cs b/tests/NATS.Client.TestUtilities/Utils.cs index ad4de27ab..752c5bc55 100644 --- a/tests/NATS.Client.TestUtilities/Utils.cs +++ b/tests/NATS.Client.TestUtilities/Utils.cs @@ -2,6 +2,9 @@ using System.Net; using System.Net.Sockets; using System.Text; +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Core.Tests; @@ -71,12 +74,13 @@ public static Task Register(this INatsSub? sub, Action> action) return Task.CompletedTask; return Task.Run(async () => { - while (await sub.Msgs.WaitToReadAsync(CancellationToken.None).ConfigureAwait(false)) +#if NETSTANDARD2_0 + await foreach (var natsMsg in sub.Msgs.ReadAllLoopAsync()) +#else + await foreach (var natsMsg in sub.Msgs.ReadAllAsync()) +#endif { - while (sub.Msgs.TryRead(out var msg)) - { - action(msg); - } + action(natsMsg); } }); } @@ -87,12 +91,13 @@ public static Task Register(this INatsSub? sub, Func, Task> act return Task.CompletedTask; return Task.Run(async () => { - while (await sub.Msgs.WaitToReadAsync(CancellationToken.None).ConfigureAwait(false)) +#if NETSTANDARD2_0 + await foreach (var natsMsg in sub.Msgs.ReadAllLoopAsync()) +#else + await foreach (var natsMsg in sub.Msgs.ReadAllAsync()) +#endif { - while (sub.Msgs.TryRead(out var msg)) - { - action(msg); - } + await action(natsMsg); } }); } @@ -150,7 +155,7 @@ await Retry.Until("service is found", async () => } return count == limit; - }).ConfigureAwait(false); + }); var count = 0; await foreach (var msg in nats.RequestManyAsync(subject, "{}", replySerializer: serializer, replyOpts: replyOpts, cancellationToken: ct).ConfigureAwait(false)) From 4dbcd84477fcf55e1148400127bf008759d93ec1 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 23:22:21 +0100 Subject: [PATCH 22/47] platform tests --- .github/workflows/test.yml | 8 +- NATS.Client.sln | 7 + .../NATS.Client.Platform.Windows.Tests.csproj | 31 +++ .../NatsServerProcess.cs | 250 ++++++++++++++++++ .../UnitTest1.cs | 16 ++ 5 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 tests/NATS.Client.Platform.Windows.Tests/NATS.Client.Platform.Windows.Tests.csproj create mode 100644 tests/NATS.Client.Platform.Windows.Tests/NatsServerProcess.cs create mode 100644 tests/NATS.Client.Platform.Windows.Tests/UnitTest1.cs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f3720fec7..ec7d77641 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -145,7 +145,13 @@ jobs: dotnet tool install --global NUnit.ConsoleRunner.NetCore - name: Build - run: dotnet build -c Release + run: | + dotnet build -c Debug + dotnet build -c Release - name: Memory Test (net6.0) run: dotMemoryUnit $env:userprofile\.dotnet\tools\nunit.exe --propagate-exit-code -- .\tests\NATS.Client.Core.MemoryTests\bin\Release\net6.0\NATS.Client.Core.MemoryTests.dll + + - name: Platform Test (Windows net481) + run: dotnet test -c Debug --no-build --logger:"console;verbosity=normal" -f net481 .\tests\NATS.Client.Platform.Windows.Tests\NATS.Client.Platform.Windows.Tests.csproj + diff --git a/NATS.Client.sln b/NATS.Client.sln index ee9dac34d..5779d3931 100644 --- a/NATS.Client.sln +++ b/NATS.Client.sln @@ -107,6 +107,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.OpenTelemetry", "sa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NATS.Net.OpenTelemetry.Tests", "tests\NATS.Net.OpenTelemetry.Tests\NATS.Net.OpenTelemetry.Tests.csproj", "{B8554582-DE19-41A2-9784-9B27C9F22429}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NATS.Client.Platform.Windows.Tests", "tests\NATS.Client.Platform.Windows.Tests\NATS.Client.Platform.Windows.Tests.csproj", "{A37994CC-A23A-415E-8B61-9468C7178A55}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -285,6 +287,10 @@ Global {B8554582-DE19-41A2-9784-9B27C9F22429}.Debug|Any CPU.Build.0 = Debug|Any CPU {B8554582-DE19-41A2-9784-9B27C9F22429}.Release|Any CPU.ActiveCfg = Release|Any CPU {B8554582-DE19-41A2-9784-9B27C9F22429}.Release|Any CPU.Build.0 = Release|Any CPU + {A37994CC-A23A-415E-8B61-9468C7178A55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A37994CC-A23A-415E-8B61-9468C7178A55}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A37994CC-A23A-415E-8B61-9468C7178A55}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A37994CC-A23A-415E-8B61-9468C7178A55}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -334,6 +340,7 @@ Global {2EA0EB68-1AA4-40AC-8FA2-B51532075567} = {4827B3EC-73D8-436D-AE2A-5E29AC95FD0C} {474BA453-9CFF-41C2-B2E7-ADD92CC93E86} = {95A69671-16CA-4133-981C-CC381B7AAA30} {B8554582-DE19-41A2-9784-9B27C9F22429} = {C526E8AB-739A-48D7-8FC4-048978C9B650} + {A37994CC-A23A-415E-8B61-9468C7178A55} = {C526E8AB-739A-48D7-8FC4-048978C9B650} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8CBB7278-D093-448E-B3DE-B5991209A1AA} diff --git a/tests/NATS.Client.Platform.Windows.Tests/NATS.Client.Platform.Windows.Tests.csproj b/tests/NATS.Client.Platform.Windows.Tests/NATS.Client.Platform.Windows.Tests.csproj new file mode 100644 index 000000000..4b7df53e7 --- /dev/null +++ b/tests/NATS.Client.Platform.Windows.Tests/NATS.Client.Platform.Windows.Tests.csproj @@ -0,0 +1,31 @@ + + + + net481 + enable + enable + $(NoWarn);CS8002;VSTHRD200;VSTHRD111 + false + true + latest + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/NATS.Client.Platform.Windows.Tests/NatsServerProcess.cs b/tests/NATS.Client.Platform.Windows.Tests/NatsServerProcess.cs new file mode 100644 index 000000000..c82481e49 --- /dev/null +++ b/tests/NATS.Client.Platform.Windows.Tests/NatsServerProcess.cs @@ -0,0 +1,250 @@ +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +#pragma warning disable SA1512 +// ReSharper disable SuggestVarOrType_BuiltInTypes +// ReSharper disable SuggestVarOrType_SimpleTypes +// ReSharper disable NotAccessedField.Local +// ReSharper disable UseObjectOrCollectionInitializer +// ReSharper disable InconsistentNaming + +namespace NATS.Client.Platform.Windows.Tests; + +public class NatsServerProcess : IAsyncDisposable +{ + private readonly Action _logger; + private readonly Process _process; + private readonly string _portsFile; + + private NatsServerProcess(Action logger, Process process, string url, string portsFile) + { + Url = url; + _logger = logger; + _process = process; + _portsFile = portsFile; + } + + public string Url { get; } + + public static async ValueTask StartAsync(Action? logger = null) + { + var log = logger ?? (_ => { }); + + var tmp = Path.GetTempPath(); + var tmpEsc = tmp.Replace(@"\", @"\\"); + var info = new ProcessStartInfo + { + FileName = "nats-server.exe", + Arguments = $"-a 127.0.0.1 -p -1 -js --ports_file_dir \"{tmpEsc}\"", + UseShellExecute = false, + CreateNoWindow = false, + RedirectStandardError = true, + RedirectStandardOutput = true, + }; + var process = new Process { StartInfo = info, }; + + var tcs = new TaskCompletionSource(); + DataReceivedEventHandler outputHandler = (_, e) => + { + log(e.Data); + if (e.Data != null && e.Data.Contains("Server is ready")) + { + tcs.SetResult(e.Data); + } + }; + process.OutputDataReceived += outputHandler; + process.ErrorDataReceived += outputHandler; + + process.Start(); + + ChildProcessTracker.AddProcess(process); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + await tcs.Task; + + var portsFile = Path.Combine(tmp, $"nats-server.exe_{process.Id}.ports"); + log($"portsFile={portsFile}"); + var ports = File.ReadAllText(portsFile); + var url = Regex.Match(ports, @"nats://[\d\.]+:\d+").Groups[0].Value; + log($"ports={ports}"); + log($"url={url}"); + + return new NatsServerProcess(log, process, url, portsFile); + } + + public async ValueTask DisposeAsync() + { + try + { + _process.Kill(); + } + catch + { + // best effort + } + + for (var i = 0; i < 3; i++) + { + try + { + File.Delete(_portsFile); + break; + } + catch + { + await Task.Delay(100); + } + } + + _process.Dispose(); + } +} + +// Borrowed from https://stackoverflow.com/questions/3342941/kill-child-process-when-parent-process-is-killed/37034966#37034966 + +/// +/// Allows processes to be automatically killed if this parent process unexpectedly quits. +/// This feature requires Windows 8 or greater. On Windows 7, nothing is done. +/// References: +/// https://stackoverflow.com/a/4657392/386091 +/// https://stackoverflow.com/a/9164742/386091 +#pragma warning disable SA1204 +#pragma warning disable SA1129 +#pragma warning disable SA1201 +#pragma warning disable SA1117 +#pragma warning disable SA1400 +#pragma warning disable SA1311 +#pragma warning disable SA1308 +#pragma warning disable SA1413 +#pragma warning disable SA1121 +public static class ChildProcessTracker +{ + /// + /// Add the process to be tracked. If our current process is killed, the child processes + /// that we are tracking will be automatically killed, too. If the child process terminates + /// first, that's fine, too. + /// + public static void AddProcess(Process process) + { + if (s_jobHandle != IntPtr.Zero) + { + bool success = AssignProcessToJobObject(s_jobHandle, process.Handle); + if (!success && !process.HasExited) + throw new Win32Exception(); + } + } + + static ChildProcessTracker() + { + // This feature requires Windows 8 or later. To support Windows 7, requires + // registry settings to be added if you are using Visual Studio plus an + // app.manifest change. + // https://stackoverflow.com/a/4232259/386091 + // https://stackoverflow.com/a/9507862/386091 + if (Environment.OSVersion.Version < new Version(6, 2)) + return; + + // The job name is optional (and can be null), but it helps with diagnostics. + // If it's not null, it has to be unique. Use SysInternals' Handle command-line + // utility: handle -a ChildProcessTracker + string jobName = "ChildProcessTracker" + Process.GetCurrentProcess().Id; + s_jobHandle = CreateJobObject(IntPtr.Zero, jobName); + + var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION(); + + // This is the key flag. When our process is killed, Windows will automatically + // close the job handle, and when that happens, we want the child processes to + // be killed, too. + info.LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + + var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); + extendedInfo.BasicLimitInformation = info; + + int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); + IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length); + try + { + Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); + + if (!SetInformationJobObject(s_jobHandle, JobObjectInfoType.ExtendedLimitInformation, + extendedInfoPtr, (uint)length)) + { + throw new Win32Exception(); + } + } + finally + { + Marshal.FreeHGlobal(extendedInfoPtr); + } + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string name); + + [DllImport("kernel32.dll")] + static extern bool SetInformationJobObject(IntPtr job, JobObjectInfoType infoType, + IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); + + // Windows will automatically close any open job handles when our process terminates. + // This can be verified by using SysInternals' Handle utility. When the job handle + // is closed, the child processes will be killed. + private static readonly IntPtr s_jobHandle; +} + +public enum JobObjectInfoType +{ + AssociateCompletionPortInformation = 7, + BasicLimitInformation = 2, + BasicUIRestrictions = 4, + EndOfJobTimeInformation = 6, + ExtendedLimitInformation = 9, + SecurityLimitInformation = 5, + GroupInformation = 11 +} + +[StructLayout(LayoutKind.Sequential)] +public struct JOBOBJECT_BASIC_LIMIT_INFORMATION +{ + public Int64 PerProcessUserTimeLimit; + public Int64 PerJobUserTimeLimit; + public JOBOBJECTLIMIT LimitFlags; + public UIntPtr MinimumWorkingSetSize; + public UIntPtr MaximumWorkingSetSize; + public UInt32 ActiveProcessLimit; + public Int64 Affinity; + public UInt32 PriorityClass; + public UInt32 SchedulingClass; +} + +[Flags] +public enum JOBOBJECTLIMIT : uint +{ + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000 +} + +[StructLayout(LayoutKind.Sequential)] +public struct IO_COUNTERS +{ + public UInt64 ReadOperationCount; + public UInt64 WriteOperationCount; + public UInt64 OtherOperationCount; + public UInt64 ReadTransferCount; + public UInt64 WriteTransferCount; + public UInt64 OtherTransferCount; +} + +[StructLayout(LayoutKind.Sequential)] +public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION +{ + public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; + public IO_COUNTERS IoInfo; + public UIntPtr ProcessMemoryLimit; + public UIntPtr JobMemoryLimit; + public UIntPtr PeakProcessMemoryUsed; + public UIntPtr PeakJobMemoryUsed; +} diff --git a/tests/NATS.Client.Platform.Windows.Tests/UnitTest1.cs b/tests/NATS.Client.Platform.Windows.Tests/UnitTest1.cs new file mode 100644 index 000000000..b36d44957 --- /dev/null +++ b/tests/NATS.Client.Platform.Windows.Tests/UnitTest1.cs @@ -0,0 +1,16 @@ +using NATS.Client.Core; +using Xunit.Abstractions; + +namespace NATS.Client.Platform.Windows.Tests; + +public class UnitTest1(ITestOutputHelper output) +{ + [Fact] + public async Task Test1() + { + await using var server = await NatsServerProcess.StartAsync(); + await using var nats = new NatsConnection(new NatsOpts { Url = server.Url }); + var rtt = await nats.PingAsync(); + output.WriteLine($"rtt={rtt}"); + } +} From b206685647d05decb38c3ca3118ae97130f8a59d Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Thu, 13 Jun 2024 23:26:31 +0100 Subject: [PATCH 23/47] windows test tweak --- .github/workflows/test.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ec7d77641..0603a761e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -145,13 +145,11 @@ jobs: dotnet tool install --global NUnit.ConsoleRunner.NetCore - name: Build - run: | - dotnet build -c Debug - dotnet build -c Release + run: dotnet build -c Release - name: Memory Test (net6.0) run: dotMemoryUnit $env:userprofile\.dotnet\tools\nunit.exe --propagate-exit-code -- .\tests\NATS.Client.Core.MemoryTests\bin\Release\net6.0\NATS.Client.Core.MemoryTests.dll - name: Platform Test (Windows net481) - run: dotnet test -c Debug --no-build --logger:"console;verbosity=normal" -f net481 .\tests\NATS.Client.Platform.Windows.Tests\NATS.Client.Platform.Windows.Tests.csproj + run: dotnet test -c Release --no-build --logger:"console;verbosity=normal" -f net481 .\tests\NATS.Client.Platform.Windows.Tests\NATS.Client.Platform.Windows.Tests.csproj From 83f02c59f32a034e9fdcb7063183bdbf735440d7 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 14 Jun 2024 00:57:38 +0100 Subject: [PATCH 24/47] dotnet format --- .../NatsServerProcess.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/NATS.Client.Platform.Windows.Tests/NatsServerProcess.cs b/tests/NATS.Client.Platform.Windows.Tests/NatsServerProcess.cs index c82481e49..a02e50fb6 100644 --- a/tests/NATS.Client.Platform.Windows.Tests/NatsServerProcess.cs +++ b/tests/NATS.Client.Platform.Windows.Tests/NatsServerProcess.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using System.Text.RegularExpressions; @@ -130,7 +130,7 @@ public static void AddProcess(Process process) { if (s_jobHandle != IntPtr.Zero) { - bool success = AssignProcessToJobObject(s_jobHandle, process.Handle); + var success = AssignProcessToJobObject(s_jobHandle, process.Handle); if (!success && !process.HasExited) throw new Win32Exception(); } @@ -149,7 +149,7 @@ static ChildProcessTracker() // The job name is optional (and can be null), but it helps with diagnostics. // If it's not null, it has to be unique. Use SysInternals' Handle command-line // utility: handle -a ChildProcessTracker - string jobName = "ChildProcessTracker" + Process.GetCurrentProcess().Id; + var jobName = "ChildProcessTracker" + Process.GetCurrentProcess().Id; s_jobHandle = CreateJobObject(IntPtr.Zero, jobName); var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION(); @@ -162,8 +162,8 @@ static ChildProcessTracker() var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); extendedInfo.BasicLimitInformation = info; - int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); - IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length); + var length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); + var extendedInfoPtr = Marshal.AllocHGlobal(length); try { Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); @@ -210,15 +210,15 @@ public enum JobObjectInfoType [StructLayout(LayoutKind.Sequential)] public struct JOBOBJECT_BASIC_LIMIT_INFORMATION { - public Int64 PerProcessUserTimeLimit; - public Int64 PerJobUserTimeLimit; + public long PerProcessUserTimeLimit; + public long PerJobUserTimeLimit; public JOBOBJECTLIMIT LimitFlags; public UIntPtr MinimumWorkingSetSize; public UIntPtr MaximumWorkingSetSize; - public UInt32 ActiveProcessLimit; - public Int64 Affinity; - public UInt32 PriorityClass; - public UInt32 SchedulingClass; + public uint ActiveProcessLimit; + public long Affinity; + public uint PriorityClass; + public uint SchedulingClass; } [Flags] @@ -230,12 +230,12 @@ public enum JOBOBJECTLIMIT : uint [StructLayout(LayoutKind.Sequential)] public struct IO_COUNTERS { - public UInt64 ReadOperationCount; - public UInt64 WriteOperationCount; - public UInt64 OtherOperationCount; - public UInt64 ReadTransferCount; - public UInt64 WriteTransferCount; - public UInt64 OtherTransferCount; + public ulong ReadOperationCount; + public ulong WriteOperationCount; + public ulong OtherOperationCount; + public ulong ReadTransferCount; + public ulong WriteTransferCount; + public ulong OtherTransferCount; } [StructLayout(LayoutKind.Sequential)] From 5516b201688070a0df6e40e679807f936d029bac Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 14 Jun 2024 01:54:02 +0100 Subject: [PATCH 25/47] platform basic tests --- .../BasicTests.cs | 141 ++++++++++++++++++ .../NatsServerProcess.cs | 66 ++++++-- .../UnitTest1.cs | 16 -- 3 files changed, 196 insertions(+), 27 deletions(-) create mode 100644 tests/NATS.Client.Platform.Windows.Tests/BasicTests.cs delete mode 100644 tests/NATS.Client.Platform.Windows.Tests/UnitTest1.cs diff --git a/tests/NATS.Client.Platform.Windows.Tests/BasicTests.cs b/tests/NATS.Client.Platform.Windows.Tests/BasicTests.cs new file mode 100644 index 000000000..d5c5920f3 --- /dev/null +++ b/tests/NATS.Client.Platform.Windows.Tests/BasicTests.cs @@ -0,0 +1,141 @@ +using NATS.Client.Core; +using NATS.Client.JetStream; +using NATS.Client.JetStream.Models; +using NATS.Client.KeyValueStore; +using NATS.Client.ObjectStore; +using NATS.Client.Serializers.Json; +using NATS.Client.Services; +using Xunit.Abstractions; + +namespace NATS.Client.Platform.Windows.Tests; + +public class BasicTests(ITestOutputHelper output) +{ + [Fact] + public async Task Core() + { + await using var server = await NatsServerProcess.StartAsync(output.WriteLine); + await using var nats = new NatsConnection(new NatsOpts { Url = server.Url, SerializerRegistry = NatsJsonSerializerRegistry.Default }); + + await nats.PingAsync(); + + await using var sub = await nats.SubscribeCoreAsync("foo"); + for (var i = 0; i < 16; i++) + { + await nats.PublishAsync("foo", new TestData { Id = i }); + Assert.Equal(i, (await sub.Msgs.ReadAsync()).Data!.Id); + } + } + + [Fact] + public async Task JetStream() + { + await using var server = await NatsServerProcess.StartAsync(output.WriteLine); + await using var nats = new NatsConnection(new NatsOpts { Url = server.Url }); + var js = new NatsJSContext(nats); + + var stream = await js.CreateStreamAsync(new StreamConfig("s1", ["s1.>"])); + + for (var i = 0; i < 16; i++) + { + var ack = await js.PublishAsync("s1.foo", $"bar{i}"); + ack.EnsureSuccess(); + } + + var consumer = await stream.CreateOrUpdateConsumerAsync(new ConsumerConfig("c1")); + + var count = 0; + await foreach (var msg in consumer.ConsumeAsync()) + { + await msg.AckAsync(); + Assert.Equal($"bar{count++}", msg.Data); + if (count == 16) + { + break; + } + } + + var orderedConsumer = await js.CreateOrderedConsumerAsync("s1"); + count = 0; + await foreach (var msg in orderedConsumer.ConsumeAsync()) + { + Assert.Equal($"bar{count++}", msg.Data); + if (count == 16) + { + break; + } + } + } + + [Fact] + public async Task KV() + { + await using var server = await NatsServerProcess.StartAsync(output.WriteLine); + await using var nats = new NatsConnection(new NatsOpts { Url = server.Url }); + var js = new NatsJSContext(nats); + var kv = new NatsKVContext(js); + + var store = await kv.CreateStoreAsync("b1"); + + for (var i = 0; i < 16; i++) + { + await store.PutAsync($"k{i}", $"v{i}"); + } + + for (var i = 0; i < 16; i++) + { + var entry = await store.GetEntryAsync($"k{i}"); + Assert.Equal($"v{i}", entry.Value); + } + } + + [Fact] + public async Task ObjectStore() + { + await using var server = await NatsServerProcess.StartAsync(output.WriteLine); + await using var nats = new NatsConnection(new NatsOpts { Url = server.Url }); + var js = new NatsJSContext(nats); + var obj = new NatsObjContext(js); + + var store = await obj.CreateObjectStoreAsync("b1"); + + for (var i = 0; i < 16; i++) + { + await store.PutAsync($"k{i}", [(byte)i]); + } + + for (var i = 0; i < 16; i++) + { + var bytes = await store.GetBytesAsync($"k{i}"); + Assert.Equal([(byte)i], bytes); + } + } + + [Fact] + public async Task Services() + { + await using var server = await NatsServerProcess.StartAsync(output.WriteLine); + await using var nats = new NatsConnection(new NatsOpts { Url = server.Url }); + var svc = new NatsSvcContext(nats); + + var s1 = await svc.AddServiceAsync("s1", "1.0.0"); + + await s1.AddEndpointAsync( + async msg => + { + await msg.ReplyAsync(msg.Data * 2); + }, + "multiply"); + + for (var i = 0; i < 16; i++) + { + var reply = await nats.RequestAsync("multiply", i); + Assert.Equal(i * 2, reply.Data); + } + } + + private class TestData + { + public int Id { get; set; } + } +} diff --git a/tests/NATS.Client.Platform.Windows.Tests/NatsServerProcess.cs b/tests/NATS.Client.Platform.Windows.Tests/NatsServerProcess.cs index a02e50fb6..6d79e87b4 100644 --- a/tests/NATS.Client.Platform.Windows.Tests/NatsServerProcess.cs +++ b/tests/NATS.Client.Platform.Windows.Tests/NatsServerProcess.cs @@ -2,7 +2,12 @@ using System.Diagnostics; using System.Runtime.InteropServices; using System.Text.RegularExpressions; +using Exception = System.Exception; + +#pragma warning disable VSTHRD103 +#pragma warning disable VSTHRD105 #pragma warning disable SA1512 + // ReSharper disable SuggestVarOrType_BuiltInTypes // ReSharper disable SuggestVarOrType_SimpleTypes // ReSharper disable NotAccessedField.Local @@ -15,14 +20,14 @@ public class NatsServerProcess : IAsyncDisposable { private readonly Action _logger; private readonly Process _process; - private readonly string _portsFile; + private readonly string _scratch; - private NatsServerProcess(Action logger, Process process, string url, string portsFile) + private NatsServerProcess(Action logger, Process process, string url, string scratch) { Url = url; _logger = logger; _process = process; - _portsFile = portsFile; + _scratch = scratch; } public string Url { get; } @@ -31,12 +36,20 @@ public static async ValueTask StartAsync(Action? logg { var log = logger ?? (_ => { }); - var tmp = Path.GetTempPath(); - var tmpEsc = tmp.Replace(@"\", @"\\"); + var scratch = Path.Combine(Path.GetTempPath(), "nats.net.tests", Guid.NewGuid().ToString()); + + var portsFileDir = Path.Combine(scratch, "port"); + Directory.CreateDirectory(portsFileDir); + + var sd = Path.Combine(scratch, "data"); + Directory.CreateDirectory(sd); + + var portsFileDirEsc = portsFileDir.Replace(@"\", @"\\"); + var sdEsc = sd.Replace(@"\", @"\\"); var info = new ProcessStartInfo { FileName = "nats-server.exe", - Arguments = $"-a 127.0.0.1 -p -1 -js --ports_file_dir \"{tmpEsc}\"", + Arguments = $"-a 127.0.0.1 -p -1 -js -sd \"{sdEsc}\" --ports_file_dir \"{portsFileDirEsc}\"", UseShellExecute = false, CreateNoWindow = false, RedirectStandardError = true, @@ -62,16 +75,47 @@ public static async ValueTask StartAsync(Action? logg process.BeginOutputReadLine(); process.BeginErrorReadLine(); - await tcs.Task; + var timeoutTask = Task.Delay(10_000); + await Task.WhenAny(tcs.Task, timeoutTask).ContinueWith( + completedTask => + { + if (completedTask.Result == timeoutTask) + { + throw new TimeoutException("The operation has timed out."); + } - var portsFile = Path.Combine(tmp, $"nats-server.exe_{process.Id}.ports"); + return tcs.Task; + }).Unwrap(); + + var portsFile = Path.Combine(portsFileDir, $"nats-server.exe_{process.Id}.ports"); log($"portsFile={portsFile}"); - var ports = File.ReadAllText(portsFile); + + string? ports = null; + Exception? exception = null; + for (var i = 0; i < 3; i++) + { + try + { + ports = File.ReadAllText(portsFile); + break; + } + catch (Exception e) + { + exception = e; + await Task.Delay(100 + (500 * i)); + } + } + + if (ports == null) + { + throw exception ?? new Exception("Failed to read ports file."); + } + var url = Regex.Match(ports, @"nats://[\d\.]+:\d+").Groups[0].Value; log($"ports={ports}"); log($"url={url}"); - return new NatsServerProcess(log, process, url, portsFile); + return new NatsServerProcess(log, process, url, scratch); } public async ValueTask DisposeAsync() @@ -89,7 +133,7 @@ public async ValueTask DisposeAsync() { try { - File.Delete(_portsFile); + Directory.Delete(_scratch, recursive: true); break; } catch diff --git a/tests/NATS.Client.Platform.Windows.Tests/UnitTest1.cs b/tests/NATS.Client.Platform.Windows.Tests/UnitTest1.cs deleted file mode 100644 index b36d44957..000000000 --- a/tests/NATS.Client.Platform.Windows.Tests/UnitTest1.cs +++ /dev/null @@ -1,16 +0,0 @@ -using NATS.Client.Core; -using Xunit.Abstractions; - -namespace NATS.Client.Platform.Windows.Tests; - -public class UnitTest1(ITestOutputHelper output) -{ - [Fact] - public async Task Test1() - { - await using var server = await NatsServerProcess.StartAsync(); - await using var nats = new NatsConnection(new NatsOpts { Url = server.Url }); - var rtt = await nats.PingAsync(); - output.WriteLine($"rtt={rtt}"); - } -} From 2cee902416b2088db887a2d538dba85ef629fb72 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 14 Jun 2024 03:17:40 +0100 Subject: [PATCH 26/47] revert unnecessary changes to test utils --- .../NATS.Client.Platform.Windows.Tests.csproj | 1 - tests/NATS.Client.TestUtilities/MockServer.cs | 5 +--- .../NATS.Client.TestUtilities.csproj | 23 ++----------------- tests/NATS.Client.TestUtilities/NatsProxy.cs | 6 ++--- tests/NATS.Client.TestUtilities/NatsServer.cs | 2 +- tests/NATS.Client.TestUtilities/Utils.cs | 11 --------- tests/NATS.Client.TestUtilities/WaitSignal.cs | 16 +------------ 7 files changed, 8 insertions(+), 56 deletions(-) diff --git a/tests/NATS.Client.Platform.Windows.Tests/NATS.Client.Platform.Windows.Tests.csproj b/tests/NATS.Client.Platform.Windows.Tests/NATS.Client.Platform.Windows.Tests.csproj index 4b7df53e7..caccee642 100644 --- a/tests/NATS.Client.Platform.Windows.Tests/NATS.Client.Platform.Windows.Tests.csproj +++ b/tests/NATS.Client.Platform.Windows.Tests/NATS.Client.Platform.Windows.Tests.csproj @@ -25,7 +25,6 @@ - diff --git a/tests/NATS.Client.TestUtilities/MockServer.cs b/tests/NATS.Client.TestUtilities/MockServer.cs index 0589d2b6a..634154295 100644 --- a/tests/NATS.Client.TestUtilities/MockServer.cs +++ b/tests/NATS.Client.TestUtilities/MockServer.cs @@ -2,9 +2,6 @@ using System.Net.Sockets; using System.Text; using System.Text.RegularExpressions; -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.TestUtilities; @@ -35,7 +32,7 @@ public MockServer( var n = 0; while (!cancellationToken.IsCancellationRequested) { - var tcpClient = await _server.AcceptTcpClientAsync(); + var tcpClient = await _server.AcceptTcpClientAsync(cancellationToken); var client = new Client(this, tcpClient); n++; Log($"[S] [{n}] New client connected"); diff --git a/tests/NATS.Client.TestUtilities/NATS.Client.TestUtilities.csproj b/tests/NATS.Client.TestUtilities/NATS.Client.TestUtilities.csproj index 054019aee..4bf81b8e4 100644 --- a/tests/NATS.Client.TestUtilities/NATS.Client.TestUtilities.csproj +++ b/tests/NATS.Client.TestUtilities/NATS.Client.TestUtilities.csproj @@ -1,31 +1,14 @@ - netstandard2.0;net6.0;net8.0 + net6.0;net8.0 enable enable false - $(NoWarn);CS8002;CS4014;VSTHRD110;VSTHRD002;VSTHRD103;VSTHRD200;VSTHRD111;VSTHRD105 - - - + $(NoWarn);CS8002 true - - - - - - - - - - - - - - @@ -43,8 +26,6 @@ - - diff --git a/tests/NATS.Client.TestUtilities/NatsProxy.cs b/tests/NATS.Client.TestUtilities/NatsProxy.cs index ed5d44e49..ad2c887d9 100644 --- a/tests/NATS.Client.TestUtilities/NatsProxy.cs +++ b/tests/NATS.Client.TestUtilities/NatsProxy.cs @@ -259,15 +259,15 @@ void Write(string? rawFrame) { var size = int.Parse(match.Groups[1].Value); var buffer = new char[size + 2]; - var offset = 0; + var span = buffer.AsSpan(); while (true) { - var read = sr.Read(buffer, offset, buffer.Length - offset); + var read = sr.Read(span); if (read == 0) break; if (read == -1) return false; - offset += read; + span = span[read..]; } var bufferDump = Dump(buffer.AsSpan()[..size]); diff --git a/tests/NATS.Client.TestUtilities/NatsServer.cs b/tests/NATS.Client.TestUtilities/NatsServer.cs index 142c475f1..595d5b363 100644 --- a/tests/NATS.Client.TestUtilities/NatsServer.cs +++ b/tests/NATS.Client.TestUtilities/NatsServer.cs @@ -181,7 +181,7 @@ public void StartServerProcess() { try { - await client.ConnectAsync("127.0.0.1", Opts.ServerPort); + await client.ConnectAsync("127.0.0.1", Opts.ServerPort, _cancellationTokenSource.Token); if (client.Connected) return; } diff --git a/tests/NATS.Client.TestUtilities/Utils.cs b/tests/NATS.Client.TestUtilities/Utils.cs index 752c5bc55..47024a41e 100644 --- a/tests/NATS.Client.TestUtilities/Utils.cs +++ b/tests/NATS.Client.TestUtilities/Utils.cs @@ -2,9 +2,6 @@ using System.Net; using System.Net.Sockets; using System.Text; -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core.Tests; @@ -74,11 +71,7 @@ public static Task Register(this INatsSub? sub, Action> action) return Task.CompletedTask; return Task.Run(async () => { -#if NETSTANDARD2_0 - await foreach (var natsMsg in sub.Msgs.ReadAllLoopAsync()) -#else await foreach (var natsMsg in sub.Msgs.ReadAllAsync()) -#endif { action(natsMsg); } @@ -91,11 +84,7 @@ public static Task Register(this INatsSub? sub, Func, Task> act return Task.CompletedTask; return Task.Run(async () => { -#if NETSTANDARD2_0 - await foreach (var natsMsg in sub.Msgs.ReadAllLoopAsync()) -#else await foreach (var natsMsg in sub.Msgs.ReadAllAsync()) -#endif { await action(natsMsg); } diff --git a/tests/NATS.Client.TestUtilities/WaitSignal.cs b/tests/NATS.Client.TestUtilities/WaitSignal.cs index dcb91422f..8a81ed97c 100644 --- a/tests/NATS.Client.TestUtilities/WaitSignal.cs +++ b/tests/NATS.Client.TestUtilities/WaitSignal.cs @@ -1,7 +1,4 @@ using System.Runtime.CompilerServices; -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core.Tests; @@ -137,17 +134,6 @@ public void Pulse(T result, Exception? exception = null) public TaskAwaiter GetAwaiter() { - var timeoutTask = Task.Delay(_timeout); - - return Task.WhenAny(_tcs.Task, timeoutTask).ContinueWith( - completedTask => - { - if (completedTask.Result == timeoutTask) - { - throw new TimeoutException("The operation has timed out."); - } - - return _tcs.Task; - }).Unwrap().GetAwaiter(); + return _tcs.Task.WaitAsync(_timeout).GetAwaiter(); } } From 8cf5f8a91680fe1904eff5a1fe4425fdeb575e38 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 14 Jun 2024 11:35:25 +0100 Subject: [PATCH 27/47] netstandard ulong interlocked increment --- src/NATS.Client.Core/Internal/netstandard.cs | 15 +++++++++++ .../Internal/NatsJSOrderedPushConsumer.cs | 25 +++++++++++++++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/NATS.Client.Core/Internal/netstandard.cs b/src/NATS.Client.Core/Internal/netstandard.cs index 7e12e07fc..b114b686c 100644 --- a/src/NATS.Client.Core/Internal/netstandard.cs +++ b/src/NATS.Client.Core/Internal/netstandard.cs @@ -235,6 +235,21 @@ internal static void GetBytes(this Encoding encoding, string chars, IBufferWrite bw.Write(buffer); } } + + internal static class InterlockedEx + { + internal static ulong Increment(ref ulong location) + { + long incremented = Interlocked.Increment(ref Unsafe.As(ref location)); + return Unsafe.As(ref incremented); + } + + internal static ulong Exchange(ref ulong location, ulong value) + { + long original = Interlocked.Exchange(ref Unsafe.As(ref location), Unsafe.As(ref value)); + return Unsafe.As(ref original); + } + } } #endif diff --git a/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs b/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs index 18676fee4..719e2a67a 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs @@ -4,6 +4,9 @@ using NATS.Client.Core; using NATS.Client.Core.Internal; using NATS.Client.JetStream.Models; +#if NETSTANDARD2_0 || NETSTANDARD2_1 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.JetStream.Internal; @@ -61,8 +64,8 @@ internal class NatsJSOrderedPushConsumer private readonly Task _consumerCreateTask; private readonly Task _commandTask; - private long _sequenceStream; - private long _sequenceConsumer; + private ulong _sequenceStream; + private ulong _sequenceConsumer; private string _consumer; private volatile NatsJSOrderedPushConsumerSub? _sub; private int _done; @@ -219,9 +222,13 @@ private async Task CommandLoop() continue; } +#if NETSTANDARD2_0 || NETSTANDARD2_1 + var sequence = InterlockedEx.Increment(ref _sequenceConsumer); +#else var sequence = Interlocked.Increment(ref _sequenceConsumer); +#endif - if (sequence != (long)metadata.Sequence.Consumer) + if (sequence != metadata.Sequence.Consumer) { CreateSub("sequence-mismatch"); _logger.LogWarning(NatsJSLogEvents.RecreateConsumer, "Missed messages, recreating consumer"); @@ -231,7 +238,11 @@ private async Task CommandLoop() // Increment the sequence before writing to the channel in case the channel is full // and the writer is waiting for the reader to read the message. This way the sequence // will be correctly incremented in case the timeout kicks in and recreated the consumer. - Interlocked.Exchange(ref _sequenceStream, (long)metadata.Sequence.Stream); +#if NETSTANDARD2_0 || NETSTANDARD2_1 + InterlockedEx.Exchange(ref _sequenceStream, metadata.Sequence.Stream); +#else + Interlocked.Exchange(ref _sequenceStream, metadata.Sequence.Stream); +#endif if (!IsDone) { @@ -331,7 +342,11 @@ private async ValueTask CreatePushConsumer(string origin) _logger.LogDebug(NatsJSLogEvents.NewDeliverySubject, "New delivery subject {Subject}", _sub.Subject); } +#if NETSTANDARD2_0 || NETSTANDARD2_1 + InterlockedEx.Exchange(ref _sequenceConsumer, 0); +#else Interlocked.Exchange(ref _sequenceConsumer, 0); +#endif var sequence = Volatile.Read(ref _sequenceStream); @@ -357,7 +372,7 @@ private async ValueTask CreatePushConsumer(string origin) if (sequence > 0) { config.DeliverPolicy = ConsumerConfigDeliverPolicy.ByStartSequence; - config.OptStartSeq = (ulong)sequence + 1; + config.OptStartSeq = sequence + 1; } await _context.CreateOrUpdateConsumerAsync( From 231449227281a70dede69e168945f55e2edcfafc Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 14 Jun 2024 11:53:25 +0100 Subject: [PATCH 28/47] channel reader extension tidy-up --- src/NATS.Client.Core/Internal/netstandard.cs | 6 +++--- .../NatsConnection.RequestReply.cs | 15 --------------- src/NATS.Client.Core/NatsConnection.Subscribe.cs | 5 ----- .../Internal/NatsJSConsume.cs | 4 ---- .../Internal/NatsJSOrderedConsume.cs | 4 ---- src/NATS.Client.JetStream/NatsJSConsumer.cs | 8 -------- src/NATS.Client.JetStream/NatsJSContext.cs | 8 -------- src/NATS.Client.KeyValueStore/NatsKVStore.cs | 16 ---------------- src/NATS.Client.ObjectStore/NatsObjStore.cs | 8 -------- src/NATS.Client.Services/Internal/SvcListener.cs | 4 ---- src/NATS.Client.Services/NatsSvcEndPoint.cs | 4 ---- src/NATS.Client.Services/NatsSvcServer.cs | 4 ---- 12 files changed, 3 insertions(+), 83 deletions(-) diff --git a/src/NATS.Client.Core/Internal/netstandard.cs b/src/NATS.Client.Core/Internal/netstandard.cs index b114b686c..dfa5ee521 100644 --- a/src/NATS.Client.Core/Internal/netstandard.cs +++ b/src/NATS.Client.Core/Internal/netstandard.cs @@ -240,13 +240,13 @@ internal static class InterlockedEx { internal static ulong Increment(ref ulong location) { - long incremented = Interlocked.Increment(ref Unsafe.As(ref location)); + var incremented = Interlocked.Increment(ref Unsafe.As(ref location)); return Unsafe.As(ref incremented); } internal static ulong Exchange(ref ulong location, ulong value) { - long original = Interlocked.Exchange(ref Unsafe.As(ref location), Unsafe.As(ref value)); + var original = Interlocked.Exchange(ref Unsafe.As(ref location), Unsafe.As(ref value)); return Unsafe.As(ref original); } } @@ -296,7 +296,7 @@ internal static void Deconstruct(this KeyValuePair k internal static class ChannelReaderExtensions { - public static async IAsyncEnumerable ReadAllLoopAsync(this ChannelReader reader, [EnumeratorCancellation] CancellationToken cancellationToken = default) + public static async IAsyncEnumerable ReadAllAsync(this ChannelReader reader, [EnumeratorCancellation] CancellationToken cancellationToken = default) { while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) { diff --git a/src/NATS.Client.Core/NatsConnection.RequestReply.cs b/src/NATS.Client.Core/NatsConnection.RequestReply.cs index 41f177a59..79cf34d87 100644 --- a/src/NATS.Client.Core/NatsConnection.RequestReply.cs +++ b/src/NATS.Client.Core/NatsConnection.RequestReply.cs @@ -45,12 +45,7 @@ public async ValueTask> RequestAsync( await using var sub1 = await RequestSubAsync(subject, data, headers, requestSerializer, replySerializer, requestOpts, replyOpts, cancellationToken) .ConfigureAwait(false); -#if NETSTANDARD2_0 - await foreach (var msg in sub1.Msgs.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) -#else - // Prefer ReadAllAsync() since underlying ActivityEndingMsgReader maintains GCHandle for the subscription more efficiently. await foreach (var msg in sub1.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) -#endif { return msg; } @@ -68,12 +63,7 @@ public async ValueTask> RequestAsync( await using var sub = await RequestSubAsync(subject, data, headers, requestSerializer, replySerializer, requestOpts, replyOpts, cancellationToken) .ConfigureAwait(false); -#if NETSTANDARD2_0 - await foreach (var msg in sub.Msgs.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) -#else - // Prefer ReadAllAsync() since underlying ActivityEndingMsgReader maintains GCHandle for the subscription more efficiently. await foreach (var msg in sub.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) -#endif { return msg; } @@ -112,12 +102,7 @@ public async IAsyncEnumerable> RequestManyAsync(subject, data, headers, requestSerializer, replySerializer, requestOpts, replyOpts, cancellationToken) .ConfigureAwait(false); -#if NETSTANDARD2_0 - await foreach (var msg in sub.Msgs.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) -#else - // Prefer ReadAllAsync() since underlying ActivityEndingMsgReader maintains GCHandle for the subscription more efficiently. await foreach (var msg in sub.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) -#endif { yield return msg; } diff --git a/src/NATS.Client.Core/NatsConnection.Subscribe.cs b/src/NATS.Client.Core/NatsConnection.Subscribe.cs index ce9d1148d..d19de9f13 100644 --- a/src/NATS.Client.Core/NatsConnection.Subscribe.cs +++ b/src/NATS.Client.Core/NatsConnection.Subscribe.cs @@ -18,12 +18,7 @@ public async IAsyncEnumerable> SubscribeAsync(string subject, stri // We don't cancel the channel reader here because we want to keep reading until the subscription // channel writer completes so that messages left in the channel can be consumed before exit the loop. -#if NETSTANDARD2_0 - await foreach (var msg in sub.Msgs.ReadAllLoopAsync(CancellationToken.None).ConfigureAwait(false)) -#else - // Prefer ReadAllAsync() since underlying ActivityEndingMsgReader maintains GCHandle for the subscription more efficiently. await foreach (var msg in sub.Msgs.ReadAllAsync(CancellationToken.None).ConfigureAwait(false)) -#endif { yield return msg; } diff --git a/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs b/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs index 3cbdb0d74..85f21f5f4 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs @@ -441,11 +441,7 @@ private void Pull(string origin, long batch, long maxBytes) => _pullRequests.Wri private async Task PullLoop() { -#if NETSTANDARD2_0 - await foreach (var pr in _pullRequests.Reader.ReadAllLoopAsync().ConfigureAwait(false)) -#else await foreach (var pr in _pullRequests.Reader.ReadAllAsync().ConfigureAwait(false)) -#endif { var origin = $"pull-loop({pr.Origin})"; await CallMsgNextAsync(origin, pr.Request).ConfigureAwait(false); diff --git a/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs b/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs index 85a6cdd00..b07fa4146 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs @@ -394,11 +394,7 @@ private void Pull(string origin, long batch, long maxBytes) => _pullRequests.Wri private async Task PullLoop() { -#if NETSTANDARD2_0 - await foreach (var pr in _pullRequests.Reader.ReadAllLoopAsync().ConfigureAwait(false)) -#else await foreach (var pr in _pullRequests.Reader.ReadAllAsync().ConfigureAwait(false)) -#endif { var origin = $"pull-loop({pr.Origin})"; await CallMsgNextAsync(origin, pr.Request).ConfigureAwait(false); diff --git a/src/NATS.Client.JetStream/NatsJSConsumer.cs b/src/NATS.Client.JetStream/NatsJSConsumer.cs index a264186ba..055013713 100644 --- a/src/NATS.Client.JetStream/NatsJSConsumer.cs +++ b/src/NATS.Client.JetStream/NatsJSConsumer.cs @@ -151,11 +151,7 @@ public async IAsyncEnumerable> ConsumeAsync( serializer, cancellationToken: cancellationToken).ConfigureAwait(false); -#if NETSTANDARD2_0 - await foreach (var natsJSMsg in f.Msgs.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) -#else await foreach (var natsJSMsg in f.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) -#endif { return natsJSMsg; } @@ -266,11 +262,7 @@ public async IAsyncEnumerable> FetchNoWaitAsync( serializer ??= _context.Connection.Opts.SerializerRegistry.GetDeserializer(); await using var fc = await FetchInternalAsync(opts with { NoWait = true }, serializer, cancellationToken).ConfigureAwait(false); -#if NETSTANDARD2_0 - await foreach (var jsMsg in fc.Msgs.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) -#else await foreach (var jsMsg in fc.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) -#endif { yield return jsMsg; } diff --git a/src/NATS.Client.JetStream/NatsJSContext.cs b/src/NATS.Client.JetStream/NatsJSContext.cs index df1e4a3d1..8b4c0e6eb 100644 --- a/src/NATS.Client.JetStream/NatsJSContext.cs +++ b/src/NATS.Client.JetStream/NatsJSContext.cs @@ -147,11 +147,7 @@ public async ValueTask PublishAsync( try { -#if NETSTANDARD2_0 - await foreach (var msg in sub.Msgs.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) -#else await foreach (var msg in sub.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) -#endif { if (msg.Data == null) { @@ -247,11 +243,7 @@ internal async ValueTask> JSRequestAsync> WatchAsync(IEnumerable } } -#if NETSTANDARD2_0 - await foreach (var entry in watcher.Entries.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) -#else await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken).ConfigureAwait(false)) -#endif { yield return entry; } @@ -370,11 +366,7 @@ public async IAsyncEnumerable> HistoryAsync(string key, INatsD await using var watcher = await WatchInternalAsync([key], serializer, opts, cancellationToken); -#if NETSTANDARD2_0 - await foreach (var entry in watcher.Entries.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) -#else await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken).ConfigureAwait(false)) -#endif { yield return entry; if (entry.Delta == 0) @@ -411,11 +403,7 @@ public async ValueTask PurgeDeletesAsync(NatsKVPurgeOpts? opts = default, Cancel if (watcher.InitialConsumer.Info.NumPending == 0) return; -#if NETSTANDARD2_0 - await foreach (var entry in watcher.Entries.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) -#else await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken).ConfigureAwait(false)) -#endif { if (entry.Operation is NatsKVOperation.Purge or NatsKVOperation.Del) deleted.Add(entry); @@ -456,11 +444,7 @@ public async IAsyncEnumerable GetKeysAsync(NatsKVWatchOpts? opts = defau if (watcher.InitialConsumer.Info.NumPending == 0) yield break; -#if NETSTANDARD2_0 - await foreach (var entry in watcher.Entries.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) -#else await foreach (var entry in watcher.Entries.ReadAllAsync(cancellationToken).ConfigureAwait(false)) -#endif { if (entry.Operation is NatsKVOperation.Put) yield return entry.Key; diff --git a/src/NATS.Client.ObjectStore/NatsObjStore.cs b/src/NATS.Client.ObjectStore/NatsObjStore.cs index 8b163e1f5..01207c2aa 100644 --- a/src/NATS.Client.ObjectStore/NatsObjStore.cs +++ b/src/NATS.Client.ObjectStore/NatsObjStore.cs @@ -105,11 +105,7 @@ public async ValueTask GetAsync(string key, Stream stream, bool await using (var hashedStream = new CryptoStream(stream, sha256, CryptoStreamMode.Write, leaveOpen)) #endif { -#if NETSTANDARD2_0 - await foreach (var msg in pushConsumer.Msgs.ReadAllLoopAsync(cancellationToken)) -#else await foreach (var msg in pushConsumer.Msgs.ReadAllAsync(cancellationToken)) -#endif { // We have to make sure to carry on consuming the channel to avoid any blocking: // e.g. if the channel is full, we would be blocking the reads off the socket (this was intentionally @@ -609,11 +605,7 @@ public async IAsyncEnumerable WatchAsync(NatsObjWatchOpts? opts pushConsumer.Init(); -#if NETSTANDARD2_0 - await foreach (var msg in pushConsumer.Msgs.ReadAllLoopAsync(cancellationToken).ConfigureAwait(false)) -#else await foreach (var msg in pushConsumer.Msgs.ReadAllAsync(cancellationToken).ConfigureAwait(false)) -#endif { if (pushConsumer.IsDone) continue; diff --git a/src/NATS.Client.Services/Internal/SvcListener.cs b/src/NATS.Client.Services/Internal/SvcListener.cs index 73159ca24..7501ed948 100644 --- a/src/NATS.Client.Services/Internal/SvcListener.cs +++ b/src/NATS.Client.Services/Internal/SvcListener.cs @@ -33,11 +33,7 @@ public async ValueTask StartAsync() { await using (sub) { -#if NETSTANDARD2_0 - await foreach (var msg in sub.Msgs.ReadAllLoopAsync()) -#else await foreach (var msg in sub.Msgs.ReadAllAsync()) -#endif { await _channel.Writer.WriteAsync(new SvcMsg(_type, msg), _cts.Token).ConfigureAwait(false); } diff --git a/src/NATS.Client.Services/NatsSvcEndPoint.cs b/src/NATS.Client.Services/NatsSvcEndPoint.cs index f160d8e34..13ade239d 100644 --- a/src/NATS.Client.Services/NatsSvcEndPoint.cs +++ b/src/NATS.Client.Services/NatsSvcEndPoint.cs @@ -215,11 +215,7 @@ protected override ValueTask ReceiveInternalAsync( private async Task HandlerLoop() { var stopwatch = new Stopwatch(); -#if NETSTANDARD2_0 - await foreach (var svcMsg in _channel.Reader.ReadAllLoopAsync(_cancellationToken).ConfigureAwait(false)) -#else await foreach (var svcMsg in _channel.Reader.ReadAllAsync(_cancellationToken).ConfigureAwait(false)) -#endif { Interlocked.Increment(ref _requests); stopwatch.Restart(); diff --git a/src/NATS.Client.Services/NatsSvcServer.cs b/src/NATS.Client.Services/NatsSvcServer.cs index e3e136a76..a9493337e 100644 --- a/src/NATS.Client.Services/NatsSvcServer.cs +++ b/src/NATS.Client.Services/NatsSvcServer.cs @@ -230,11 +230,7 @@ private async ValueTask AddEndpointInternalAsync(Func, ValueTas private async Task MsgLoop() { -#if NETSTANDARD2_0 - await foreach (var svcMsg in _channel.Reader.ReadAllLoopAsync(_cts.Token)) -#else await foreach (var svcMsg in _channel.Reader.ReadAllAsync(_cts.Token)) -#endif { try { From dec2a26f88b5e25ee9fd3f09c8e1a15d5f6d7da3 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 14 Jun 2024 12:20:15 +0100 Subject: [PATCH 29/47] simplify diffs --- .../Internal/ActivityEndingMsgReader.cs | 6 ++--- src/NATS.Client.Core/NatsSubBase.cs | 25 +++++++++++-------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/NATS.Client.Core/Internal/ActivityEndingMsgReader.cs b/src/NATS.Client.Core/Internal/ActivityEndingMsgReader.cs index d475b37ba..9908faa6f 100644 --- a/src/NATS.Client.Core/Internal/ActivityEndingMsgReader.cs +++ b/src/NATS.Client.Core/Internal/ActivityEndingMsgReader.cs @@ -109,11 +109,11 @@ public override bool TryPeek(out NatsMsg item) return _inner.TryPeek(out item); } - public #if NETSTANDARD2_1 || NET6_0_OR_GREATER - override + public override async IAsyncEnumerable> ReadAllAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) +#else + public async IAsyncEnumerable> ReadAllAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) #endif - async IAsyncEnumerable> ReadAllAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) { var handle = GCHandle.Alloc(_sub); try diff --git a/src/NATS.Client.Core/NatsSubBase.cs b/src/NATS.Client.Core/NatsSubBase.cs index 56435e343..e7f27a751 100644 --- a/src/NATS.Client.Core/NatsSubBase.cs +++ b/src/NATS.Client.Core/NatsSubBase.cs @@ -75,20 +75,23 @@ internal NatsSubBase( // might be a problem. This should reduce the impact of that problem. cancellationToken.ThrowIfCancellationRequested(); - _tokenRegistration = cancellationToken #if NET6_0_OR_GREATER - .UnsafeRegister( + _tokenRegistration = cancellationToken.UnsafeRegister( + state => + { + var self = (NatsSubBase)state!; + self.EndSubscription(NatsSubEndReason.Cancelled); + }, + this); #else - .Register( + _tokenRegistration = cancellationToken.Register( + state => + { + var self = (NatsSubBase)state!; + self.EndSubscription(NatsSubEndReason.Cancelled); + }, + this); #endif -#pragma warning disable SA1114 - state => - { - var self = (NatsSubBase)state!; - self.EndSubscription(NatsSubEndReason.Cancelled); - }, -#pragma warning restore SA1114 - this); // Only allocate timers if necessary to reduce GC pressure if (_idleTimeout != default) From b53cd08079fb0795c3477eded3f0e740cf66a682 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 14 Jun 2024 12:29:27 +0100 Subject: [PATCH 30/47] simplify diffs --- src/NATS.Client.Core/NatsTlsOpts.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/NATS.Client.Core/NatsTlsOpts.cs b/src/NATS.Client.Core/NatsTlsOpts.cs index 3f022307f..ce6d17651 100644 --- a/src/NATS.Client.Core/NatsTlsOpts.cs +++ b/src/NATS.Client.Core/NatsTlsOpts.cs @@ -125,14 +125,11 @@ public sealed record NatsTlsOpts /// TLS mode to use during connection public TlsMode Mode { get; init; } - internal bool HasTlsCerts => - CertFile != default - || KeyFile != default - || CaFile != default #if NETSTANDARD2_1 || NET6_0_OR_GREATER - || ConfigureClientAuthentication != default + internal bool HasTlsCerts => CertFile != default || KeyFile != default || CaFile != default || ConfigureClientAuthentication != default; +#else + internal bool HasTlsCerts => CertFile != default || KeyFile != default || CaFile != default; #endif - ; #if NET6_0_OR_GREATER /// From 6e6db0c957ac79f325143a69b506fcfb4a3dfc57 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 14 Jun 2024 12:54:42 +0100 Subject: [PATCH 31/47] simplify diffs --- src/NATS.Client.Core/Internal/netstandard.cs | 36 ++++++++++++++++++-- src/NATS.Client.Core/NatsBufferWriter.cs | 12 +++---- src/NATS.Client.JetStream/NatsJSContext.cs | 19 +++-------- 3 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/NATS.Client.Core/Internal/netstandard.cs b/src/NATS.Client.Core/Internal/netstandard.cs index dfa5ee521..14687a729 100644 --- a/src/NATS.Client.Core/Internal/netstandard.cs +++ b/src/NATS.Client.Core/Internal/netstandard.cs @@ -2,6 +2,9 @@ // ReSharper disable ConvertToPrimaryConstructor // ReSharper disable RedundantCast // ReSharper disable SuggestVarOrType_Elsewhere +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable ArrangeConstructorOrDestructorBody +// ReSharper disable ArrangeMethodOrOperatorBody #pragma warning disable SA1403 #pragma warning disable SA1204 @@ -15,12 +18,23 @@ namespace System.Runtime.CompilerServices internal static class IsExternalInit { } + + internal sealed class CallerArgumentExpressionAttribute : Attribute + { + public CallerArgumentExpressionAttribute(string parameterName) + { + ParameterName = parameterName; + } + + public string ParameterName { get; } + } } namespace NATS.Client.Core.Internal.NetStandardExtensions { using System.Buffers; using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -217,9 +231,9 @@ internal static long GetOffset(this in ReadOnlySequence sequence, Sequence throw new ArgumentOutOfRangeException(); } - Debug.Assert(currentSegment!.RunningIndex + positionIndex >= 0); + Debug.Assert(currentSegment.RunningIndex + positionIndex >= 0); - return currentSegment!.RunningIndex + positionIndex; + return currentSegment.RunningIndex + positionIndex; } } } @@ -250,12 +264,30 @@ internal static ulong Exchange(ref ulong location, ulong value) return Unsafe.As(ref original); } } + + internal static class ArgumentNullExceptionEx + { + public static void ThrowIfNull([NotNull] object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) + { + if (argument is null) + { + throw new ArgumentException(paramName); + } + } + } } #endif #if NETSTANDARD2_0 +namespace System.Diagnostics.CodeAnalysis +{ + internal sealed class NotNullAttribute : Attribute + { + } +} + namespace NATS.Client.Core.Internal.NetStandardExtensions { using System.Runtime.CompilerServices; diff --git a/src/NATS.Client.Core/NatsBufferWriter.cs b/src/NATS.Client.Core/NatsBufferWriter.cs index f74c00a2c..b12f7e645 100644 --- a/src/NATS.Client.Core/NatsBufferWriter.cs +++ b/src/NATS.Client.Core/NatsBufferWriter.cs @@ -7,6 +7,9 @@ #if NET6_0_OR_GREATER using BitOperations = System.Numerics.BitOperations; #endif +#if NETSTANDARD2_0 +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Core; @@ -391,14 +394,7 @@ internal static class NatsBufferWriterExtensions /// Indicates whether the contents of the array should be cleared before reuse. /// Thrown when is less than 0. /// When this method returns, the caller must not use any references to the old array anymore. - public static void Resize( - this ArrayPool pool, -#if !NETSTANDARD2_0 - [NotNull] -#endif - ref T[]? array, - int newSize, - bool clearArray = false) + public static void Resize(this ArrayPool pool, [NotNull] ref T[]? array, int newSize, bool clearArray = false) { // If the old array is null, just create a new one with the requested size if (array is null) diff --git a/src/NATS.Client.JetStream/NatsJSContext.cs b/src/NATS.Client.JetStream/NatsJSContext.cs index 8b4c0e6eb..3b5f6ecf0 100644 --- a/src/NATS.Client.JetStream/NatsJSContext.cs +++ b/src/NATS.Client.JetStream/NatsJSContext.cs @@ -4,7 +4,7 @@ using NATS.Client.Core; using NATS.Client.JetStream.Internal; using NATS.Client.JetStream.Models; -#if NETSTANDARD2_0 +#if NETSTANDARD2_0 || NETSTANDARD2_1 using NATS.Client.Core.Internal.NetStandardExtensions; #endif @@ -173,21 +173,12 @@ public async ValueTask PublishAsync( throw new NatsJSPublishNoResponseException(); } - internal static void ThrowIfInvalidStreamName( -#if NETSTANDARD2_1 || NET6_0_OR_GREATER - [NotNull] -#endif - string? name, -#if NET6_0_OR_GREATER - [CallerArgumentExpression("name")] -#endif - string? paramName = null) + internal static void ThrowIfInvalidStreamName([NotNull] string? name, [CallerArgumentExpression("name")] string? paramName = null) { -#if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(name, paramName); +#if NETSTANDARD2_0 || NETSTANDARD2_1 + ArgumentNullExceptionEx.ThrowIfNull(name, paramName); #else - if (name == null) - throw new ArgumentException(); + ArgumentNullException.ThrowIfNull(name, paramName); #endif if (name.Length == 0) From 5cebd56482c14725cd340e2c99db7717715656d0 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 14 Jun 2024 13:29:26 +0100 Subject: [PATCH 32/47] use Nullable package --- .../Commands/CommandWriter.cs | 2 +- src/NATS.Client.Core/INatsSerialize.cs | 2 +- .../Internal/NatsReadProtocolProcessor.cs | 2 +- src/NATS.Client.Core/Internal/NuidWriter.cs | 11 ++-------- src/NATS.Client.Core/Internal/SocketReader.cs | 2 +- .../Internal/TcpConnection.cs | 2 +- .../Internal/WebSocketConnection.cs | 2 +- src/NATS.Client.Core/Internal/netstandard.cs | 13 +++++------- src/NATS.Client.Core/NATS.Client.Core.csproj | 1 + .../NatsConnection.LowLevelApi.cs | 2 +- src/NATS.Client.Core/NatsConnection.Ping.cs | 2 +- .../NatsConnection.Publish.cs | 2 +- .../NatsConnection.RequestReply.cs | 2 -- src/NATS.Client.Core/NatsConnection.cs | 2 +- src/NATS.Client.Core/NatsHeaderParser.cs | 2 +- src/NATS.Client.Core/NatsHeaders.cs | 5 +++-- src/NATS.Client.Core/NatsMsg.cs | 20 ++++++------------- .../Internal/NatsJSOrderedPushConsumer.cs | 8 ++++---- .../Internal/netstandard.cs | 2 +- src/NATS.Client.JetStream/NatsJSContext.cs | 4 ++-- src/NATS.Client.JetStream/NatsJSMsg.cs | 17 ++++++---------- 21 files changed, 41 insertions(+), 64 deletions(-) diff --git a/src/NATS.Client.Core/Commands/CommandWriter.cs b/src/NATS.Client.Core/Commands/CommandWriter.cs index 6f12d944c..8be74b192 100644 --- a/src/NATS.Client.Core/Commands/CommandWriter.cs +++ b/src/NATS.Client.Core/Commands/CommandWriter.cs @@ -4,7 +4,7 @@ using System.Threading.Channels; using Microsoft.Extensions.Logging; using NATS.Client.Core.Internal; -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Core/INatsSerialize.cs b/src/NATS.Client.Core/INatsSerialize.cs index ff1958a20..8ca2676b7 100644 --- a/src/NATS.Client.Core/INatsSerialize.cs +++ b/src/NATS.Client.Core/INatsSerialize.cs @@ -4,7 +4,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs b/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs index 460179f78..5f12742b9 100644 --- a/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs +++ b/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs @@ -8,7 +8,7 @@ using System.Text.Json; using Microsoft.Extensions.Logging; using NATS.Client.Core.Commands; -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Core/Internal/NuidWriter.cs b/src/NATS.Client.Core/Internal/NuidWriter.cs index 894175624..43f068af4 100644 --- a/src/NATS.Client.Core/Internal/NuidWriter.cs +++ b/src/NATS.Client.Core/Internal/NuidWriter.cs @@ -2,15 +2,14 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD +using NATS.Client.Core.Internal.NetStandardExtensions; using Random = NATS.Client.Core.Internal.NetStandardExtensions.Random; #endif namespace NATS.Client.Core.Internal; -#if NET6_0_OR_GREATER [SkipLocalsInit] -#endif internal sealed class NuidWriter { internal const nuint NuidLength = PrefixLength + SequentialLength; @@ -24,11 +23,7 @@ internal sealed class NuidWriter [ThreadStatic] private static NuidWriter? _writer; -#if NET6_0_OR_GREATER private char[] _prefix; -#else - private char[] _prefix = null!; -#endif private ulong _increment; private ulong _sequential; @@ -164,9 +159,7 @@ bool RefreshAndWrite(Span buffer) } [MethodImpl(MethodImplOptions.NoInlining)] -#if NET6_0_OR_GREATER [MemberNotNull(nameof(_prefix))] -#endif private char[] Refresh(out ulong sequential) { var prefix = _prefix = GetPrefix(); diff --git a/src/NATS.Client.Core/Internal/SocketReader.cs b/src/NATS.Client.Core/Internal/SocketReader.cs index e9e1cc9b1..bbd1065aa 100644 --- a/src/NATS.Client.Core/Internal/SocketReader.cs +++ b/src/NATS.Client.Core/Internal/SocketReader.cs @@ -2,7 +2,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Core/Internal/TcpConnection.cs b/src/NATS.Client.Core/Internal/TcpConnection.cs index 8ea3763a8..43ebc5284 100644 --- a/src/NATS.Client.Core/Internal/TcpConnection.cs +++ b/src/NATS.Client.Core/Internal/TcpConnection.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Core/Internal/WebSocketConnection.cs b/src/NATS.Client.Core/Internal/WebSocketConnection.cs index e430d0d88..12e6c43c2 100644 --- a/src/NATS.Client.Core/Internal/WebSocketConnection.cs +++ b/src/NATS.Client.Core/Internal/WebSocketConnection.cs @@ -1,7 +1,7 @@ using System.Net.Sockets; using System.Net.WebSockets; using System.Runtime.CompilerServices; -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD using System.Runtime.InteropServices; #endif diff --git a/src/NATS.Client.Core/Internal/netstandard.cs b/src/NATS.Client.Core/Internal/netstandard.cs index 14687a729..40f4b00a2 100644 --- a/src/NATS.Client.Core/Internal/netstandard.cs +++ b/src/NATS.Client.Core/Internal/netstandard.cs @@ -10,7 +10,7 @@ #pragma warning disable SA1204 #pragma warning disable SA1405 -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD // Enable init only setters namespace System.Runtime.CompilerServices @@ -28,6 +28,10 @@ public CallerArgumentExpressionAttribute(string parameterName) public string ParameterName { get; } } + + internal sealed class SkipLocalsInitAttribute : Attribute + { + } } namespace NATS.Client.Core.Internal.NetStandardExtensions @@ -281,13 +285,6 @@ public static void ThrowIfNull([NotNull] object? argument, [CallerArgumentExpres #if NETSTANDARD2_0 -namespace System.Diagnostics.CodeAnalysis -{ - internal sealed class NotNullAttribute : Attribute - { - } -} - namespace NATS.Client.Core.Internal.NetStandardExtensions { using System.Runtime.CompilerServices; diff --git a/src/NATS.Client.Core/NATS.Client.Core.csproj b/src/NATS.Client.Core/NATS.Client.Core.csproj index 1f8757290..b2c233354 100644 --- a/src/NATS.Client.Core/NATS.Client.Core.csproj +++ b/src/NATS.Client.Core/NATS.Client.Core.csproj @@ -21,6 +21,7 @@ + diff --git a/src/NATS.Client.Core/NatsConnection.LowLevelApi.cs b/src/NATS.Client.Core/NatsConnection.LowLevelApi.cs index c67aac5e2..10decf979 100644 --- a/src/NATS.Client.Core/NatsConnection.LowLevelApi.cs +++ b/src/NATS.Client.Core/NatsConnection.LowLevelApi.cs @@ -1,4 +1,4 @@ -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Core/NatsConnection.Ping.cs b/src/NATS.Client.Core/NatsConnection.Ping.cs index c570bb5c1..7cfd9fe55 100644 --- a/src/NATS.Client.Core/NatsConnection.Ping.cs +++ b/src/NATS.Client.Core/NatsConnection.Ping.cs @@ -1,6 +1,6 @@ using System.Runtime.CompilerServices; using NATS.Client.Core.Commands; -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Core/NatsConnection.Publish.cs b/src/NATS.Client.Core/NatsConnection.Publish.cs index 1df47bfc1..3115e8f82 100644 --- a/src/NATS.Client.Core/NatsConnection.Publish.cs +++ b/src/NATS.Client.Core/NatsConnection.Publish.cs @@ -1,6 +1,6 @@ using System.Diagnostics; using NATS.Client.Core.Internal; -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Core/NatsConnection.RequestReply.cs b/src/NATS.Client.Core/NatsConnection.RequestReply.cs index 79cf34d87..dd8f9fa69 100644 --- a/src/NATS.Client.Core/NatsConnection.RequestReply.cs +++ b/src/NATS.Client.Core/NatsConnection.RequestReply.cs @@ -112,9 +112,7 @@ public async IAsyncEnumerable> RequestManyAsync NewInbox(prefix.AsSpan()); #endif -#if NET6_0_OR_GREATER [SkipLocalsInit] -#endif internal static string NewInbox(ReadOnlySpan prefix) { Span buffer = stackalloc char[64]; diff --git a/src/NATS.Client.Core/NatsConnection.cs b/src/NATS.Client.Core/NatsConnection.cs index f5f73ff41..e684fa202 100644 --- a/src/NATS.Client.Core/NatsConnection.cs +++ b/src/NATS.Client.Core/NatsConnection.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.Logging; using NATS.Client.Core.Commands; using NATS.Client.Core.Internal; -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD using NATS.Client.Core.Internal.NetStandardExtensions; using Random = NATS.Client.Core.Internal.NetStandardExtensions.Random; #endif diff --git a/src/NATS.Client.Core/NatsHeaderParser.cs b/src/NATS.Client.Core/NatsHeaderParser.cs index 7e8d1e031..bf443aae9 100644 --- a/src/NATS.Client.Core/NatsHeaderParser.cs +++ b/src/NATS.Client.Core/NatsHeaderParser.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Primitives; using NATS.Client.Core.Commands; using NATS.Client.Core.Internal; -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Core/NatsHeaders.cs b/src/NATS.Client.Core/NatsHeaders.cs index 32a5c0799..558b86553 100644 --- a/src/NATS.Client.Core/NatsHeaders.cs +++ b/src/NATS.Client.Core/NatsHeaders.cs @@ -2,6 +2,9 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Primitives; +#if NETSTANDARD +using NATS.Client.Core.Internal.NetStandardExtensions; +#endif namespace NATS.Client.Core; @@ -102,9 +105,7 @@ public NatsHeaders(int capacity) private Dictionary? Store { get; set; } -#if NET6_0_OR_GREATER [MemberNotNull(nameof(Store))] -#endif private void EnsureStore(int capacity) { if (Store == null) diff --git a/src/NATS.Client.Core/NatsMsg.cs b/src/NATS.Client.Core/NatsMsg.cs index 33cd06278..df041ff69 100644 --- a/src/NATS.Client.Core/NatsMsg.cs +++ b/src/NATS.Client.Core/NatsMsg.cs @@ -148,11 +148,7 @@ public void EnsureSuccess() public ValueTask ReplyAsync(NatsHeaders? headers = default, string? replyTo = default, NatsPubOpts? opts = default, CancellationToken cancellationToken = default) { CheckReplyPreconditions(); -#if NET6_0_OR_GREATER return Connection.PublishAsync(ReplyTo, headers, replyTo, opts, cancellationToken); -#else - return Connection!.PublishAsync(ReplyTo!, headers, replyTo, opts, cancellationToken); -#endif } /// @@ -180,11 +176,7 @@ public ValueTask ReplyAsync(NatsHeaders? headers = default, string? replyTo = de public ValueTask ReplyAsync(TReply data, NatsHeaders? headers = default, string? replyTo = default, INatsSerialize? serializer = default, NatsPubOpts? opts = default, CancellationToken cancellationToken = default) { CheckReplyPreconditions(); -#if NET6_0_OR_GREATER return Connection.PublishAsync(ReplyTo, data, headers, replyTo, serializer, opts, cancellationToken); -#else - return Connection!.PublishAsync(ReplyTo!, data, headers, replyTo, serializer, opts, cancellationToken); -#endif } /// @@ -202,11 +194,7 @@ public ValueTask ReplyAsync(TReply data, NatsHeaders? headers = default, public ValueTask ReplyAsync(NatsMsg msg, INatsSerialize? serializer = default, NatsPubOpts? opts = default, CancellationToken cancellationToken = default) { CheckReplyPreconditions(); -#if NET6_0_OR_GREATER return Connection.PublishAsync(msg with { Subject = ReplyTo }, serializer, opts, cancellationToken); -#else - return Connection!.PublishAsync(msg with { Subject = ReplyTo! }, serializer, opts, cancellationToken); -#endif } internal static NatsMsg Build( @@ -292,10 +280,8 @@ internal static NatsMsg Build( return new NatsMsg(subject, replyTo, (int)size, headers, data, connection); } -#if NET6_0_OR_GREATER [MemberNotNull(nameof(Connection))] [MemberNotNull(nameof(ReplyTo))] -#endif private void CheckReplyPreconditions() { if (Connection == default) @@ -307,7 +293,13 @@ private void CheckReplyPreconditions() { throw new NatsException("unable to send reply; ReplyTo is empty"); } +#if NETSTANDARD +#pragma warning disable CS8774 // Member must have a non-null value when exiting. +#endif } +#if NETSTANDARD +#pragma warning restore CS8774 // Member must have a non-null value when exiting. +#endif } public class NatsDeserializeException : NatsException diff --git a/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs b/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs index 719e2a67a..04481ecc8 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs @@ -4,7 +4,7 @@ using NATS.Client.Core; using NATS.Client.Core.Internal; using NATS.Client.JetStream.Models; -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD using NATS.Client.Core.Internal.NetStandardExtensions; #endif @@ -222,7 +222,7 @@ private async Task CommandLoop() continue; } -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD var sequence = InterlockedEx.Increment(ref _sequenceConsumer); #else var sequence = Interlocked.Increment(ref _sequenceConsumer); @@ -238,7 +238,7 @@ private async Task CommandLoop() // Increment the sequence before writing to the channel in case the channel is full // and the writer is waiting for the reader to read the message. This way the sequence // will be correctly incremented in case the timeout kicks in and recreated the consumer. -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD InterlockedEx.Exchange(ref _sequenceStream, metadata.Sequence.Stream); #else Interlocked.Exchange(ref _sequenceStream, metadata.Sequence.Stream); @@ -342,7 +342,7 @@ private async ValueTask CreatePushConsumer(string origin) _logger.LogDebug(NatsJSLogEvents.NewDeliverySubject, "New delivery subject {Subject}", _sub.Subject); } -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD InterlockedEx.Exchange(ref _sequenceConsumer, 0); #else Interlocked.Exchange(ref _sequenceConsumer, 0); diff --git a/src/NATS.Client.JetStream/Internal/netstandard.cs b/src/NATS.Client.JetStream/Internal/netstandard.cs index a3b99ff12..f14282452 100644 --- a/src/NATS.Client.JetStream/Internal/netstandard.cs +++ b/src/NATS.Client.JetStream/Internal/netstandard.cs @@ -1,4 +1,4 @@ -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD #pragma warning disable SA1201 #pragma warning disable SA1403 diff --git a/src/NATS.Client.JetStream/NatsJSContext.cs b/src/NATS.Client.JetStream/NatsJSContext.cs index 3b5f6ecf0..a4836471b 100644 --- a/src/NATS.Client.JetStream/NatsJSContext.cs +++ b/src/NATS.Client.JetStream/NatsJSContext.cs @@ -4,7 +4,7 @@ using NATS.Client.Core; using NATS.Client.JetStream.Internal; using NATS.Client.JetStream.Models; -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD using NATS.Client.Core.Internal.NetStandardExtensions; #endif @@ -175,7 +175,7 @@ public async ValueTask PublishAsync( internal static void ThrowIfInvalidStreamName([NotNull] string? name, [CallerArgumentExpression("name")] string? paramName = null) { -#if NETSTANDARD2_0 || NETSTANDARD2_1 +#if NETSTANDARD ArgumentNullExceptionEx.ThrowIfNull(name, paramName); #else ArgumentNullException.ThrowIfNull(name, paramName); diff --git a/src/NATS.Client.JetStream/NatsJSMsg.cs b/src/NATS.Client.JetStream/NatsJSMsg.cs index 9c49d4ae4..26977942d 100644 --- a/src/NATS.Client.JetStream/NatsJSMsg.cs +++ b/src/NATS.Client.JetStream/NatsJSMsg.cs @@ -278,21 +278,12 @@ private async ValueTask SendAckAsync(ReadOnlySequence payload, AckOpts? op if (opts?.DoubleAck ?? _context.Opts.DoubleAck) { -#if NET6_0_OR_GREATER await Connection.RequestAsync, object?>( subject: ReplyTo, data: payload, requestSerializer: NatsRawSerializer>.Default, replySerializer: NatsRawSerializer.Default, cancellationToken: cancellationToken); -#else - await Connection!.RequestAsync, object?>( - subject: ReplyTo!, - data: payload, - requestSerializer: NatsRawSerializer>.Default, - replySerializer: NatsRawSerializer.Default, - cancellationToken: cancellationToken); -#endif } else { @@ -303,10 +294,8 @@ await _msg.ReplyAsync( } } -#if NET6_0_OR_GREATER [MemberNotNull(nameof(Connection))] [MemberNotNull(nameof(ReplyTo))] -#endif private void CheckPreconditions() { if (Connection == default) @@ -318,7 +307,13 @@ private void CheckPreconditions() { throw new NatsException("unable to send acknowledgment; ReplyTo is empty"); } +#if NETSTANDARD +#pragma warning disable CS8774 // Member must have a non-null value when exiting. +#endif } +#if NETSTANDARD +#pragma warning restore CS8774 // Member must have a non-null value when exiting. +#endif } /// From 6dd35d678b69b518acfdba43e573831bfe503399 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 14 Jun 2024 13:34:01 +0100 Subject: [PATCH 33/47] simplify --- src/NATS.Client.Core/Internal/ActivityEndingMsgReader.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NATS.Client.Core/Internal/ActivityEndingMsgReader.cs b/src/NATS.Client.Core/Internal/ActivityEndingMsgReader.cs index 9908faa6f..743d13420 100644 --- a/src/NATS.Client.Core/Internal/ActivityEndingMsgReader.cs +++ b/src/NATS.Client.Core/Internal/ActivityEndingMsgReader.cs @@ -109,10 +109,10 @@ public override bool TryPeek(out NatsMsg item) return _inner.TryPeek(out item); } -#if NETSTANDARD2_1 || NET6_0_OR_GREATER - public override async IAsyncEnumerable> ReadAllAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) -#else +#if NETSTANDARD2_0 public async IAsyncEnumerable> ReadAllAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) +#else + public override async IAsyncEnumerable> ReadAllAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) #endif { var handle = GCHandle.Alloc(_sub); From 0ce7c80d23edb75d3c21f9572be8951762d9b4e4 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 14 Jun 2024 13:36:05 +0100 Subject: [PATCH 34/47] use Nullable package --- src/NATS.Client.Core/Internal/ObjectPool.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/NATS.Client.Core/Internal/ObjectPool.cs b/src/NATS.Client.Core/Internal/ObjectPool.cs index d36e7e583..0658294f2 100644 --- a/src/NATS.Client.Core/Internal/ObjectPool.cs +++ b/src/NATS.Client.Core/Internal/ObjectPool.cs @@ -23,11 +23,7 @@ public ObjectPool(int poolLimit) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryRent( -#if NETSTANDARD2_1 || NET6_0_OR_GREATER - [NotNullWhen(true)] -#endif - out T? value) + public bool TryRent([NotNullWhen(true)] out T? value) where T : class, IObjectPoolNode { // poolNodes is grow only, safe to access indexer with no-lock @@ -107,11 +103,7 @@ public ObjectPool(int limit) public int Size => _size; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryPop( -#if NETSTANDARD2_1 || NET6_0_OR_GREATER - [NotNullWhen(true)] -#endif - out T? result) + public bool TryPop([NotNullWhen(true)] out T? result) { // Instead of lock, use CompareExchange gate. // In a worst case, missed cached object(create new one) but it's not a big deal. From cf213e6cfc64c979caeac3bba2765c926cee9c8b Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 14 Jun 2024 13:41:26 +0100 Subject: [PATCH 35/47] simplify --- src/NATS.Client.Core/Internal/SslStreamConnection.cs | 4 ++-- src/NATS.Client.Core/NatsMsg.cs | 10 ++++------ src/NATS.Client.JetStream/NatsJSMsg.cs | 10 ++++------ 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/NATS.Client.Core/Internal/SslStreamConnection.cs b/src/NATS.Client.Core/Internal/SslStreamConnection.cs index cfb416b0f..06b0bfe98 100644 --- a/src/NATS.Client.Core/Internal/SslStreamConnection.cs +++ b/src/NATS.Client.Core/Internal/SslStreamConnection.cs @@ -1,11 +1,11 @@ using System.Net.Security; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Security.Authentication; using Microsoft.Extensions.Logging; #if NETSTANDARD2_0 -#pragma warning disable CS1998 +using System.Runtime.InteropServices; +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously #endif namespace NATS.Client.Core.Internal; diff --git a/src/NATS.Client.Core/NatsMsg.cs b/src/NATS.Client.Core/NatsMsg.cs index df041ff69..5df092f28 100644 --- a/src/NATS.Client.Core/NatsMsg.cs +++ b/src/NATS.Client.Core/NatsMsg.cs @@ -2,6 +2,10 @@ using System.Diagnostics.CodeAnalysis; using NATS.Client.Core.Internal; +#if NETSTANDARD +#pragma warning disable CS8774 // Member must have a non-null value when exiting. +#endif + namespace NATS.Client.Core; /// @@ -293,13 +297,7 @@ private void CheckReplyPreconditions() { throw new NatsException("unable to send reply; ReplyTo is empty"); } -#if NETSTANDARD -#pragma warning disable CS8774 // Member must have a non-null value when exiting. -#endif } -#if NETSTANDARD -#pragma warning restore CS8774 // Member must have a non-null value when exiting. -#endif } public class NatsDeserializeException : NatsException diff --git a/src/NATS.Client.JetStream/NatsJSMsg.cs b/src/NATS.Client.JetStream/NatsJSMsg.cs index 26977942d..b2aa1d4cb 100644 --- a/src/NATS.Client.JetStream/NatsJSMsg.cs +++ b/src/NATS.Client.JetStream/NatsJSMsg.cs @@ -4,6 +4,10 @@ using NATS.Client.Core; using NATS.Client.JetStream.Internal; +#if NETSTANDARD +#pragma warning disable CS8774 // Member must have a non-null value when exiting. +#endif + namespace NATS.Client.JetStream; /// @@ -307,13 +311,7 @@ private void CheckPreconditions() { throw new NatsException("unable to send acknowledgment; ReplyTo is empty"); } -#if NETSTANDARD -#pragma warning disable CS8774 // Member must have a non-null value when exiting. -#endif } -#if NETSTANDARD -#pragma warning restore CS8774 // Member must have a non-null value when exiting. -#endif } /// From 1d2166a92bb38156f312ccc6f9e7747c4261f5fa Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 14 Jun 2024 13:52:41 +0100 Subject: [PATCH 36/47] simplify diffs --- src/NATS.Client.Core/INatsSerialize.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/NATS.Client.Core/INatsSerialize.cs b/src/NATS.Client.Core/INatsSerialize.cs index 8ca2676b7..d0d33d2ba 100644 --- a/src/NATS.Client.Core/INatsSerialize.cs +++ b/src/NATS.Client.Core/INatsSerialize.cs @@ -378,13 +378,11 @@ public void Serialize(IBufferWriter bufferWriter, T value) return (T)(object)Encoding.UTF8.GetString(buffer); } - var span = buffer.IsSingleSegment #if NETSTANDARD2_0 - ? buffer.First.Span + var span = buffer.IsSingleSegment ? buffer.First.Span : buffer.ToArray(); #else - ? buffer.FirstSpan + var span = buffer.IsSingleSegment ? buffer.FirstSpan : buffer.ToArray(); #endif - : buffer.ToArray(); if (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateTime?)) { From e0c282f5ac04db54daea6fe06b1d047d4d491408 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 14 Jun 2024 14:16:13 +0100 Subject: [PATCH 37/47] global using --- src/NATS.Client.Core/Commands/CommandWriter.cs | 3 --- src/NATS.Client.Core/Commands/ProtocolWriter.cs | 3 --- src/NATS.Client.Core/INatsSerialize.cs | 3 --- src/NATS.Client.Core/Internal/DebuggingExtensions.cs | 3 --- src/NATS.Client.Core/Internal/HeaderWriter.cs | 3 --- src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs | 3 --- src/NATS.Client.Core/Internal/NuidWriter.cs | 1 - src/NATS.Client.Core/Internal/SocketReader.cs | 3 --- src/NATS.Client.Core/Internal/SubscriptionManager.cs | 3 --- src/NATS.Client.Core/Internal/TcpConnection.cs | 3 --- src/NATS.Client.Core/NatsBufferWriter.cs | 3 --- src/NATS.Client.Core/NatsConnection.LowLevelApi.cs | 4 ---- src/NATS.Client.Core/NatsConnection.Ping.cs | 3 --- src/NATS.Client.Core/NatsConnection.Publish.cs | 3 --- src/NATS.Client.Core/NatsConnection.RequestReply.cs | 3 --- src/NATS.Client.Core/NatsConnection.Subscribe.cs | 3 --- src/NATS.Client.Core/NatsConnection.cs | 1 - src/NATS.Client.Core/NatsHeaderParser.cs | 3 --- src/NATS.Client.Core/NatsHeaders.cs | 3 --- src/NATS.Client.Core/Usings.cs | 3 +++ src/NATS.Client.JetStream/Internal/NatsJSConsume.cs | 3 --- src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs | 3 --- .../Internal/NatsJSOrderedPushConsumer.cs | 3 --- src/NATS.Client.JetStream/NatsJSConsumer.cs | 3 --- src/NATS.Client.JetStream/NatsJSContext.cs | 3 --- src/NATS.Client.JetStream/Usings.cs | 3 +++ src/NATS.Client.KeyValueStore/NatsKVStore.cs | 3 --- src/NATS.Client.KeyValueStore/Usings.cs | 3 +++ src/NATS.Client.ObjectStore/NatsObjStore.cs | 3 --- src/NATS.Client.ObjectStore/Usings.cs | 3 +++ src/NATS.Client.Services/Internal/SvcListener.cs | 3 --- src/NATS.Client.Services/NatsSvcEndPoint.cs | 3 --- src/NATS.Client.Services/NatsSvcServer.cs | 3 --- src/NATS.Client.Services/Usings.cs | 3 +++ 34 files changed, 15 insertions(+), 84 deletions(-) create mode 100644 src/NATS.Client.Core/Usings.cs create mode 100644 src/NATS.Client.JetStream/Usings.cs create mode 100644 src/NATS.Client.KeyValueStore/Usings.cs create mode 100644 src/NATS.Client.ObjectStore/Usings.cs create mode 100644 src/NATS.Client.Services/Usings.cs diff --git a/src/NATS.Client.Core/Commands/CommandWriter.cs b/src/NATS.Client.Core/Commands/CommandWriter.cs index 8be74b192..272c08606 100644 --- a/src/NATS.Client.Core/Commands/CommandWriter.cs +++ b/src/NATS.Client.Core/Commands/CommandWriter.cs @@ -4,9 +4,6 @@ using System.Threading.Channels; using Microsoft.Extensions.Logging; using NATS.Client.Core.Internal; -#if NETSTANDARD -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core.Commands; diff --git a/src/NATS.Client.Core/Commands/ProtocolWriter.cs b/src/NATS.Client.Core/Commands/ProtocolWriter.cs index 22fcd3213..cf48fbbac 100644 --- a/src/NATS.Client.Core/Commands/ProtocolWriter.cs +++ b/src/NATS.Client.Core/Commands/ProtocolWriter.cs @@ -5,9 +5,6 @@ using System.Text; using System.Text.Json; using NATS.Client.Core.Internal; -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core.Commands; diff --git a/src/NATS.Client.Core/INatsSerialize.cs b/src/NATS.Client.Core/INatsSerialize.cs index d0d33d2ba..d4a5ba84e 100644 --- a/src/NATS.Client.Core/INatsSerialize.cs +++ b/src/NATS.Client.Core/INatsSerialize.cs @@ -4,9 +4,6 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; -#if NETSTANDARD -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core; diff --git a/src/NATS.Client.Core/Internal/DebuggingExtensions.cs b/src/NATS.Client.Core/Internal/DebuggingExtensions.cs index bc703006a..efdcc987d 100644 --- a/src/NATS.Client.Core/Internal/DebuggingExtensions.cs +++ b/src/NATS.Client.Core/Internal/DebuggingExtensions.cs @@ -1,8 +1,5 @@ using System.Buffers; using System.Text; -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core.Internal; diff --git a/src/NATS.Client.Core/Internal/HeaderWriter.cs b/src/NATS.Client.Core/Internal/HeaderWriter.cs index 7e42bd267..8bb62694d 100644 --- a/src/NATS.Client.Core/Internal/HeaderWriter.cs +++ b/src/NATS.Client.Core/Internal/HeaderWriter.cs @@ -2,9 +2,6 @@ using System.IO.Pipelines; using System.Text; using NATS.Client.Core.Commands; -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core.Internal; diff --git a/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs b/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs index 5f12742b9..caf645885 100644 --- a/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs +++ b/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs @@ -8,9 +8,6 @@ using System.Text.Json; using Microsoft.Extensions.Logging; using NATS.Client.Core.Commands; -#if NETSTANDARD -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core.Internal; diff --git a/src/NATS.Client.Core/Internal/NuidWriter.cs b/src/NATS.Client.Core/Internal/NuidWriter.cs index 43f068af4..14b371a8e 100644 --- a/src/NATS.Client.Core/Internal/NuidWriter.cs +++ b/src/NATS.Client.Core/Internal/NuidWriter.cs @@ -3,7 +3,6 @@ using System.Runtime.InteropServices; using System.Security.Cryptography; #if NETSTANDARD -using NATS.Client.Core.Internal.NetStandardExtensions; using Random = NATS.Client.Core.Internal.NetStandardExtensions.Random; #endif diff --git a/src/NATS.Client.Core/Internal/SocketReader.cs b/src/NATS.Client.Core/Internal/SocketReader.cs index bbd1065aa..a5fc5ebe1 100644 --- a/src/NATS.Client.Core/Internal/SocketReader.cs +++ b/src/NATS.Client.Core/Internal/SocketReader.cs @@ -2,9 +2,6 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; -#if NETSTANDARD -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core.Internal; diff --git a/src/NATS.Client.Core/Internal/SubscriptionManager.cs b/src/NATS.Client.Core/Internal/SubscriptionManager.cs index dbc938b62..2f1df1199 100644 --- a/src/NATS.Client.Core/Internal/SubscriptionManager.cs +++ b/src/NATS.Client.Core/Internal/SubscriptionManager.cs @@ -3,9 +3,6 @@ using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; using NATS.Client.Core.Commands; -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core.Internal; diff --git a/src/NATS.Client.Core/Internal/TcpConnection.cs b/src/NATS.Client.Core/Internal/TcpConnection.cs index 43ebc5284..7f039804c 100644 --- a/src/NATS.Client.Core/Internal/TcpConnection.cs +++ b/src/NATS.Client.Core/Internal/TcpConnection.cs @@ -3,9 +3,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; -#if NETSTANDARD -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core.Internal; diff --git a/src/NATS.Client.Core/NatsBufferWriter.cs b/src/NATS.Client.Core/NatsBufferWriter.cs index b12f7e645..8d474c92e 100644 --- a/src/NATS.Client.Core/NatsBufferWriter.cs +++ b/src/NATS.Client.Core/NatsBufferWriter.cs @@ -7,9 +7,6 @@ #if NET6_0_OR_GREATER using BitOperations = System.Numerics.BitOperations; #endif -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core; diff --git a/src/NATS.Client.Core/NatsConnection.LowLevelApi.cs b/src/NATS.Client.Core/NatsConnection.LowLevelApi.cs index 10decf979..9cbcf23e7 100644 --- a/src/NATS.Client.Core/NatsConnection.LowLevelApi.cs +++ b/src/NATS.Client.Core/NatsConnection.LowLevelApi.cs @@ -1,7 +1,3 @@ -#if NETSTANDARD -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif - namespace NATS.Client.Core; public partial class NatsConnection diff --git a/src/NATS.Client.Core/NatsConnection.Ping.cs b/src/NATS.Client.Core/NatsConnection.Ping.cs index 7cfd9fe55..277995fb5 100644 --- a/src/NATS.Client.Core/NatsConnection.Ping.cs +++ b/src/NATS.Client.Core/NatsConnection.Ping.cs @@ -1,8 +1,5 @@ using System.Runtime.CompilerServices; using NATS.Client.Core.Commands; -#if NETSTANDARD -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core; diff --git a/src/NATS.Client.Core/NatsConnection.Publish.cs b/src/NATS.Client.Core/NatsConnection.Publish.cs index 3115e8f82..abe0fbf7b 100644 --- a/src/NATS.Client.Core/NatsConnection.Publish.cs +++ b/src/NATS.Client.Core/NatsConnection.Publish.cs @@ -1,8 +1,5 @@ using System.Diagnostics; using NATS.Client.Core.Internal; -#if NETSTANDARD -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core; diff --git a/src/NATS.Client.Core/NatsConnection.RequestReply.cs b/src/NATS.Client.Core/NatsConnection.RequestReply.cs index dd8f9fa69..724341c3b 100644 --- a/src/NATS.Client.Core/NatsConnection.RequestReply.cs +++ b/src/NATS.Client.Core/NatsConnection.RequestReply.cs @@ -2,9 +2,6 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using NATS.Client.Core.Internal; -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core; diff --git a/src/NATS.Client.Core/NatsConnection.Subscribe.cs b/src/NATS.Client.Core/NatsConnection.Subscribe.cs index d19de9f13..d163db30e 100644 --- a/src/NATS.Client.Core/NatsConnection.Subscribe.cs +++ b/src/NATS.Client.Core/NatsConnection.Subscribe.cs @@ -1,8 +1,5 @@ using System.Collections.Concurrent; using System.Runtime.CompilerServices; -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core; diff --git a/src/NATS.Client.Core/NatsConnection.cs b/src/NATS.Client.Core/NatsConnection.cs index e684fa202..b2e1d3ef4 100644 --- a/src/NATS.Client.Core/NatsConnection.cs +++ b/src/NATS.Client.Core/NatsConnection.cs @@ -5,7 +5,6 @@ using NATS.Client.Core.Commands; using NATS.Client.Core.Internal; #if NETSTANDARD -using NATS.Client.Core.Internal.NetStandardExtensions; using Random = NATS.Client.Core.Internal.NetStandardExtensions.Random; #endif diff --git a/src/NATS.Client.Core/NatsHeaderParser.cs b/src/NATS.Client.Core/NatsHeaderParser.cs index bf443aae9..19b2fd96e 100644 --- a/src/NATS.Client.Core/NatsHeaderParser.cs +++ b/src/NATS.Client.Core/NatsHeaderParser.cs @@ -7,9 +7,6 @@ using Microsoft.Extensions.Primitives; using NATS.Client.Core.Commands; using NATS.Client.Core.Internal; -#if NETSTANDARD -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif // ReSharper disable ConditionIsAlwaysTrueOrFalse // ReSharper disable PossiblyImpureMethodCallOnReadonlyVariable diff --git a/src/NATS.Client.Core/NatsHeaders.cs b/src/NATS.Client.Core/NatsHeaders.cs index 558b86553..e451848db 100644 --- a/src/NATS.Client.Core/NatsHeaders.cs +++ b/src/NATS.Client.Core/NatsHeaders.cs @@ -2,9 +2,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Primitives; -#if NETSTANDARD -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Core; diff --git a/src/NATS.Client.Core/Usings.cs b/src/NATS.Client.Core/Usings.cs new file mode 100644 index 000000000..7e672a940 --- /dev/null +++ b/src/NATS.Client.Core/Usings.cs @@ -0,0 +1,3 @@ +#if NETSTANDARD +global using NATS.Client.Core.Internal.NetStandardExtensions; +#endif diff --git a/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs b/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs index 85f21f5f4..c83f7e0b6 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSConsume.cs @@ -6,9 +6,6 @@ using NATS.Client.Core.Commands; using NATS.Client.Core.Internal; using NATS.Client.JetStream.Models; -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.JetStream.Internal; diff --git a/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs b/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs index b07fa4146..e7c9997b6 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSOrderedConsume.cs @@ -5,9 +5,6 @@ using NATS.Client.Core; using NATS.Client.Core.Commands; using NATS.Client.JetStream.Models; -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.JetStream.Internal; diff --git a/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs b/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs index 04481ecc8..8e7505e60 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs @@ -4,9 +4,6 @@ using NATS.Client.Core; using NATS.Client.Core.Internal; using NATS.Client.JetStream.Models; -#if NETSTANDARD -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.JetStream.Internal; diff --git a/src/NATS.Client.JetStream/NatsJSConsumer.cs b/src/NATS.Client.JetStream/NatsJSConsumer.cs index 055013713..e1c7b400d 100644 --- a/src/NATS.Client.JetStream/NatsJSConsumer.cs +++ b/src/NATS.Client.JetStream/NatsJSConsumer.cs @@ -3,9 +3,6 @@ using NATS.Client.Core; using NATS.Client.JetStream.Internal; using NATS.Client.JetStream.Models; -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.JetStream; diff --git a/src/NATS.Client.JetStream/NatsJSContext.cs b/src/NATS.Client.JetStream/NatsJSContext.cs index a4836471b..deff7f8ee 100644 --- a/src/NATS.Client.JetStream/NatsJSContext.cs +++ b/src/NATS.Client.JetStream/NatsJSContext.cs @@ -4,9 +4,6 @@ using NATS.Client.Core; using NATS.Client.JetStream.Internal; using NATS.Client.JetStream.Models; -#if NETSTANDARD -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.JetStream; diff --git a/src/NATS.Client.JetStream/Usings.cs b/src/NATS.Client.JetStream/Usings.cs new file mode 100644 index 000000000..7e672a940 --- /dev/null +++ b/src/NATS.Client.JetStream/Usings.cs @@ -0,0 +1,3 @@ +#if NETSTANDARD +global using NATS.Client.Core.Internal.NetStandardExtensions; +#endif diff --git a/src/NATS.Client.KeyValueStore/NatsKVStore.cs b/src/NATS.Client.KeyValueStore/NatsKVStore.cs index 5e0a4de57..ebfbfc0f0 100644 --- a/src/NATS.Client.KeyValueStore/NatsKVStore.cs +++ b/src/NATS.Client.KeyValueStore/NatsKVStore.cs @@ -5,9 +5,6 @@ using NATS.Client.JetStream; using NATS.Client.JetStream.Models; using NATS.Client.KeyValueStore.Internal; -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.KeyValueStore; diff --git a/src/NATS.Client.KeyValueStore/Usings.cs b/src/NATS.Client.KeyValueStore/Usings.cs new file mode 100644 index 000000000..7e672a940 --- /dev/null +++ b/src/NATS.Client.KeyValueStore/Usings.cs @@ -0,0 +1,3 @@ +#if NETSTANDARD +global using NATS.Client.Core.Internal.NetStandardExtensions; +#endif diff --git a/src/NATS.Client.ObjectStore/NatsObjStore.cs b/src/NATS.Client.ObjectStore/NatsObjStore.cs index 01207c2aa..12fdae85f 100644 --- a/src/NATS.Client.ObjectStore/NatsObjStore.cs +++ b/src/NATS.Client.ObjectStore/NatsObjStore.cs @@ -10,9 +10,6 @@ using NATS.Client.JetStream.Models; using NATS.Client.ObjectStore.Internal; using NATS.Client.ObjectStore.Models; -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.ObjectStore; diff --git a/src/NATS.Client.ObjectStore/Usings.cs b/src/NATS.Client.ObjectStore/Usings.cs new file mode 100644 index 000000000..7e672a940 --- /dev/null +++ b/src/NATS.Client.ObjectStore/Usings.cs @@ -0,0 +1,3 @@ +#if NETSTANDARD +global using NATS.Client.Core.Internal.NetStandardExtensions; +#endif diff --git a/src/NATS.Client.Services/Internal/SvcListener.cs b/src/NATS.Client.Services/Internal/SvcListener.cs index 7501ed948..88fdd234d 100644 --- a/src/NATS.Client.Services/Internal/SvcListener.cs +++ b/src/NATS.Client.Services/Internal/SvcListener.cs @@ -1,8 +1,5 @@ using System.Threading.Channels; using NATS.Client.Core; -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Services.Internal; diff --git a/src/NATS.Client.Services/NatsSvcEndPoint.cs b/src/NATS.Client.Services/NatsSvcEndPoint.cs index 13ade239d..b13a722ff 100644 --- a/src/NATS.Client.Services/NatsSvcEndPoint.cs +++ b/src/NATS.Client.Services/NatsSvcEndPoint.cs @@ -5,9 +5,6 @@ using Microsoft.Extensions.Logging; using NATS.Client.Core; using NATS.Client.Core.Internal; -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Services; diff --git a/src/NATS.Client.Services/NatsSvcServer.cs b/src/NATS.Client.Services/NatsSvcServer.cs index a9493337e..e785db65a 100644 --- a/src/NATS.Client.Services/NatsSvcServer.cs +++ b/src/NATS.Client.Services/NatsSvcServer.cs @@ -6,9 +6,6 @@ using NATS.Client.Core.Internal; using NATS.Client.Services.Internal; using NATS.Client.Services.Models; -#if NETSTANDARD2_0 -using NATS.Client.Core.Internal.NetStandardExtensions; -#endif namespace NATS.Client.Services; diff --git a/src/NATS.Client.Services/Usings.cs b/src/NATS.Client.Services/Usings.cs new file mode 100644 index 000000000..7e672a940 --- /dev/null +++ b/src/NATS.Client.Services/Usings.cs @@ -0,0 +1,3 @@ +#if NETSTANDARD +global using NATS.Client.Core.Internal.NetStandardExtensions; +#endif From 2ea882826d782a7dfc529f180ee4108dc254de22 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 14 Jun 2024 14:33:34 +0100 Subject: [PATCH 38/47] use Nullable package for DoesNotReturn --- src/NATS.Client.Core/NatsConnection.RequestReply.cs | 2 -- src/NATS.Client.JetStream/NatsJSContext.cs | 4 ---- 2 files changed, 6 deletions(-) diff --git a/src/NATS.Client.Core/NatsConnection.RequestReply.cs b/src/NATS.Client.Core/NatsConnection.RequestReply.cs index 724341c3b..ed706a857 100644 --- a/src/NATS.Client.Core/NatsConnection.RequestReply.cs +++ b/src/NATS.Client.Core/NatsConnection.RequestReply.cs @@ -141,9 +141,7 @@ internal static string NewInbox(ReadOnlySpan prefix) return Throw(); -#if NETSTANDARD2_1 || NET6_0_OR_GREATER [DoesNotReturn] -#endif string Throw() { Debug.Fail("Must not happen"); diff --git a/src/NATS.Client.JetStream/NatsJSContext.cs b/src/NATS.Client.JetStream/NatsJSContext.cs index deff7f8ee..1c46b0e43 100644 --- a/src/NATS.Client.JetStream/NatsJSContext.cs +++ b/src/NATS.Client.JetStream/NatsJSContext.cs @@ -259,15 +259,11 @@ internal async ValueTask> JSRequestAsync throw new ArgumentException("Stream name cannot contain ' ', '.'", paramName); -#if NETSTANDARD2_1 || NET6_0_OR_GREATER [DoesNotReturn] -#endif private static void ThrowEmptyException(string? paramName) => throw new ArgumentException("The value cannot be an empty string.", paramName); } From 4b5a94c3a137c2fbb2d52b906ffcd049aa5fb354 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 14 Jun 2024 14:41:36 +0100 Subject: [PATCH 39/47] dotnet format --- src/NATS.Client.Core/Usings.cs | 2 +- src/NATS.Client.JetStream/Usings.cs | 2 +- src/NATS.Client.KeyValueStore/Usings.cs | 2 +- src/NATS.Client.ObjectStore/Usings.cs | 2 +- src/NATS.Client.Services/Usings.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/NATS.Client.Core/Usings.cs b/src/NATS.Client.Core/Usings.cs index 7e672a940..354619453 100644 --- a/src/NATS.Client.Core/Usings.cs +++ b/src/NATS.Client.Core/Usings.cs @@ -1,3 +1,3 @@ -#if NETSTANDARD +#if NETSTANDARD global using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.JetStream/Usings.cs b/src/NATS.Client.JetStream/Usings.cs index 7e672a940..354619453 100644 --- a/src/NATS.Client.JetStream/Usings.cs +++ b/src/NATS.Client.JetStream/Usings.cs @@ -1,3 +1,3 @@ -#if NETSTANDARD +#if NETSTANDARD global using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.KeyValueStore/Usings.cs b/src/NATS.Client.KeyValueStore/Usings.cs index 7e672a940..354619453 100644 --- a/src/NATS.Client.KeyValueStore/Usings.cs +++ b/src/NATS.Client.KeyValueStore/Usings.cs @@ -1,3 +1,3 @@ -#if NETSTANDARD +#if NETSTANDARD global using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.ObjectStore/Usings.cs b/src/NATS.Client.ObjectStore/Usings.cs index 7e672a940..354619453 100644 --- a/src/NATS.Client.ObjectStore/Usings.cs +++ b/src/NATS.Client.ObjectStore/Usings.cs @@ -1,3 +1,3 @@ -#if NETSTANDARD +#if NETSTANDARD global using NATS.Client.Core.Internal.NetStandardExtensions; #endif diff --git a/src/NATS.Client.Services/Usings.cs b/src/NATS.Client.Services/Usings.cs index 7e672a940..354619453 100644 --- a/src/NATS.Client.Services/Usings.cs +++ b/src/NATS.Client.Services/Usings.cs @@ -1,3 +1,3 @@ -#if NETSTANDARD +#if NETSTANDARD global using NATS.Client.Core.Internal.NetStandardExtensions; #endif From c56cc54f6f3997ef31a86cd84409b6819216f37a Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 14 Jun 2024 19:24:34 +0100 Subject: [PATCH 40/47] use NETSTANDARD in defs --- .github/workflows/test.yml | 2 +- .../Commands/CommandWriter.cs | 2 +- .../Commands/NatsPooledBufferWriter.cs | 2 +- src/NATS.Client.Core/Internal/InboxSub.cs | 6 +-- .../Internal/NatsReadProtocolProcessor.cs | 2 +- src/NATS.Client.Core/Internal/NuidWriter.cs | 6 +-- src/NATS.Client.Core/Internal/SocketReader.cs | 4 +- ...slClientAuthenticationOptionsExtensions.cs | 2 +- .../Internal/SslStreamConnection.cs | 17 +++++--- .../Internal/TcpConnection.cs | 18 ++++---- .../Internal/UserCredentials.cs | 8 ---- .../Internal/WebSocketConnection.cs | 6 +-- src/NATS.Client.Core/Internal/netstandard.cs | 7 ++++ src/NATS.Client.Core/NATS.Client.Core.csproj | 7 ++++ src/NATS.Client.Core/NaCl/Sha512.cs | 13 +++--- src/NATS.Client.Core/NatsBufferWriter.cs | 4 +- src/NATS.Client.Core/NatsConnection.Ping.cs | 2 +- src/NATS.Client.Core/NatsException.cs | 4 +- src/NATS.Client.Core/NatsHeaderParser.cs | 2 - src/NATS.Client.Core/NatsHeaders.cs | 19 +-------- src/NATS.Client.Core/NatsMemoryOwner.cs | 10 ++--- src/NATS.Client.Core/NatsMsg.cs | 4 -- src/NATS.Client.Core/NatsSubBase.cs | 6 +-- src/NATS.Client.Core/NatsTlsOpts.cs | 42 +++++++++---------- .../NATS.Client.JetStream.csproj | 4 ++ src/NATS.Client.JetStream/NatsJSExtensions.cs | 7 ++-- src/NATS.Client.JetStream/NatsJSMsg.cs | 4 -- src/NATS.Client.JetStream/NatsJSStream.cs | 7 ++-- .../Internal/NatsKVWatcher.cs | 22 +++++++--- .../NATS.Client.ObjectStore.csproj | 4 ++ src/NATS.Client.ObjectStore/NatsObjStore.cs | 7 +++- 31 files changed, 125 insertions(+), 125 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0603a761e..6adbc0225 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -145,7 +145,7 @@ jobs: dotnet tool install --global NUnit.ConsoleRunner.NetCore - name: Build - run: dotnet build -c Release + run: dotnet build -c Release - name: Memory Test (net6.0) run: dotMemoryUnit $env:userprofile\.dotnet\tools\nunit.exe --propagate-exit-code -- .\tests\NATS.Client.Core.MemoryTests\bin\Release\net6.0\NATS.Client.Core.MemoryTests.dll diff --git a/src/NATS.Client.Core/Commands/CommandWriter.cs b/src/NATS.Client.Core/Commands/CommandWriter.cs index 272c08606..a8686fc7d 100644 --- a/src/NATS.Client.Core/Commands/CommandWriter.cs +++ b/src/NATS.Client.Core/Commands/CommandWriter.cs @@ -777,7 +777,7 @@ private async ValueTask PongStateMachineAsync(bool lockHeld, CancellationToken c } } -#if NET6_0_OR_GREATER +#if !NETSTANDARD [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))] #endif private async ValueTask PublishStateMachineAsync(bool lockHeld, string subject, string? replyTo, NatsPooledBufferWriter? headersBuffer, NatsPooledBufferWriter payloadBuffer, CancellationToken cancellationToken) diff --git a/src/NATS.Client.Core/Commands/NatsPooledBufferWriter.cs b/src/NATS.Client.Core/Commands/NatsPooledBufferWriter.cs index 235127ed4..9f64c11df 100644 --- a/src/NATS.Client.Core/Commands/NatsPooledBufferWriter.cs +++ b/src/NATS.Client.Core/Commands/NatsPooledBufferWriter.cs @@ -193,7 +193,7 @@ private void ResizeBuffer(int sizeHint) { var minimumSize = (uint)_index + (uint)sizeHint; -#if NET6_0_OR_GREATER +#if !NETSTANDARD // The ArrayPool class has a maximum threshold of 1024 * 1024 for the maximum length of // pooled arrays, and once this is exceeded it will just allocate a new array every time // of exactly the requested size. In that case, we manually round up the requested size to diff --git a/src/NATS.Client.Core/Internal/InboxSub.cs b/src/NATS.Client.Core/Internal/InboxSub.cs index ef29ebfff..fc1eea5d3 100644 --- a/src/NATS.Client.Core/Internal/InboxSub.cs +++ b/src/NATS.Client.Core/Internal/InboxSub.cs @@ -166,10 +166,10 @@ public ValueTask RemoveAsync(NatsSubBase sub) { // try to remove this specific instance of the subTable // if an update is in process and sees an empty subTable, it will set a new instance -#if NET6_0_OR_GREATER - _bySubject.TryRemove(KeyValuePair.Create(sub.Subject, subTable)); -#else +#if NETSTANDARD _bySubject.TryRemove(sub.Subject, out _); +#else + _bySubject.TryRemove(KeyValuePair.Create(sub.Subject, subTable)); #endif } #endif diff --git a/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs b/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs index caf645885..c1448ded1 100644 --- a/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs +++ b/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs @@ -307,7 +307,7 @@ await _connection.PublishToClientHandlersAsync(subject, replyTo, sid, headerSlic } } -#if NET6_0_OR_GREATER +#if !NETSTANDARD [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] #endif private async ValueTask> DispatchCommandAsync(int code, ReadOnlySequence buffer) diff --git a/src/NATS.Client.Core/Internal/NuidWriter.cs b/src/NATS.Client.Core/Internal/NuidWriter.cs index 14b371a8e..b116bff79 100644 --- a/src/NATS.Client.Core/Internal/NuidWriter.cs +++ b/src/NATS.Client.Core/Internal/NuidWriter.cs @@ -31,11 +31,11 @@ private NuidWriter() Refresh(out _); } - private static ReadOnlySpan Digits => "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" #if NETSTANDARD2_0 - .AsSpan() + private static ReadOnlySpan Digits => "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".AsSpan(); +#else + private static ReadOnlySpan Digits => "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; #endif - ; public static bool TryWriteNuid(Span nuidBuffer) { diff --git a/src/NATS.Client.Core/Internal/SocketReader.cs b/src/NATS.Client.Core/Internal/SocketReader.cs index a5fc5ebe1..2aa1feb76 100644 --- a/src/NATS.Client.Core/Internal/SocketReader.cs +++ b/src/NATS.Client.Core/Internal/SocketReader.cs @@ -27,7 +27,7 @@ public SocketReader(ISocketConnection socketConnection, int minimumBufferSize, C _isTraceLogging = _logger.IsEnabled(LogLevel.Trace); } -#if NET6_0_OR_GREATER +#if !NETSTANDARD [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] #endif public async ValueTask> ReadAtLeastAsync(int minimumSize) @@ -75,7 +75,7 @@ public async ValueTask> ReadAtLeastAsync(int minimumSize) return _seqeunceBuilder.ToReadOnlySequence(); } -#if NET6_0_OR_GREATER +#if !NETSTANDARD [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] #endif public async ValueTask> ReadUntilReceiveNewLineAsync() diff --git a/src/NATS.Client.Core/Internal/SslClientAuthenticationOptionsExtensions.cs b/src/NATS.Client.Core/Internal/SslClientAuthenticationOptionsExtensions.cs index 0c527c725..6781cedf1 100644 --- a/src/NATS.Client.Core/Internal/SslClientAuthenticationOptionsExtensions.cs +++ b/src/NATS.Client.Core/Internal/SslClientAuthenticationOptionsExtensions.cs @@ -1,4 +1,4 @@ -#if NET6_0_OR_GREATER +#if !NETSTANDARD using System.Net.Security; using System.Runtime.InteropServices; diff --git a/src/NATS.Client.Core/Internal/SslStreamConnection.cs b/src/NATS.Client.Core/Internal/SslStreamConnection.cs index 06b0bfe98..814d8f290 100644 --- a/src/NATS.Client.Core/Internal/SslStreamConnection.cs +++ b/src/NATS.Client.Core/Internal/SslStreamConnection.cs @@ -96,17 +96,21 @@ public void SignalDisconnected(Exception exception) public async Task AuthenticateAsClientAsync(NatsUri uri, TimeSpan timeout) { -#if NET6_0_OR_GREATER +#if NETSTANDARD + try + { + await _sslStream.AuthenticateAsClientAsync(uri.Host).ConfigureAwait(false); + } + catch (AuthenticationException ex) + { + throw new NatsException($"TLS authentication failed", ex); + } +#else var options = await _tlsOpts.AuthenticateAsClientOptionsAsync(uri).ConfigureAwait(true); -#endif try { -#if NET6_0_OR_GREATER using var cts = new CancellationTokenSource(timeout); await _sslStream.AuthenticateAsClientAsync(options, cts.Token).ConfigureAwait(false); -#else - await _sslStream.AuthenticateAsClientAsync(uri.Host).ConfigureAwait(false); -#endif } catch (OperationCanceledException) { @@ -116,5 +120,6 @@ public async Task AuthenticateAsClientAsync(NatsUri uri, TimeSpan timeout) { throw new NatsException($"TLS authentication failed", ex); } +#endif } } diff --git a/src/NATS.Client.Core/Internal/TcpConnection.cs b/src/NATS.Client.Core/Internal/TcpConnection.cs index 7f039804c..98295cfc6 100644 --- a/src/NATS.Client.Core/Internal/TcpConnection.cs +++ b/src/NATS.Client.Core/Internal/TcpConnection.cs @@ -47,10 +47,10 @@ public TcpConnection(ILogger logger) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask ConnectAsync(string host, int port, CancellationToken cancellationToken) { -#if NET6_0_OR_GREATER - return _socket.ConnectAsync(host, port, cancellationToken); -#else +#if NETSTANDARD return new ValueTask(_socket.ConnectAsync(host, port).WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken)); +#else + return _socket.ConnectAsync(host, port, cancellationToken); #endif } @@ -62,10 +62,10 @@ public async ValueTask ConnectAsync(string host, int port, TimeSpan timeout) using var cts = new CancellationTokenSource(timeout); try { -#if NET6_0_OR_GREATER - await _socket.ConnectAsync(host, port, cts.Token).ConfigureAwait(false); -#else +#if NETSTANDARD await _socket.ConnectAsync(host, port).WaitAsync(timeout, cts.Token).ConfigureAwait(false); +#else + await _socket.ConnectAsync(host, port, cts.Token).ConfigureAwait(false); #endif } catch (Exception ex) @@ -106,11 +106,11 @@ public ValueTask ReceiveAsync(Memory buffer) public ValueTask AbortConnectionAsync(CancellationToken cancellationToken) { -#if NET6_0_OR_GREATER - return _socket.DisconnectAsync(false, cancellationToken); -#else +#if NETSTANDARD _socket.Disconnect(false); return default; +#else + return _socket.DisconnectAsync(false, cancellationToken); #endif } diff --git a/src/NATS.Client.Core/Internal/UserCredentials.cs b/src/NATS.Client.Core/Internal/UserCredentials.cs index b0a8336e7..898060841 100644 --- a/src/NATS.Client.Core/Internal/UserCredentials.cs +++ b/src/NATS.Client.Core/Internal/UserCredentials.cs @@ -14,20 +14,12 @@ public UserCredentials(NatsAuthOpts authOpts) if (!string.IsNullOrEmpty(authOpts.CredsFile)) { -#if NETSTANDARD2_0 - (Jwt, Seed) = LoadCredsFile(authOpts.CredsFile!); -#else (Jwt, Seed) = LoadCredsFile(authOpts.CredsFile); -#endif } if (!string.IsNullOrEmpty(authOpts.NKeyFile)) { -#if NETSTANDARD2_0 - (Seed, NKey) = LoadNKeyFile(authOpts.NKeyFile!); -#else (Seed, NKey) = LoadNKeyFile(authOpts.NKeyFile); -#endif } } diff --git a/src/NATS.Client.Core/Internal/WebSocketConnection.cs b/src/NATS.Client.Core/Internal/WebSocketConnection.cs index 12e6c43c2..c69669c93 100644 --- a/src/NATS.Client.Core/Internal/WebSocketConnection.cs +++ b/src/NATS.Client.Core/Internal/WebSocketConnection.cs @@ -63,11 +63,11 @@ public async ValueTask ConnectAsync(Uri uri, TimeSpan timeout) [MethodImpl(MethodImplOptions.AggressiveInlining)] public async ValueTask SendAsync(ReadOnlyMemory buffer) { -#if NET6_0_OR_GREATER - await _socket.SendAsync(buffer, WebSocketMessageType.Binary, WebSocketMessageFlags.EndOfMessage, CancellationToken.None).ConfigureAwait(false); -#else +#if NETSTANDARD MemoryMarshal.TryGetArray(buffer, out var segment); await _socket.SendAsync(segment, WebSocketMessageType.Binary, true, CancellationToken.None).ConfigureAwait(false); +#else + await _socket.SendAsync(buffer, WebSocketMessageType.Binary, WebSocketMessageFlags.EndOfMessage, CancellationToken.None).ConfigureAwait(false); #endif return buffer.Length; } diff --git a/src/NATS.Client.Core/Internal/netstandard.cs b/src/NATS.Client.Core/Internal/netstandard.cs index 40f4b00a2..99f61bbe4 100644 --- a/src/NATS.Client.Core/Internal/netstandard.cs +++ b/src/NATS.Client.Core/Internal/netstandard.cs @@ -34,6 +34,13 @@ internal sealed class SkipLocalsInitAttribute : Attribute } } +namespace System.Diagnostics +{ + internal sealed class StackTraceHiddenAttribute : Attribute + { + } +} + namespace NATS.Client.Core.Internal.NetStandardExtensions { using System.Buffers; diff --git a/src/NATS.Client.Core/NATS.Client.Core.csproj b/src/NATS.Client.Core/NATS.Client.Core.csproj index b2c233354..42547916b 100644 --- a/src/NATS.Client.Core/NATS.Client.Core.csproj +++ b/src/NATS.Client.Core/NATS.Client.Core.csproj @@ -16,6 +16,13 @@ true + + $(NoWarn);CS8774 + + + $(NoWarn);CS8604 + + diff --git a/src/NATS.Client.Core/NaCl/Sha512.cs b/src/NATS.Client.Core/NaCl/Sha512.cs index c517550fd..b7dbd79be 100644 --- a/src/NATS.Client.Core/NaCl/Sha512.cs +++ b/src/NATS.Client.Core/NaCl/Sha512.cs @@ -66,17 +66,14 @@ public byte[] Finalize() /// Hash bytes public static byte[] Hash(byte[] data, int index, int length) { -#if NET6_0_OR_GREATER +#if NETSTANDARD + ArgumentNullExceptionEx.ThrowIfNull(data, nameof(data)); + using var sha512 = SHA512.Create(); + return sha512.ComputeHash(data, index, length); +#else ArgumentNullException.ThrowIfNull(data); ReadOnlySpan dataSpan = data; return SHA512.HashData(dataSpan.Slice(index, length)); -#else - if (data == null) - { - throw new ArgumentNullException(nameof(data)); - } - using var sha512 = SHA512.Create(); - return sha512.ComputeHash(data, index, length); #endif } diff --git a/src/NATS.Client.Core/NatsBufferWriter.cs b/src/NATS.Client.Core/NatsBufferWriter.cs index 8d474c92e..0f0002a12 100644 --- a/src/NATS.Client.Core/NatsBufferWriter.cs +++ b/src/NATS.Client.Core/NatsBufferWriter.cs @@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -#if NET6_0_OR_GREATER +#if !NETSTANDARD using BitOperations = System.Numerics.BitOperations; #endif @@ -363,7 +363,7 @@ private void ResizeBuffer(int sizeHint) { var minimumSize = (uint)_index + (uint)sizeHint; -#if NET6_0_OR_GREATER +#if !NETSTANDARD // The ArrayPool class has a maximum threshold of 1024 * 1024 for the maximum length of // pooled arrays, and once this is exceeded it will just allocate a new array every time // of exactly the requested size. In that case, we manually round up the requested size to diff --git a/src/NATS.Client.Core/NatsConnection.Ping.cs b/src/NATS.Client.Core/NatsConnection.Ping.cs index 277995fb5..86f31b68d 100644 --- a/src/NATS.Client.Core/NatsConnection.Ping.cs +++ b/src/NATS.Client.Core/NatsConnection.Ping.cs @@ -6,7 +6,7 @@ namespace NATS.Client.Core; public partial class NatsConnection { /// -#if NET6_0_OR_GREATER +#if !NETSTANDARD [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] #endif public async ValueTask PingAsync(CancellationToken cancellationToken = default) diff --git a/src/NATS.Client.Core/NatsException.cs b/src/NATS.Client.Core/NatsException.cs index 8b8b2b872..4f7d2be78 100644 --- a/src/NATS.Client.Core/NatsException.cs +++ b/src/NATS.Client.Core/NatsException.cs @@ -1,5 +1,3 @@ -using System.Globalization; - namespace NATS.Client.Core; public class NatsException : Exception @@ -36,7 +34,7 @@ public sealed class NatsServerException : NatsException public NatsServerException(string error) : base($"Server error: {error}") { - Error = error.ToLower(CultureInfo.InvariantCulture); + Error = error.ToLower(); IsAuthError = Error.Contains("authorization violation") || Error.Contains("user authentication expired") || Error.Contains("user authentication revoked") diff --git a/src/NATS.Client.Core/NatsHeaderParser.cs b/src/NATS.Client.Core/NatsHeaderParser.cs index 19b2fd96e..e26077aa5 100644 --- a/src/NATS.Client.Core/NatsHeaderParser.cs +++ b/src/NATS.Client.Core/NatsHeaderParser.cs @@ -237,9 +237,7 @@ private bool TryTakeSingleHeader(ReadOnlySpan headerLine, NatsHeaders head return true; } -#if NET6_0_OR_GREATER [StackTraceHidden] -#endif private void RejectRequestHeader(ReadOnlySpan headerLine) => throw new NatsException( $"Protocol error: invalid request header line '{headerLine.Dump()}'"); diff --git a/src/NATS.Client.Core/NatsHeaders.cs b/src/NATS.Client.Core/NatsHeaders.cs index e451848db..f7bec3f67 100644 --- a/src/NATS.Client.Core/NatsHeaders.cs +++ b/src/NATS.Client.Core/NatsHeaders.cs @@ -146,11 +146,7 @@ public StringValues this[string key] else { EnsureStore(1); -#if NET6_0_OR_GREATER Store[key] = value; -#else - Store![key] = value; -#endif } } } @@ -221,11 +217,7 @@ public void Add(KeyValuePair item) } ThrowIfReadOnly(); EnsureStore(1); -#if NET6_0_OR_GREATER Store.Add(item.Key, item.Value); -#else - Store!.Add(item.Key, item.Value); -#endif } /// @@ -241,11 +233,7 @@ public void Add(string key, StringValues value) } ThrowIfReadOnly(); EnsureStore(1); -#if NET6_0_OR_GREATER Store.Add(key, value); -#else - Store!.Add(key, value); -#endif } /// @@ -363,12 +351,7 @@ public bool TryGetValue(string key, out StringValues value) /// The header name. /// The value. /// true if the contains the key; otherwise, false. - public bool TryGetLastValue( - string key, -#if NET6_0_OR_GREATER - [NotNullWhen(returnValue: true)] -#endif - out string? value) + public bool TryGetLastValue(string key, [NotNullWhen(returnValue: true)] out string? value) { if (Store != null && Store.TryGetValue(key, out var values)) { diff --git a/src/NATS.Client.Core/NatsMemoryOwner.cs b/src/NATS.Client.Core/NatsMemoryOwner.cs index dc53e178c..a17df3cf5 100644 --- a/src/NATS.Client.Core/NatsMemoryOwner.cs +++ b/src/NATS.Client.Core/NatsMemoryOwner.cs @@ -177,7 +177,9 @@ public Span Span [MethodImpl(MethodImplOptions.AggressiveInlining)] get { -#if NET6_0_OR_GREATER +#if NETSTANDARD + return _array.AsSpan().Slice(_start, _length); +#else var array = _array; if (array is null) @@ -197,13 +199,11 @@ public Span Span // default Span constructor and paying the cost of the extra conditional branches, // especially if T is a value type, in which case the covariance check is JIT removed. return MemoryMarshal.CreateSpan(ref r0, _length); -#else - return _array.AsSpan().Slice(_start, _length); #endif } } -#if NET6_0_OR_GREATER +#if !NETSTANDARD /// /// Returns a reference to the first element within the current instance, with no bounds check. /// @@ -352,7 +352,7 @@ private static void ThrowInvalidLengthException() } } -#if NET6_0_OR_GREATER +#if !NETSTANDARD internal static class NatsMemoryOwnerArrayExtensions { diff --git a/src/NATS.Client.Core/NatsMsg.cs b/src/NATS.Client.Core/NatsMsg.cs index 5df092f28..3a1f0d929 100644 --- a/src/NATS.Client.Core/NatsMsg.cs +++ b/src/NATS.Client.Core/NatsMsg.cs @@ -2,10 +2,6 @@ using System.Diagnostics.CodeAnalysis; using NATS.Client.Core.Internal; -#if NETSTANDARD -#pragma warning disable CS8774 // Member must have a non-null value when exiting. -#endif - namespace NATS.Client.Core; /// diff --git a/src/NATS.Client.Core/NatsSubBase.cs b/src/NATS.Client.Core/NatsSubBase.cs index e7f27a751..d0bdf318a 100644 --- a/src/NATS.Client.Core/NatsSubBase.cs +++ b/src/NATS.Client.Core/NatsSubBase.cs @@ -75,8 +75,8 @@ internal NatsSubBase( // might be a problem. This should reduce the impact of that problem. cancellationToken.ThrowIfCancellationRequested(); -#if NET6_0_OR_GREATER - _tokenRegistration = cancellationToken.UnsafeRegister( +#if NETSTANDARD + _tokenRegistration = cancellationToken.Register( state => { var self = (NatsSubBase)state!; @@ -84,7 +84,7 @@ internal NatsSubBase( }, this); #else - _tokenRegistration = cancellationToken.Register( + _tokenRegistration = cancellationToken.UnsafeRegister( state => { var self = (NatsSubBase)state!; diff --git a/src/NATS.Client.Core/NatsTlsOpts.cs b/src/NATS.Client.Core/NatsTlsOpts.cs index ce6d17651..bbeb6f4fc 100644 --- a/src/NATS.Client.Core/NatsTlsOpts.cs +++ b/src/NATS.Client.Core/NatsTlsOpts.cs @@ -61,26 +61,26 @@ public sealed record NatsTlsOpts /// public string? KeyFile { get; init; } -#if NETSTANDARD2_1 || NET6_0_OR_GREATER +#if !NETSTANDARD2_0 /// /// Callback to configure /// public Func? ConfigureClientAuthentication { get; init; } #endif -#if NETSTANDARD2_1 || NET6_0_OR_GREATER +#if NETSTANDARD2_0 /// /// Callback that loads Client Certificate /// - /// - /// Obsolete, use instead - /// - [Obsolete("use ConfigureClientAuthentication")] public Func>? LoadClientCert { get; init; } #else /// /// Callback that loads Client Certificate /// + /// + /// Obsolete, use instead + /// + [Obsolete("use ConfigureClientAuthentication")] public Func>? LoadClientCert { get; init; } #endif @@ -89,49 +89,49 @@ public sealed record NatsTlsOpts /// public string? CaFile { get; init; } -#if NETSTANDARD2_1 || NET6_0_OR_GREATER +#if NETSTANDARD2_0 /// /// Callback that loads CA Certificates /// - /// - /// Obsolete, use instead - /// - [Obsolete("use ConfigureClientAuthentication")] public Func>? LoadCaCerts { get; init; } #else /// /// Callback that loads CA Certificates /// + /// + /// Obsolete, use instead + /// + [Obsolete("use ConfigureClientAuthentication")] public Func>? LoadCaCerts { get; init; } #endif /// When true, skip remote certificate verification and accept any server certificate public bool InsecureSkipVerify { get; init; } -#if NETSTANDARD2_1 || NET6_0_OR_GREATER +#if NETSTANDARD2_0 /// Certificate revocation mode for certificate validation. /// One of the values in . The default is . - /// - /// Obsolete, use instead - /// - [Obsolete("use ConfigureClientAuthentication")] public X509RevocationMode CertificateRevocationCheckMode { get; init; } #else /// Certificate revocation mode for certificate validation. /// One of the values in . The default is . + /// + /// Obsolete, use instead + /// + [Obsolete("use ConfigureClientAuthentication")] public X509RevocationMode CertificateRevocationCheckMode { get; init; } #endif /// TLS mode to use during connection public TlsMode Mode { get; init; } -#if NETSTANDARD2_1 || NET6_0_OR_GREATER - internal bool HasTlsCerts => CertFile != default || KeyFile != default || CaFile != default || ConfigureClientAuthentication != default; -#else +#if NETSTANDARD2_0 internal bool HasTlsCerts => CertFile != default || KeyFile != default || CaFile != default; +#else + internal bool HasTlsCerts => CertFile != default || KeyFile != default || CaFile != default || ConfigureClientAuthentication != default; #endif -#if NET6_0_OR_GREATER +#if !NETSTANDARD /// /// Helper method to load a Client Certificate from a pem-encoded string /// @@ -172,7 +172,7 @@ internal bool TryTls(NatsUri uri) return effectiveMode is TlsMode.Require or TlsMode.Prefer; } -#if NET6_0_OR_GREATER +#if !NETSTANDARD internal async ValueTask AuthenticateAsClientOptionsAsync(NatsUri uri) { #pragma warning disable CS0618 // Type or member is obsolete diff --git a/src/NATS.Client.JetStream/NATS.Client.JetStream.csproj b/src/NATS.Client.JetStream/NATS.Client.JetStream.csproj index 345d0c430..d9383e5f0 100644 --- a/src/NATS.Client.JetStream/NATS.Client.JetStream.csproj +++ b/src/NATS.Client.JetStream/NATS.Client.JetStream.csproj @@ -16,6 +16,10 @@ true + + $(NoWarn);CS8774 + + diff --git a/src/NATS.Client.JetStream/NatsJSExtensions.cs b/src/NATS.Client.JetStream/NatsJSExtensions.cs index d9a4aa4ad..759c999f9 100644 --- a/src/NATS.Client.JetStream/NatsJSExtensions.cs +++ b/src/NATS.Client.JetStream/NatsJSExtensions.cs @@ -31,11 +31,10 @@ public static void EnsureSuccess(this PubAckResponse ack) /// is NULL. public static bool IsSuccess(this PubAckResponse ack) { -#if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(ack); +#if NETSTANDARD + ArgumentNullExceptionEx.ThrowIfNull(ack, nameof(ack)); #else - if (ack == null) - throw new ArgumentNullException(nameof(ack)); + ArgumentNullException.ThrowIfNull(ack); #endif return ack.Error == null && !ack.Duplicate; } diff --git a/src/NATS.Client.JetStream/NatsJSMsg.cs b/src/NATS.Client.JetStream/NatsJSMsg.cs index b2aa1d4cb..e5a0bd151 100644 --- a/src/NATS.Client.JetStream/NatsJSMsg.cs +++ b/src/NATS.Client.JetStream/NatsJSMsg.cs @@ -4,10 +4,6 @@ using NATS.Client.Core; using NATS.Client.JetStream.Internal; -#if NETSTANDARD -#pragma warning disable CS8774 // Member must have a non-null value when exiting. -#endif - namespace NATS.Client.JetStream; /// diff --git a/src/NATS.Client.JetStream/NatsJSStream.cs b/src/NATS.Client.JetStream/NatsJSStream.cs index 1d36ada20..54f8b63e0 100644 --- a/src/NATS.Client.JetStream/NatsJSStream.cs +++ b/src/NATS.Client.JetStream/NatsJSStream.cs @@ -18,11 +18,10 @@ public class NatsJSStream : INatsJSStream #endif internal NatsJSStream(NatsJSContext context, StreamInfo info) { -#if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(info.Config.Name, nameof(info.Config.Name)); +#if NETSTANDARD + ArgumentNullExceptionEx.ThrowIfNull(info.Config.Name, nameof(info.Config.Name)); #else - if (info.Config.Name == null) - throw new ArgumentNullException(nameof(info.Config.Name)); + ArgumentNullException.ThrowIfNull(info.Config.Name, nameof(info.Config.Name)); #endif _context = context; Info = info; diff --git a/src/NATS.Client.KeyValueStore/Internal/NatsKVWatcher.cs b/src/NATS.Client.KeyValueStore/Internal/NatsKVWatcher.cs index db79cfd19..8d6a9b1f8 100644 --- a/src/NATS.Client.KeyValueStore/Internal/NatsKVWatcher.cs +++ b/src/NATS.Client.KeyValueStore/Internal/NatsKVWatcher.cs @@ -46,8 +46,8 @@ internal class NatsKVWatcher : IAsyncDisposable private readonly string _stream; private readonly Task _commandTask; - private long _sequenceStream; - private long _sequenceConsumer; + private ulong _sequenceStream; + private ulong _sequenceConsumer; private string _consumer; private volatile NatsKVWatchSub? _sub; private INatsJSConsumer? _initialConsumer; @@ -238,9 +238,13 @@ private async Task CommandLoop() continue; } +#if NETSTANDARD + var sequence = InterlockedEx.Increment(ref _sequenceConsumer); +#else var sequence = Interlocked.Increment(ref _sequenceConsumer); +#endif - if (sequence != (long)metadata.Sequence.Consumer) + if (sequence != metadata.Sequence.Consumer) { CreateSub("sequence-mismatch"); _logger.LogWarning(NatsKVLogEvents.RecreateConsumer, "Missed messages, recreating consumer"); @@ -267,7 +271,11 @@ private async Task CommandLoop() // Increment the sequence before writing to the channel in case the channel is full // and the writer is waiting for the reader to read the message. This way the sequence // will be correctly incremented in case the timeout kicks in and recreated the consumer. - Interlocked.Exchange(ref _sequenceStream, (long)metadata.Sequence.Stream); +#if NETSTANDARD + InterlockedEx.Exchange(ref _sequenceStream, metadata.Sequence.Stream); +#else + Interlocked.Exchange(ref _sequenceStream, metadata.Sequence.Stream); +#endif await _entryChannel.Writer.WriteAsync(entry, _cancellationToken); } @@ -356,7 +364,11 @@ private async ValueTask CreatePushConsumer(string origin) _logger.LogDebug(NatsKVLogEvents.NewDeliverySubject, "New delivery subject {Subject}", _sub.Subject); } +#if NETSTANDARD + InterlockedEx.Exchange(ref _sequenceConsumer, 0); +#else Interlocked.Exchange(ref _sequenceConsumer, 0); +#endif var sequence = Volatile.Read(ref _sequenceStream); @@ -408,7 +420,7 @@ private async ValueTask CreatePushConsumer(string origin) if (sequence > 0) { config.DeliverPolicy = ConsumerConfigDeliverPolicy.ByStartSequence; - config.OptStartSeq = (ulong)sequence + 1; + config.OptStartSeq = sequence + 1; } else if (_opts.ResumeAtRevision > 0) { diff --git a/src/NATS.Client.ObjectStore/NATS.Client.ObjectStore.csproj b/src/NATS.Client.ObjectStore/NATS.Client.ObjectStore.csproj index cc9c5134e..10ace349d 100644 --- a/src/NATS.Client.ObjectStore/NATS.Client.ObjectStore.csproj +++ b/src/NATS.Client.ObjectStore/NATS.Client.ObjectStore.csproj @@ -16,6 +16,10 @@ true + + $(NoWarn);CS8604 + + diff --git a/src/NATS.Client.ObjectStore/NatsObjStore.cs b/src/NATS.Client.ObjectStore/NatsObjStore.cs index 12fdae85f..464191f02 100644 --- a/src/NATS.Client.ObjectStore/NatsObjStore.cs +++ b/src/NATS.Client.ObjectStore/NatsObjStore.cs @@ -1,6 +1,5 @@ using System.Buffers; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text.Json; using NATS.Client.Core; @@ -11,6 +10,10 @@ using NATS.Client.ObjectStore.Internal; using NATS.Client.ObjectStore.Models; +#if NETSTANDARD2_0 +using System.Runtime.InteropServices; +#endif + namespace NATS.Client.ObjectStore; /// @@ -655,7 +658,7 @@ public async ValueTask DeleteAsync(string key, CancellationToken cancellationTok await PublishMeta(meta, cancellationToken); - var response = await _stream.PurgeAsync(new StreamPurgeRequest { Filter = GetChunkSubject(meta.Nuid!) }, cancellationToken); + var response = await _stream.PurgeAsync(new StreamPurgeRequest { Filter = GetChunkSubject(meta.Nuid) }, cancellationToken); if (!response.Success) { throw new NatsObjException("Can't purge object chunks"); From 7bf683ba25e7998eca6145a3b58a69c71b95b624 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Mon, 17 Jun 2024 11:32:57 +0100 Subject: [PATCH 41/47] IsNotCompletedSuccessfully extension --- .../Commands/CommandWriter.cs | 73 ------------------- src/NATS.Client.Core/Internal/netstandard.cs | 26 +++++-- 2 files changed, 18 insertions(+), 81 deletions(-) diff --git a/src/NATS.Client.Core/Commands/CommandWriter.cs b/src/NATS.Client.Core/Commands/CommandWriter.cs index a8686fc7d..dcea8d688 100644 --- a/src/NATS.Client.Core/Commands/CommandWriter.cs +++ b/src/NATS.Client.Core/Commands/CommandWriter.cs @@ -164,11 +164,7 @@ public ValueTask ConnectAsync(ClientOpts connectOpts, CancellationToken cancella return ConnectStateMachineAsync(false, connectOpts, cancellationToken); } -#if NETSTANDARD2_0 if (_flushTask.IsNotCompletedSuccessfully()) -#else - if (_flushTask is { IsCompletedSuccessfully: false }) -#endif { return ConnectStateMachineAsync(true, connectOpts, cancellationToken); } @@ -202,11 +198,7 @@ public ValueTask PingAsync(PingCommand pingCommand, CancellationToken cancellati return PingStateMachineAsync(false, pingCommand, cancellationToken); } -#if NETSTANDARD2_0 if (_flushTask.IsNotCompletedSuccessfully()) -#else - if (_flushTask is { IsCompletedSuccessfully: false }) -#endif { return PingStateMachineAsync(true, pingCommand, cancellationToken); } @@ -241,11 +233,7 @@ public ValueTask PongAsync(CancellationToken cancellationToken = default) return PongStateMachineAsync(false, cancellationToken); } -#if NETSTANDARD2_0 if (_flushTask.IsNotCompletedSuccessfully()) -#else - if (_flushTask is { IsCompletedSuccessfully: false }) -#endif { return PongStateMachineAsync(true, cancellationToken); } @@ -318,11 +306,7 @@ public ValueTask PublishAsync(string subject, T? value, NatsHeaders? headers, return PublishStateMachineAsync(false, subject, replyTo, headersBuffer, payloadBuffer, cancellationToken); } -#if NETSTANDARD2_0 if (_flushTask.IsNotCompletedSuccessfully()) -#else - if (_flushTask is { IsCompletedSuccessfully: false }) -#endif { return PublishStateMachineAsync(true, subject, replyTo, headersBuffer, payloadBuffer, cancellationToken); } @@ -365,11 +349,7 @@ public ValueTask SubscribeAsync(int sid, string subject, string? queueGroup, int return SubscribeStateMachineAsync(false, sid, subject, queueGroup, maxMsgs, cancellationToken); } -#if NETSTANDARD2_0 if (_flushTask.IsNotCompletedSuccessfully()) -#else - if (_flushTask is { IsCompletedSuccessfully: false }) -#endif { return SubscribeStateMachineAsync(true, sid, subject, queueGroup, maxMsgs, cancellationToken); } @@ -403,11 +383,7 @@ public ValueTask UnsubscribeAsync(int sid, int? maxMsgs, CancellationToken cance return UnsubscribeStateMachineAsync(false, sid, maxMsgs, cancellationToken); } -#if NETSTANDARD2_0 if (_flushTask.IsNotCompletedSuccessfully()) -#else - if (_flushTask is { IsCompletedSuccessfully: false }) -#endif { return UnsubscribeStateMachineAsync(true, sid, maxMsgs, cancellationToken); } @@ -437,17 +413,10 @@ internal async Task TestStallFlushAsync(TimeSpan timeSpan, CancellationToken can try { -#if NETSTANDARD2_0 if (_flushTask.IsNotCompletedSuccessfully()) { await _flushTask!.ConfigureAwait(false); } -#else - if (_flushTask is { IsCompletedSuccessfully: false }) - { - await _flushTask.ConfigureAwait(false); - } -#endif _flushTask = Task.Delay(timeSpan, cancellationToken); } @@ -661,17 +630,10 @@ private async ValueTask ConnectStateMachineAsync(bool lockHeld, ClientOpts conne throw new ObjectDisposedException(nameof(CommandWriter)); } -#if NETSTANDARD2_0 if (_flushTask.IsNotCompletedSuccessfully()) { await _flushTask!.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); } -#else - if (_flushTask is { IsCompletedSuccessfully: false }) - { - await _flushTask.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); - } -#endif _protocolWriter.WriteConnect(_pipeWriter, connectOpts); EnqueueCommand(); @@ -705,17 +667,10 @@ private async ValueTask PingStateMachineAsync(bool lockHeld, PingCommand pingCom throw new ObjectDisposedException(nameof(CommandWriter)); } -#if NETSTANDARD2_0 if (_flushTask.IsNotCompletedSuccessfully()) { await _flushTask!.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); } -#else - if (_flushTask is { IsCompletedSuccessfully: false }) - { - await _flushTask.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); - } -#endif _protocolWriter.WritePing(_pipeWriter); _enqueuePing(pingCommand); @@ -750,17 +705,10 @@ private async ValueTask PongStateMachineAsync(bool lockHeld, CancellationToken c throw new ObjectDisposedException(nameof(CommandWriter)); } -#if NETSTANDARD2_0 if (_flushTask.IsNotCompletedSuccessfully()) { await _flushTask!.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); } -#else - if (_flushTask is { IsCompletedSuccessfully: false }) - { - await _flushTask.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); - } -#endif _protocolWriter.WritePong(_pipeWriter); EnqueueCommand(); @@ -799,17 +747,10 @@ private async ValueTask PublishStateMachineAsync(bool lockHeld, string subject, throw new ObjectDisposedException(nameof(CommandWriter)); } -#if NETSTANDARD2_0 if (_flushTask.IsNotCompletedSuccessfully()) { await _flushTask!.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); } -#else - if (_flushTask is { IsCompletedSuccessfully: false }) - { - await _flushTask.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); - } -#endif _protocolWriter.WritePublish(_pipeWriter, subject, replyTo, headersBuffer?.WrittenMemory, payloadBuffer.WrittenMemory); EnqueueCommand(); @@ -855,17 +796,10 @@ private async ValueTask SubscribeStateMachineAsync(bool lockHeld, int sid, strin throw new ObjectDisposedException(nameof(CommandWriter)); } -#if NETSTANDARD2_0 if (_flushTask.IsNotCompletedSuccessfully()) { await _flushTask!.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); } -#else - if (_flushTask is { IsCompletedSuccessfully: false }) - { - await _flushTask.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); - } -#endif _protocolWriter.WriteSubscribe(_pipeWriter, sid, subject, queueGroup, maxMsgs); EnqueueCommand(); @@ -899,17 +833,10 @@ private async ValueTask UnsubscribeStateMachineAsync(bool lockHeld, int sid, int throw new ObjectDisposedException(nameof(CommandWriter)); } -#if NETSTANDARD2_0 if (_flushTask.IsNotCompletedSuccessfully()) { await _flushTask!.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); } -#else - if (_flushTask is { IsCompletedSuccessfully: false }) - { - await _flushTask.WaitAsync(_defaultCommandTimeout, cancellationToken).ConfigureAwait(false); - } -#endif _protocolWriter.WriteUnsubscribe(_pipeWriter, sid, maxMsgs); EnqueueCommand(); diff --git a/src/NATS.Client.Core/Internal/netstandard.cs b/src/NATS.Client.Core/Internal/netstandard.cs index 99f61bbe4..2b74c59a6 100644 --- a/src/NATS.Client.Core/Internal/netstandard.cs +++ b/src/NATS.Client.Core/Internal/netstandard.cs @@ -313,14 +313,6 @@ internal static string GetString(this Encoding encoding, in ReadOnlySpan b } } - internal static class TaskExtensions - { - internal static bool IsNotCompletedSuccessfully(this Task? task) - { - return task != null && (!task.IsCompleted || task.IsCanceled || task.IsFaulted); - } - } - internal static class KeyValuePairExtensions { internal static void Deconstruct(this KeyValuePair kv, out TKey key, out TValue value) @@ -346,3 +338,21 @@ public static async IAsyncEnumerable ReadAllAsync(this ChannelReader re } #endif + +namespace NATS.Client.Core.Internal +{ + using System.Runtime.CompilerServices; + + internal static class TaskExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsNotCompletedSuccessfully(this Task? task) + { + #if NETSTANDARD2_0 + return task != null && task.Status != TaskStatus.RanToCompletion; + #else + return task is { IsCompletedSuccessfully: false }; + #endif + } + } +} From 74b3b9cef850da181558e987fb3c9b24ab6801d1 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Mon, 17 Jun 2024 12:09:16 +0100 Subject: [PATCH 42/47] first span extension --- src/NATS.Client.Core/INatsSerialize.cs | 13 +++-------- .../Internal/BufferExtensions.cs | 6 +---- .../Internal/NatsReadProtocolProcessor.cs | 23 ++++--------------- src/NATS.Client.Core/Internal/netstandard.cs | 14 +++++++++++ 4 files changed, 22 insertions(+), 34 deletions(-) diff --git a/src/NATS.Client.Core/INatsSerialize.cs b/src/NATS.Client.Core/INatsSerialize.cs index d4a5ba84e..ddb055532 100644 --- a/src/NATS.Client.Core/INatsSerialize.cs +++ b/src/NATS.Client.Core/INatsSerialize.cs @@ -4,6 +4,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; +using NATS.Client.Core.Internal; namespace NATS.Client.Core; @@ -375,11 +376,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) return (T)(object)Encoding.UTF8.GetString(buffer); } -#if NETSTANDARD2_0 - var span = buffer.IsSingleSegment ? buffer.First.Span : buffer.ToArray(); -#else - var span = buffer.IsSingleSegment ? buffer.FirstSpan : buffer.ToArray(); -#endif + var span = buffer.IsSingleSegment ? buffer.GetFirstSpan() : buffer.ToArray(); if (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateTime?)) { @@ -625,11 +622,7 @@ public void Serialize(IBufferWriter bufferWriter, T value) { if (readOnlySequence.IsSingleSegment) { -#if NETSTANDARD2_0 - bufferWriter.Write(readOnlySequence.First.Span); -#else - bufferWriter.Write(readOnlySequence.FirstSpan); -#endif + bufferWriter.Write(readOnlySequence.GetFirstSpan()); } else { diff --git a/src/NATS.Client.Core/Internal/BufferExtensions.cs b/src/NATS.Client.Core/Internal/BufferExtensions.cs index 8dd7295ca..cc941b99c 100644 --- a/src/NATS.Client.Core/Internal/BufferExtensions.cs +++ b/src/NATS.Client.Core/Internal/BufferExtensions.cs @@ -29,11 +29,7 @@ public static ReadOnlySpan ToSpan(this ReadOnlySequence buffer) { if (buffer.IsSingleSegment) { -#if NETSTANDARD2_0 - return buffer.First.Span; -#else - return buffer.FirstSpan; -#endif + return buffer.GetFirstSpan(); } return buffer.ToArray(); diff --git a/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs b/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs index c1448ded1..096ad83ee 100644 --- a/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs +++ b/src/NATS.Client.Core/Internal/NatsReadProtocolProcessor.cs @@ -86,17 +86,10 @@ private static int GetInt32(ReadOnlySpan span) private static int GetInt32(in ReadOnlySequence sequence) { -#if NETSTANDARD2_0 - if (sequence.IsSingleSegment || sequence.First.Span.Length <= 10) + if (sequence.IsSingleSegment || sequence.GetFirstSpan().Length <= 10) { - return GetInt32(sequence.First.Span); + return GetInt32(sequence.GetFirstSpan()); } -#else - if (sequence.IsSingleSegment || sequence.FirstSpan.Length <= 10) - { - return GetInt32(sequence.FirstSpan); - } -#endif Span buf = stackalloc byte[Math.Min((int)sequence.Length, 10)]; sequence.Slice(buf.Length).CopyTo(buf); @@ -467,11 +460,7 @@ private async ValueTask> DispatchCommandAsync(int code, R { if (msgHeader.IsSingleSegment) { -#if NETSTANDARD2_0 - return ParseMessageHeader(msgHeader.First.Span); -#else - return ParseMessageHeader(msgHeader.FirstSpan); -#endif + return ParseMessageHeader(msgHeader.GetFirstSpan()); } // header parsing use Slice frequently so ReadOnlySequence is high cost, should use Span. @@ -532,11 +521,7 @@ private async ValueTask> DispatchCommandAsync(int code, R { if (msgHeader.IsSingleSegment) { -#if NETSTANDARD2_0 - return ParseHMessageHeader(msgHeader.First.Span); -#else - return ParseHMessageHeader(msgHeader.FirstSpan); -#endif + return ParseHMessageHeader(msgHeader.GetFirstSpan()); } // header parsing use Slice frequently so ReadOnlySequence is high cost, should use Span. diff --git a/src/NATS.Client.Core/Internal/netstandard.cs b/src/NATS.Client.Core/Internal/netstandard.cs index 2b74c59a6..12880068e 100644 --- a/src/NATS.Client.Core/Internal/netstandard.cs +++ b/src/NATS.Client.Core/Internal/netstandard.cs @@ -341,6 +341,7 @@ public static async IAsyncEnumerable ReadAllAsync(this ChannelReader re namespace NATS.Client.Core.Internal { + using System.Buffers; using System.Runtime.CompilerServices; internal static class TaskExtensions @@ -355,4 +356,17 @@ internal static bool IsNotCompletedSuccessfully(this Task? task) #endif } } + + internal static class FistSpanExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ReadOnlySpan GetFirstSpan(this ReadOnlySequence sequence) + { + #if NETSTANDARD2_0 + return sequence.First.Span; + #else + return sequence.FirstSpan; + #endif + } + } } From 9695a1da35ba6c326c57e9ee21fce84293c1bfbd Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Mon, 17 Jun 2024 12:23:20 +0100 Subject: [PATCH 43/47] use span-char toString --- src/NATS.Client.Core/Internal/NuidWriter.cs | 6 +----- src/NATS.Client.Core/NatsConnection.RequestReply.cs | 6 +----- .../Internal/NatsJSOrderedPushConsumer.cs | 6 +----- src/NATS.Client.KeyValueStore/Internal/NatsKVWatcher.cs | 6 +----- src/NATS.Client.ObjectStore/NatsObjStore.cs | 6 +----- 5 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/NATS.Client.Core/Internal/NuidWriter.cs b/src/NATS.Client.Core/Internal/NuidWriter.cs index b116bff79..4e5f25f07 100644 --- a/src/NATS.Client.Core/Internal/NuidWriter.cs +++ b/src/NATS.Client.Core/Internal/NuidWriter.cs @@ -52,11 +52,7 @@ public static string NewNuid() Span buffer = stackalloc char[22]; if (TryWriteNuid(buffer)) { -#if NETSTANDARD2_0 - return new string(buffer.ToArray()); -#else - return new string(buffer); -#endif + return buffer.ToString(); } throw new InvalidOperationException("Internal error: can't generate nuid"); diff --git a/src/NATS.Client.Core/NatsConnection.RequestReply.cs b/src/NATS.Client.Core/NatsConnection.RequestReply.cs index ed706a857..da1b93015 100644 --- a/src/NATS.Client.Core/NatsConnection.RequestReply.cs +++ b/src/NATS.Client.Core/NatsConnection.RequestReply.cs @@ -132,11 +132,7 @@ internal static string NewInbox(ReadOnlySpan prefix) var remaining = buffer.Slice((int)totalPrefixLength); var didWrite = NuidWriter.TryWriteNuid(remaining); Debug.Assert(didWrite, "didWrite"); -#if NETSTANDARD2_0 - return new string(buffer.ToArray()); -#else - return new string(buffer); -#endif + return buffer.ToString(); } return Throw(); diff --git a/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs b/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs index 8e7505e60..785017167 100644 --- a/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs +++ b/src/NATS.Client.JetStream/Internal/NatsJSOrderedPushConsumer.cs @@ -388,11 +388,7 @@ private string NewNuid() Span buffer = stackalloc char[22]; if (NuidWriter.TryWriteNuid(buffer)) { -#if NETSTANDARD2_0 - return new string(buffer.ToArray()); -#else - return new string(buffer); -#endif + return buffer.ToString(); } throw new InvalidOperationException("Internal error: can't generate nuid"); diff --git a/src/NATS.Client.KeyValueStore/Internal/NatsKVWatcher.cs b/src/NATS.Client.KeyValueStore/Internal/NatsKVWatcher.cs index 8d6a9b1f8..036d19965 100644 --- a/src/NATS.Client.KeyValueStore/Internal/NatsKVWatcher.cs +++ b/src/NATS.Client.KeyValueStore/Internal/NatsKVWatcher.cs @@ -446,11 +446,7 @@ private string NewNuid() Span buffer = stackalloc char[22]; if (NuidWriter.TryWriteNuid(buffer)) { -#if NETSTANDARD2_0 - return new string(buffer.ToArray()); -#else - return new string(buffer); -#endif + return buffer.ToString(); } throw new InvalidOperationException("Internal error: can't generate nuid"); diff --git a/src/NATS.Client.ObjectStore/NatsObjStore.cs b/src/NATS.Client.ObjectStore/NatsObjStore.cs index 464191f02..465355db3 100644 --- a/src/NATS.Client.ObjectStore/NatsObjStore.cs +++ b/src/NATS.Client.ObjectStore/NatsObjStore.cs @@ -680,11 +680,7 @@ private string NewNuid() Span buffer = stackalloc char[22]; if (NuidWriter.TryWriteNuid(buffer)) { -#if NETSTANDARD2_0 - return new string(buffer.ToArray()); -#else - return new string(buffer); -#endif + return buffer.ToString(); } throw new InvalidOperationException("Internal error: can't generate nuid"); From 695bc6eec3465ecd3090067805465a69ababfffd Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Mon, 17 Jun 2024 23:10:39 +0100 Subject: [PATCH 44/47] KV base64 exception handling --- src/NATS.Client.KeyValueStore/NatsKVStore.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/NATS.Client.KeyValueStore/NatsKVStore.cs b/src/NATS.Client.KeyValueStore/NatsKVStore.cs index ebfbfc0f0..30540b436 100644 --- a/src/NATS.Client.KeyValueStore/NatsKVStore.cs +++ b/src/NATS.Client.KeyValueStore/NatsKVStore.cs @@ -264,7 +264,16 @@ public async ValueTask> GetEntryAsync(string key, ulong revisi if (response.Message.Data != null) { #if NETSTANDARD2_0 - var bytes = Convert.FromBase64String(response.Message.Data); + byte[] bytes; + try + { + bytes = Convert.FromBase64String(response.Message.Data); + } + catch (FormatException e) + { + throw new NatsKVException("Can't decode data message value", e); + } + var buffer = new ReadOnlySequence(bytes); try { From bc2aadc404abbd6e5a12b9fd89e3f70650e24b85 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Tue, 18 Jun 2024 14:25:48 +0100 Subject: [PATCH 45/47] dotnet format --- src/NATS.Client.Core/Internal/netstandard.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/NATS.Client.Core/Internal/netstandard.cs b/src/NATS.Client.Core/Internal/netstandard.cs index 12880068e..9e8af6f62 100644 --- a/src/NATS.Client.Core/Internal/netstandard.cs +++ b/src/NATS.Client.Core/Internal/netstandard.cs @@ -349,11 +349,11 @@ internal static class TaskExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool IsNotCompletedSuccessfully(this Task? task) { - #if NETSTANDARD2_0 - return task != null && task.Status != TaskStatus.RanToCompletion; - #else +#if NETSTANDARD2_0 + return task != null && task.Status != TaskStatus.RanToCompletion; +#else return task is { IsCompletedSuccessfully: false }; - #endif +#endif } } @@ -362,11 +362,11 @@ internal static class FistSpanExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ReadOnlySpan GetFirstSpan(this ReadOnlySequence sequence) { - #if NETSTANDARD2_0 - return sequence.First.Span; - #else +#if NETSTANDARD2_0 + return sequence.First.Span; +#else return sequence.FirstSpan; - #endif +#endif } } } From ebce2175cb002acaee246a27e2d38c5a7d57b3d9 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Tue, 18 Jun 2024 15:35:52 +0100 Subject: [PATCH 46/47] guarding MemoryMarshal.TryGetArray --- .../Internal/SslStreamConnection.cs | 12 ++++++++-- .../Internal/TcpConnection.cs | 12 ++++++++-- .../Internal/WebSocketConnection.cs | 12 ++++++++-- src/NATS.Client.Core/Internal/netstandard.cs | 14 ++++++++++++ src/NATS.Client.ObjectStore/NatsObjStore.cs | 22 +++++++++++++++++-- 5 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/NATS.Client.Core/Internal/SslStreamConnection.cs b/src/NATS.Client.Core/Internal/SslStreamConnection.cs index 814d8f290..b941cd8b8 100644 --- a/src/NATS.Client.Core/Internal/SslStreamConnection.cs +++ b/src/NATS.Client.Core/Internal/SslStreamConnection.cs @@ -58,7 +58,11 @@ public async ValueTask DisposeAsync() public async ValueTask SendAsync(ReadOnlyMemory buffer) { #if NETSTANDARD2_0 - MemoryMarshal.TryGetArray(buffer, out var segment); + if (MemoryMarshal.TryGetArray(buffer, out var segment) == false) + { + segment = new ArraySegment(buffer.ToArray()); + } + await _sslStream.WriteAsync(segment.Array, segment.Offset, segment.Count, _closeCts.Token).ConfigureAwait(false); #else await _sslStream.WriteAsync(buffer, _closeCts.Token).ConfigureAwait(false); @@ -70,7 +74,11 @@ public async ValueTask SendAsync(ReadOnlyMemory buffer) public ValueTask ReceiveAsync(Memory buffer) { #if NETSTANDARD2_0 - MemoryMarshal.TryGetArray(buffer, out ArraySegment segment); + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment) == false) + { + ThrowHelper.ThrowInvalidOperationException("Can't get underlying array"); + } + return new ValueTask(_sslStream.ReadAsync(segment.Array!, segment.Offset, segment.Count, _closeCts.Token)); #else return _sslStream.ReadAsync(buffer, _closeCts.Token); diff --git a/src/NATS.Client.Core/Internal/TcpConnection.cs b/src/NATS.Client.Core/Internal/TcpConnection.cs index 98295cfc6..b573d91db 100644 --- a/src/NATS.Client.Core/Internal/TcpConnection.cs +++ b/src/NATS.Client.Core/Internal/TcpConnection.cs @@ -86,7 +86,11 @@ public async ValueTask ConnectAsync(string host, int port, TimeSpan timeout) public ValueTask SendAsync(ReadOnlyMemory buffer) { #if NETSTANDARD2_0 - MemoryMarshal.TryGetArray(buffer, out var segment); + if (MemoryMarshal.TryGetArray(buffer, out var segment) == false) + { + segment = new ArraySegment(buffer.ToArray()); + } + return new ValueTask(_socket.SendAsync(segment, SocketFlags.None)); #else return _socket.SendAsync(buffer, SocketFlags.None, CancellationToken.None); @@ -97,7 +101,11 @@ public ValueTask SendAsync(ReadOnlyMemory buffer) public ValueTask ReceiveAsync(Memory buffer) { #if NETSTANDARD2_0 - MemoryMarshal.TryGetArray(buffer, out ArraySegment segment); + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment) == false) + { + ThrowHelper.ThrowInvalidOperationException("Can't get underlying array"); + } + return new ValueTask(_socket.ReceiveAsync(segment, SocketFlags.None)); #else return _socket.ReceiveAsync(buffer, SocketFlags.None, CancellationToken.None); diff --git a/src/NATS.Client.Core/Internal/WebSocketConnection.cs b/src/NATS.Client.Core/Internal/WebSocketConnection.cs index c69669c93..588ad82f3 100644 --- a/src/NATS.Client.Core/Internal/WebSocketConnection.cs +++ b/src/NATS.Client.Core/Internal/WebSocketConnection.cs @@ -64,7 +64,11 @@ public async ValueTask ConnectAsync(Uri uri, TimeSpan timeout) public async ValueTask SendAsync(ReadOnlyMemory buffer) { #if NETSTANDARD - MemoryMarshal.TryGetArray(buffer, out var segment); + if (MemoryMarshal.TryGetArray(buffer, out var segment) == false) + { + segment = new ArraySegment(buffer.ToArray()); + } + await _socket.SendAsync(segment, WebSocketMessageType.Binary, true, CancellationToken.None).ConfigureAwait(false); #else await _socket.SendAsync(buffer, WebSocketMessageType.Binary, WebSocketMessageFlags.EndOfMessage, CancellationToken.None).ConfigureAwait(false); @@ -76,7 +80,11 @@ public async ValueTask SendAsync(ReadOnlyMemory buffer) public async ValueTask ReceiveAsync(Memory buffer) { #if NETSTANDARD2_0 - MemoryMarshal.TryGetArray(buffer, out ArraySegment segment); + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment) == false) + { + ThrowHelper.ThrowInvalidOperationException("Can't get underlying array"); + } + var wsRead = await _socket.ReceiveAsync(segment, CancellationToken.None).ConfigureAwait(false); #else var wsRead = await _socket.ReceiveAsync(buffer, CancellationToken.None).ConfigureAwait(false); diff --git a/src/NATS.Client.Core/Internal/netstandard.cs b/src/NATS.Client.Core/Internal/netstandard.cs index 9e8af6f62..0194dd9bb 100644 --- a/src/NATS.Client.Core/Internal/netstandard.cs +++ b/src/NATS.Client.Core/Internal/netstandard.cs @@ -335,6 +335,20 @@ public static async IAsyncEnumerable ReadAllAsync(this ChannelReader re } } } + + internal static class ThrowHelper + { + internal static void ThrowInvalidOperationException(string message) + { + throw CreateInvalidOperationException(message); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static Exception CreateInvalidOperationException(string message) + { + return (Exception)new InvalidOperationException(message); + } + } } #endif diff --git a/src/NATS.Client.ObjectStore/NatsObjStore.cs b/src/NATS.Client.ObjectStore/NatsObjStore.cs index 465355db3..45ae0220f 100644 --- a/src/NATS.Client.ObjectStore/NatsObjStore.cs +++ b/src/NATS.Client.ObjectStore/NatsObjStore.cs @@ -244,8 +244,26 @@ public async ValueTask PutAsync(ObjectMetadata meta, Stream stre while (true) { #if NETSTANDARD2_0 - MemoryMarshal.TryGetArray((ReadOnlyMemory)memory, out var segment); - var read = await hashedStream.ReadAsync(segment.Array!, segment.Offset, segment.Count, cancellationToken); + int read; + if (MemoryMarshal.TryGetArray((ReadOnlyMemory)memory, out var segment) == false) + { + read = await hashedStream.ReadAsync(segment.Array!, segment.Offset, segment.Count, cancellationToken); + } + else + { + var bytes = ArrayPool.Shared.Rent(memory.Length); + try + { + segment = new ArraySegment(bytes, 0, memory.Length); + read = await hashedStream.ReadAsync(segment.Array!, segment.Offset, segment.Count, cancellationToken); + segment.Array.AsMemory(0, read).CopyTo(memory); + } + finally + { + ArrayPool.Shared.Return(bytes); + } + } + #else var read = await hashedStream.ReadAsync(memory, cancellationToken); #endif From 6641414e5f7b2e0b447a4a39fee58a183bcc0e2a Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Tue, 18 Jun 2024 15:38:50 +0100 Subject: [PATCH 47/47] adjusted dictionary remove call --- src/NATS.Client.Core/Internal/SubscriptionManager.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/NATS.Client.Core/Internal/SubscriptionManager.cs b/src/NATS.Client.Core/Internal/SubscriptionManager.cs index 2f1df1199..872423324 100644 --- a/src/NATS.Client.Core/Internal/SubscriptionManager.cs +++ b/src/NATS.Client.Core/Internal/SubscriptionManager.cs @@ -158,11 +158,7 @@ public ValueTask RemoveAsync(NatsSubBase sub) lock (_gate) { _bySub.Remove(sub); -#if NETSTANDARD2_0 _bySid.TryRemove(subMetadata.Sid, out _); -#else - _bySid.Remove(subMetadata.Sid, out _); -#endif } return _connection.UnsubscribeAsync(subMetadata.Sid);