-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for .NET Standard 2.0 and 2.1 #513
Conversation
Should we put this in a single file? So we don't have to put it all over the place?
|
using var cts = new CancellationTokenSource(timeout); | ||
await _sslStream.AuthenticateAsClientAsync(options, cts.Token).ConfigureAwait(false); | ||
#else | ||
await _sslStream.AuthenticateAsClientAsync(uri.Host).ConfigureAwait(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this effectively ignores _tlsOpts
on netstandard
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the problem was I couldn't see na easy way of loading the PEM files
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea we may need to bring in Bouncy Castle or something to do it. Not sure if someone has made a good compat library
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some food for thought but overall this is awesome. <3
internal static bool IsNotCompletedSuccessfully(this Task? task) | ||
{ | ||
return task != null && (!task.IsCompleted || task.IsCanceled || task.IsFaulted); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure whether it's actually better to bang against Task.Status and/or Task.IsCompleted+Task.Exception!=null
or not, here's some sharplab as FTT
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done (task.Status == TaskStatus.RanToCompletion
)
#if NETSTANDARD2_0 | ||
if (_flushTask.IsNotCompletedSuccessfully()) | ||
#else | ||
if (_flushTask is { IsCompletedSuccessfully: false }) | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stupid question: would it be cleaner to put the logic shifts in IsNotCompletedSuccessfully()
extension method and just aggressive inline? Or would it be too ugly for how shims should be composed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes good point, fewer #if
the better!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
#if NETSTANDARD2_0 | ||
var span = buffer.IsSingleSegment ? buffer.First.Span : buffer.ToArray(); | ||
#else | ||
var span = buffer.IsSingleSegment ? buffer.FirstSpan : buffer.ToArray(); | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another potential candidate for ext-method-consolidation is buffer.FirstSpan
vs buffer.First.Span
.
I should note that, AFAIR, if the extension method is internal, Resharper/Rider can always let team 're-dupe' the callsites later easily if ppl change their minds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
#if NETSTANDARD2_0 | ||
return new string(buffer.ToArray()); | ||
#else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC at least if we are pulling in System.Memory
for netstandard we can just do buffer.ToString()
here and there's logic to handle that for ReadOnlySpan<char>
without having to do the unsafe nasty on your own :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
very good observation. yes, there is an exception for span in ToString(). nice!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
#if NETSTANDARD2_0 | ||
var randomBytes = new byte[(int)PrefixLength]; | ||
|
||
if (rng == null) | ||
{ | ||
using var randomNumberGenerator = RandomNumberGenerator.Create(); | ||
randomNumberGenerator.GetBytes(randomBytes); | ||
} | ||
#else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we able to explain why the randomness this will do is as safe as newer platforms? (It's been a while since I've used the RNG in standard but figured it was good to ask)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think as long as we use 'some kind of' RNG we should be fine here. This is about generating unique IDs and there shouldn't be a huge security concern but please feel free to challenge that, I might be missing something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remarks from netstandard System.Security.Cryptography.RandomNumberGenerator class docs:
Cryptographic random number generators create cryptographically strong random values.
Using the static members of this class is the preferred way to generate random values.
To create a random number generator, call the Create() method. This is preferred over calling the constructor of the derived class RNGCryptoServiceProvider, which is not available on all platforms.
#if NETSTANDARD2_0 | ||
_bySid.TryRemove(subMetadata.Sid, out _); | ||
#else | ||
_bySid.Remove(subMetadata.Sid, out _); | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there value in using the different overloads here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
||
namespace NATS.Client.Core.Internal; | ||
|
||
internal sealed class ThreadPoolWorkItem<T> : IThreadPoolWorkItem |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RIP ;_;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it was a good one. did you write it back in the day?
@@ -128,7 +132,11 @@ internal static string NewInbox(ReadOnlySpan<char> prefix) | |||
var remaining = buffer.Slice((int)totalPrefixLength); | |||
var didWrite = NuidWriter.TryWriteNuid(remaining); | |||
Debug.Assert(didWrite, "didWrite"); | |||
#if NETSTANDARD2_0 | |||
return new string(buffer.ToArray()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another potential case for buffer.ToString()
instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
var bytes = Convert.FromBase64String(response.Message.Data); | ||
var buffer = new ReadOnlySequence<byte>(bytes); | ||
try | ||
{ | ||
data = serializer.Deserialize(buffer); | ||
} | ||
catch (Exception e) | ||
{ | ||
deserializeException = new NatsDeserializeException(buffer.ToArray(), e); | ||
data = default; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of notes here:
- I think if we can, we should try to keep behavior consistent, even if that means the
Convert.FromBase64String
is done as a try-catch. - Is there an issue on the backlog to convert the
StreamMessage.Data
type into something a bit less thrashy?System.Buffers.Text
has To/From for utf8 byte sequences and it looks like they work with netstandard20, adapting to such an approach longer term may help consolidate this code as well as lower the deserialization cost of KV and other stream reads in NET8 (as we can hold the base64 in UTF8 bytes rather than UTF16 string, maybe pool in future too? Not sure how easy it would be to do that part though...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(1) 👍
(2) that sounds good. created one: #518
#if NETSTANDARD2_0 | ||
var hash = sha256.ComputeHash(value.ToArray()); | ||
hash.AsSpan().CopyTo(destination); | ||
#else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where this gets tricky is, if the object is above a certain size, grab a pooled array and copy the contents over.
-OR-, it might be better to pay the small price of something like CommunityToolkit.HighPerformance
's ReadOnlyMemory<byte>
stream adapter to avoid the copies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
created #519
* Support for .NET Standard 2.0 and 2.1 (#513)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
* Support for .NET Standard 2.0 and 2.1 (#513)
Main focus of this PR is to introduce .NET Standard support as a preview feature and make absolutely sure the .NET 6 & 8 support is not affected at all.
To reduce the performance impact on platforms that support 2.1 (e.g. Unity) we take advantage of 2.1 APIs where possible. We can create follow up PRs to improve .NET Standard performance for both 2.0 and 2.1.
InboxSub WeakRef changes for 2.0
There are minor changes for 2.1 but 2.0 seems to lack quite a few APIs so changes in this class might require a little more scrutiny.
TLS Support
Most of the APIs aren't available in netstandard, hence TLS support will be limited in targets using netstandard. However the important thing is to make sure net8.0 isn't affected at all. Suggestion is to improve netstandard TLS support in follow up PRs and have a limited support for TLS features at least in the initial preview release.
Code Changes in net6.0 and net8.0 Targets
There are minor code changes which will affect the net6.0 and net8.0 builds. These code changes reduce preprocessor usage significantly, however they shouldn't introducing any functional changes or side-effects.
ValueTask.CompletedTask
doesn't exist in netstandard so instead we changed all our usages todefault
as it's implemented in source for ValueTask.CompletedTaskValueTask.FromResult
doesn't exist in netstandard so instead we changed all our usages to using.ctor
as it's implemented in source for ValueTask.FromResultRemoved unused classes
CancellationTimerPool
,FixedArrayBufferWriter
andThreadPoolWorkItem
and their usages.Replaced
TimeSpan
operators withTicks
calculation similar to what happens in current TimeSpan source except some bounds checks which should not affect us since we're using constants.string.Split()
, usechar
instead ofstring
inReplyToDateTimeAndSeq.cs
Also in
NatsKVStore.cs
Tests
There are no changes to the existing tests to make sure no regressions happen. Only couple of tests are deleted related to now unused classes in the library.
The new 'Platform' test targets .NET Framework 4.8.1 and uses netstandard2.0 version of the library. These tests broadly covers all the libraries demonstrating that netstandard target works as expected at least for the basic functionality.
Idea is eventually merge all the tests and run them for all the targets for best platform coverage.
Info
https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0#select-net-standard-version