Skip to content

Commit

Permalink
Add Random.Shared (#50297)
Browse files Browse the repository at this point in the history
* Add Random.Shared

* Disable on wasm test requiring parallelism
  • Loading branch information
stephentoub authored Apr 1, 2021
1 parent 363bf30 commit 1fea73f
Show file tree
Hide file tree
Showing 21 changed files with 171 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2240,7 +2240,7 @@ public virtual async Task ReadWrite_CustomMemoryManager_Success(bool useAsync)
Assert.Equal(1024, writeBuffer.Memory.Length);
Assert.Equal(writeBuffer.Memory.Length, readBuffer.Memory.Length);

new Random().NextBytes(writeBuffer.Memory.Span);
Random.Shared.NextBytes(writeBuffer.Memory.Span);
readBuffer.Memory.Span.Clear();

Task write = useAsync ?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,6 @@ private int TryPopCore(int count, out Node? poppedHead)
Node? head;
Node next;
int backoff = 1;
Random? r = null;
while (true)
{
head = _head;
Expand Down Expand Up @@ -649,11 +648,7 @@ private int TryPopCore(int count, out Node? poppedHead)

if (spin.NextSpinWillYield)
{
if (r == null)
{
r = new Random();
}
backoff = r.Next(1, BACKOFF_MAX_YIELDS);
backoff = Random.Shared.Next(1, BACKOFF_MAX_YIELDS);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public void RoundTrip_Chunks()
for (int i = 0; i < totalSize; i += chunkSize)
{
byte[] uncompressed = new byte[chunkSize];
new Random().NextBytes(uncompressed);
Random.Shared.NextBytes(uncompressed);
byte[] compressed = new byte[BrotliEncoder.GetMaxCompressedLength(chunkSize)];
byte[] decompressed = new byte[chunkSize];
var uncompressedSpan = new ReadOnlySpan<byte>(uncompressed);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class LoopbackSmtpServer : IDisposable
private bool _disposed = false;
private readonly Socket _listenSocket;
private readonly ConcurrentBag<Socket> _socketsToDispose;
private long _messageCounter = new Random().Next(1000, 2000);
private long _messageCounter = Random.Shared.Next(1000, 2000);

public readonly int Port;
public SmtpClient CreateClient() => new SmtpClient("localhost", Port);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ public partial class Ping
private const int MaxIpHeaderLengthInBytes = 60;
private static bool SendIpHeader => OperatingSystem.IsMacOS();
private static bool NeedsConnect => OperatingSystem.IsLinux();
[ThreadStatic]
private static Random? t_idGenerator;

private PingReply SendPingCore(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
{
Expand Down Expand Up @@ -51,8 +49,7 @@ private unsafe SocketConfig GetSocketConfig(IPAddress address, byte[] buffer, in
{
// Use a random value as the identifier. This doesn't need to be perfectly random
// or very unpredictable, rather just good enough to avoid unexpected conflicts.
Random rand = t_idGenerator ??= new Random();
ushort id = (ushort)rand.Next(ushort.MaxValue + 1);
ushort id = (ushort)Random.Shared.Next(ushort.MaxValue + 1);
IpHeader iph = default;

bool ipv4 = address.AddressFamily == AddressFamily.InterNetwork;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ private static async Task SendAndReceiveEOFAsync(QuicStream s1, QuicStream s2)
public async Task ReadWrite_Random_Success(int readSize, int writeSize)
{
byte[] testBuffer = new byte[8192];
new Random().NextBytes(testBuffer);
Random.Shared.NextBytes(testBuffer);

await RunClientServer(
async clientConnection =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,7 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout(
[ActiveIssue("https://github.com/dotnet/runtime/issues/46837", TestPlatforms.OSX)]
public async Task SslStream_UntrustedCaWithCustomCallback_OK(bool usePartialChain)
{
var rnd = new Random();
int split = rnd.Next(0, certificates.serverChain.Count - 1);
int split = Random.Shared.Next(0, certificates.serverChain.Count - 1);

var clientOptions = new SslClientAuthenticationOptions() { TargetHost = "localhost" };
clientOptions.RemoteCertificateValidationCallback =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@ public void AcceptAsync_WithReceiveBuffer_Success()
const int acceptBufferSize = acceptBufferOverheadSize + acceptBufferDataSize;

byte[] sendBuffer = new byte[acceptBufferDataSize];
new Random().NextBytes(sendBuffer);
Random.Shared.NextBytes(sendBuffer);

SocketAsyncEventArgs acceptArgs = new SocketAsyncEventArgs();
acceptArgs.Completed += OnAcceptCompleted;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ namespace System.Net.Sockets.Tests
public class UnixDomainSocketTest
{
private readonly ITestOutputHelper _log;
private static Random _random = new Random();

public UnixDomainSocketTest(ITestOutputHelper output)
{
Expand Down Expand Up @@ -251,7 +250,7 @@ public async Task Socket_SendReceiveAsync_Success()
public async Task Socket_SendReceiveAsync_PropagateToStream_Success(int iterations, int writeBufferSize, int readBufferSize)
{
var writeBuffer = new byte[writeBufferSize * iterations];
new Random().NextBytes(writeBuffer);
Random.Shared.NextBytes(writeBuffer);
var readData = new MemoryStream();

string path = GetRandomNonExistingFilePath();
Expand Down Expand Up @@ -317,7 +316,7 @@ public async Task ConcurrentSendReceive(bool forceNonBlocking)
const int Iters = 25;
byte[] sendData = new byte[Iters];
byte[] receiveData = new byte[sendData.Length];
new Random().NextBytes(sendData);
Random.Shared.NextBytes(sendData);

string path = GetRandomNonExistingFilePath();

Expand Down Expand Up @@ -359,7 +358,7 @@ public async Task ConcurrentSendReceiveAsync()
const int Iters = 2048;
byte[] sendData = new byte[Iters];
byte[] receiveData = new byte[sendData.Length];
new Random().NextBytes(sendData);
Random.Shared.NextBytes(sendData);

string path = GetRandomNonExistingFilePath();

Expand Down Expand Up @@ -498,7 +497,7 @@ private static string GetRandomNonExistingFilePath()
do
{
// get random name and append random number of characters to get variable name length.
result = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + new string('A', _random.Next(1, 32)));
result = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + new string('A', Random.Shared.Next(1, 32)));
}
while (File.Exists(result));

Expand Down
7 changes: 2 additions & 5 deletions src/libraries/System.Net.WebClient/tests/WebClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -660,11 +660,8 @@ public async Task UploadData_LargeData_Success(Uri server)
byte[] ignored = await UploadDataAsync(wc, server.ToString(), Encoding.UTF8.GetBytes(largeText));
}

private static string GetRandomText(int length)
{
var rand = new Random();
return new string(Enumerable.Range(0, 512 * 1024).Select(_ => (char)('a' + rand.Next(0, 26))).ToArray());
}
private static string GetRandomText(int length) =>
new string(Enumerable.Range(0, 512 * 1024).Select(_ => (char)('a' + Random.Shared.Next(0, 26))).ToArray());

[OuterLoop("Uses external servers")]
[Theory]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,8 @@ public async Task SendReceive_PartialMessageDueToSmallReceiveBuffer_Success(Uri
[PlatformSpecific(~TestPlatforms.Browser)] // JS Websocket does not support see issue https://github.com/dotnet/runtime/issues/46983
public async Task SendReceive_PartialMessageBeforeCompleteMessageArrives_Success(Uri server)
{
var rand = new Random();
var sendBuffer = new byte[ushort.MaxValue + 1];
rand.NextBytes(sendBuffer);
Random.Shared.NextBytes(sendBuffer);
var sendSegment = new ArraySegment<byte>(sendBuffer);

// Ask the remote server to echo back received messages without ever signaling "end of message".
Expand Down Expand Up @@ -464,7 +463,6 @@ public async Task ZeroByteReceive_CompletesWhenDataAvailable(Uri server)
{
using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output))
{
var rand = new Random();
var ctsDefault = new CancellationTokenSource(TimeOutMilliseconds);

// Do a 0-byte receive. It shouldn't complete yet.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,7 @@ private void TestTryCopyToSpan<T>() where T : struct
// us to check that we didn't overwrite any part of the destination
// if it was too small to contain the entire output.

new Random().NextBytes(MemoryMarshal.AsBytes(destination));
Random.Shared.NextBytes(MemoryMarshal.AsBytes(destination));
T[] destinationCopy = destination.ToArray();

Assert.False(vector.TryCopyTo(destination.Slice(1)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ namespace System.Numerics.Hashing
{
internal static class HashHelpers
{
public static readonly int RandomSeed = new Random().Next(int.MinValue, int.MaxValue);

public static int Combine(int h1, int h2)
{
// RyuJIT optimizes this to use the ROL instruction
Expand Down
103 changes: 103 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/Random.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace System
{
/// <summary>
Expand Down Expand Up @@ -36,6 +39,17 @@ public Random(int Seed) =>
// previously output continues to be output.
_impl = new Net5CompatImpl(this, Seed);

/// <summary>Constructor used by <see cref="ThreadSafeRandom"/>.</summary>
/// <param name="isThreadSafeRandom">Must be true.</param>
protected private Random(bool isThreadSafeRandom)
{
Debug.Assert(isThreadSafeRandom);
_impl = null!; // base implementation isn't used at all
}

/// <summary>Provides a thread-safe <see cref="Random"/> instance that may be used concurrently from any thread.</summary>
public static Random Shared { get; } = new ThreadSafeRandom();

/// <summary>Returns a non-negative random integer.</summary>
/// <returns>A 32-bit signed integer that is greater than or equal to 0 and less than <see cref="int.MaxValue"/>.</returns>
public virtual int Next() => _impl.Next();
Expand Down Expand Up @@ -148,5 +162,94 @@ private static void ThrowMaxValueMustBeNonNegative() =>

private static void ThrowMinMaxValueSwapped() =>
throw new ArgumentOutOfRangeException("minValue", SR.Format(SR.Argument_MinMaxValue, "minValue", "maxValue"));

/// <summary>Random implementation that delegates all calls to a ThreadStatic Impl instance.</summary>
private sealed class ThreadSafeRandom : Random
{
// We need Random.Shared to return an instance that is thread-safe, as it may be used from multiple threads.
// It's also possible that one thread could retrieve Shared and pass it to another thread, so Shared can't
// just access a ThreadStatic to return a Random instance stored there. As such, we need the instance
// returned from Shared itself to be thread-safe, which can be accomplished either by locking around all
// accesses or by itself accessing a ThreadStatic on every access: we've chosen the latter, as it is more
// scalable. With that, we have two basic approaches:
// 1. the instance returned can be a base Random instance constructed with an _impl that is a ThreadSafeImpl.
// 2. the instance returned can be a special Random-derived instance, where _impl is ignored and the derived
// type overrides all methods on the base.
// (1) is cleaner, as (2) requires duplicating a bit more code, but (2) enables all virtual dispatch to be
// devirtualized and potentially inlined, so that Random.Shared.NextXx(...) ends up being faster.
// This implements (2).

[ThreadStatic]
private static XoshiroImpl? t_random;

public ThreadSafeRandom() : base(isThreadSafeRandom: true) { }

private static XoshiroImpl LocalRandom => t_random ?? Create();

[MethodImpl(MethodImplOptions.NoInlining)]
private static XoshiroImpl Create() => t_random = new();

public override int Next() => LocalRandom.Next();

public override int Next(int maxValue)
{
if (maxValue < 0)
{
ThrowMaxValueMustBeNonNegative();
}

return LocalRandom.Next(maxValue);
}

public override int Next(int minValue, int maxValue)
{
if (minValue > maxValue)
{
ThrowMinMaxValueSwapped();
}

return LocalRandom.Next(minValue, maxValue);
}

public override long NextInt64() => LocalRandom.NextInt64();

public override long NextInt64(long maxValue)
{
if (maxValue < 0)
{
ThrowMaxValueMustBeNonNegative();
}

return LocalRandom.NextInt64(maxValue);
}

public override long NextInt64(long minValue, long maxValue)
{
if (minValue > maxValue)
{
ThrowMinMaxValueSwapped();
}

return LocalRandom.NextInt64(minValue, maxValue);
}

public override float NextSingle() => LocalRandom.NextSingle();

public override double NextDouble() => LocalRandom.NextDouble();

public override void NextBytes(byte[] buffer)
{
if (buffer is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.buffer);
}

LocalRandom.NextBytes(buffer);
}

public override void NextBytes(Span<byte> buffer) => LocalRandom.NextBytes(buffer);

protected override double Sample() => throw new NotSupportedException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ public bool CheckCanReadBinaryContent()
try
{
int nBytes = 0;
switch ((int)new Random().Next(4))
switch ((int)Random.Shared.Next(4))
{
case 0:
CError.WriteLineIgnore("Selecting RCABH");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ public void ExpansionOfVariableSucceeds()
// envvar1=animal;
// and we are going to check that the expanded %envvar1% is animal.

Random r = new Random();
string envVar1 = "TestVariable_ExpansionOfVariableSucceeds_" + r.Next().ToString();
string envVar1 = "TestVariable_ExpansionOfVariableSucceeds_" + Random.Shared.Next().ToString();
string expectedValue = "animal";

try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,8 @@ public void VariableNamesAreCaseInsensitiveAsAppropriate()
[Fact]
public void CanGetAllVariablesIndividually()
{
Random r = new Random();
string envVar1 = "TestVariable_CanGetVariablesIndividually_" + r.Next().ToString();
string envVar2 = "TestVariable_CanGetVariablesIndividually_" + r.Next().ToString();
string envVar1 = "TestVariable_CanGetVariablesIndividually_" + Random.Shared.Next().ToString();
string envVar2 = "TestVariable_CanGetVariablesIndividually_" + Random.Shared.Next().ToString();

try
{
Expand Down
Loading

0 comments on commit 1fea73f

Please sign in to comment.