Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/Polyfill/RandomPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,22 @@ sealed class ThreadSafeRandom : Random
[ThreadStatic]
static Random? random;

// On .NET Framework `new Random()` is seeded from the tick count, so instances created
// together (e.g. by threads started at the same time) would produce identical sequences.
// Instead each thread's instance takes its seed from a single process-wide source.
static readonly Random seedSource = new();

[MethodImpl(MethodImplOptions.NoInlining)]
static Random Create() => random = new();
static Random Create()
{
int seed;
lock (seedSource)
{
seed = seedSource.Next();
}

return random = new(seed);
}

static Random LocalRandom => random ?? Create();

Expand Down
11 changes: 10 additions & 1 deletion src/Split/net461/RandomPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ sealed class ThreadSafeRandom : Random
{
[ThreadStatic]
static Random? random;
static readonly Random seedSource = new();
[MethodImpl(MethodImplOptions.NoInlining)]
static Random Create() => random = new();
static Random Create()
{
int seed;
lock (seedSource)
{
seed = seedSource.Next();
}
return random = new(seed);
}
static Random LocalRandom => random ?? Create();
public override int Next() => LocalRandom.Next();
public override int Next(int maxValue) => LocalRandom.Next(maxValue);
Expand Down
11 changes: 10 additions & 1 deletion src/Split/net462/RandomPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ sealed class ThreadSafeRandom : Random
{
[ThreadStatic]
static Random? random;
static readonly Random seedSource = new();
[MethodImpl(MethodImplOptions.NoInlining)]
static Random Create() => random = new();
static Random Create()
{
int seed;
lock (seedSource)
{
seed = seedSource.Next();
}
return random = new(seed);
}
static Random LocalRandom => random ?? Create();
public override int Next() => LocalRandom.Next();
public override int Next(int maxValue) => LocalRandom.Next(maxValue);
Expand Down
11 changes: 10 additions & 1 deletion src/Split/net47/RandomPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ sealed class ThreadSafeRandom : Random
{
[ThreadStatic]
static Random? random;
static readonly Random seedSource = new();
[MethodImpl(MethodImplOptions.NoInlining)]
static Random Create() => random = new();
static Random Create()
{
int seed;
lock (seedSource)
{
seed = seedSource.Next();
}
return random = new(seed);
}
static Random LocalRandom => random ?? Create();
public override int Next() => LocalRandom.Next();
public override int Next(int maxValue) => LocalRandom.Next(maxValue);
Expand Down
11 changes: 10 additions & 1 deletion src/Split/net471/RandomPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ sealed class ThreadSafeRandom : Random
{
[ThreadStatic]
static Random? random;
static readonly Random seedSource = new();
[MethodImpl(MethodImplOptions.NoInlining)]
static Random Create() => random = new();
static Random Create()
{
int seed;
lock (seedSource)
{
seed = seedSource.Next();
}
return random = new(seed);
}
static Random LocalRandom => random ?? Create();
public override int Next() => LocalRandom.Next();
public override int Next(int maxValue) => LocalRandom.Next(maxValue);
Expand Down
11 changes: 10 additions & 1 deletion src/Split/net472/RandomPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ sealed class ThreadSafeRandom : Random
{
[ThreadStatic]
static Random? random;
static readonly Random seedSource = new();
[MethodImpl(MethodImplOptions.NoInlining)]
static Random Create() => random = new();
static Random Create()
{
int seed;
lock (seedSource)
{
seed = seedSource.Next();
}
return random = new(seed);
}
static Random LocalRandom => random ?? Create();
public override int Next() => LocalRandom.Next();
public override int Next(int maxValue) => LocalRandom.Next(maxValue);
Expand Down
11 changes: 10 additions & 1 deletion src/Split/net48/RandomPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ sealed class ThreadSafeRandom : Random
{
[ThreadStatic]
static Random? random;
static readonly Random seedSource = new();
[MethodImpl(MethodImplOptions.NoInlining)]
static Random Create() => random = new();
static Random Create()
{
int seed;
lock (seedSource)
{
seed = seedSource.Next();
}
return random = new(seed);
}
static Random LocalRandom => random ?? Create();
public override int Next() => LocalRandom.Next();
public override int Next(int maxValue) => LocalRandom.Next(maxValue);
Expand Down
11 changes: 10 additions & 1 deletion src/Split/net481/RandomPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ sealed class ThreadSafeRandom : Random
{
[ThreadStatic]
static Random? random;
static readonly Random seedSource = new();
[MethodImpl(MethodImplOptions.NoInlining)]
static Random Create() => random = new();
static Random Create()
{
int seed;
lock (seedSource)
{
seed = seedSource.Next();
}
return random = new(seed);
}
static Random LocalRandom => random ?? Create();
public override int Next() => LocalRandom.Next();
public override int Next(int maxValue) => LocalRandom.Next(maxValue);
Expand Down
11 changes: 10 additions & 1 deletion src/Split/net5.0/RandomPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ sealed class ThreadSafeRandom : Random
{
[ThreadStatic]
static Random? random;
static readonly Random seedSource = new();
[MethodImpl(MethodImplOptions.NoInlining)]
static Random Create() => random = new();
static Random Create()
{
int seed;
lock (seedSource)
{
seed = seedSource.Next();
}
return random = new(seed);
}
static Random LocalRandom => random ?? Create();
public override int Next() => LocalRandom.Next();
public override int Next(int maxValue) => LocalRandom.Next(maxValue);
Expand Down
11 changes: 10 additions & 1 deletion src/Split/netcoreapp2.0/RandomPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ sealed class ThreadSafeRandom : Random
{
[ThreadStatic]
static Random? random;
static readonly Random seedSource = new();
[MethodImpl(MethodImplOptions.NoInlining)]
static Random Create() => random = new();
static Random Create()
{
int seed;
lock (seedSource)
{
seed = seedSource.Next();
}
return random = new(seed);
}
static Random LocalRandom => random ?? Create();
public override int Next() => LocalRandom.Next();
public override int Next(int maxValue) => LocalRandom.Next(maxValue);
Expand Down
11 changes: 10 additions & 1 deletion src/Split/netcoreapp2.1/RandomPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ sealed class ThreadSafeRandom : Random
{
[ThreadStatic]
static Random? random;
static readonly Random seedSource = new();
[MethodImpl(MethodImplOptions.NoInlining)]
static Random Create() => random = new();
static Random Create()
{
int seed;
lock (seedSource)
{
seed = seedSource.Next();
}
return random = new(seed);
}
static Random LocalRandom => random ?? Create();
public override int Next() => LocalRandom.Next();
public override int Next(int maxValue) => LocalRandom.Next(maxValue);
Expand Down
11 changes: 10 additions & 1 deletion src/Split/netcoreapp2.2/RandomPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ sealed class ThreadSafeRandom : Random
{
[ThreadStatic]
static Random? random;
static readonly Random seedSource = new();
[MethodImpl(MethodImplOptions.NoInlining)]
static Random Create() => random = new();
static Random Create()
{
int seed;
lock (seedSource)
{
seed = seedSource.Next();
}
return random = new(seed);
}
static Random LocalRandom => random ?? Create();
public override int Next() => LocalRandom.Next();
public override int Next(int maxValue) => LocalRandom.Next(maxValue);
Expand Down
11 changes: 10 additions & 1 deletion src/Split/netcoreapp3.0/RandomPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ sealed class ThreadSafeRandom : Random
{
[ThreadStatic]
static Random? random;
static readonly Random seedSource = new();
[MethodImpl(MethodImplOptions.NoInlining)]
static Random Create() => random = new();
static Random Create()
{
int seed;
lock (seedSource)
{
seed = seedSource.Next();
}
return random = new(seed);
}
static Random LocalRandom => random ?? Create();
public override int Next() => LocalRandom.Next();
public override int Next(int maxValue) => LocalRandom.Next(maxValue);
Expand Down
11 changes: 10 additions & 1 deletion src/Split/netcoreapp3.1/RandomPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ sealed class ThreadSafeRandom : Random
{
[ThreadStatic]
static Random? random;
static readonly Random seedSource = new();
[MethodImpl(MethodImplOptions.NoInlining)]
static Random Create() => random = new();
static Random Create()
{
int seed;
lock (seedSource)
{
seed = seedSource.Next();
}
return random = new(seed);
}
static Random LocalRandom => random ?? Create();
public override int Next() => LocalRandom.Next();
public override int Next(int maxValue) => LocalRandom.Next(maxValue);
Expand Down
11 changes: 10 additions & 1 deletion src/Split/netstandard2.0/RandomPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ sealed class ThreadSafeRandom : Random
{
[ThreadStatic]
static Random? random;
static readonly Random seedSource = new();
[MethodImpl(MethodImplOptions.NoInlining)]
static Random Create() => random = new();
static Random Create()
{
int seed;
lock (seedSource)
{
seed = seedSource.Next();
}
return random = new(seed);
}
static Random LocalRandom => random ?? Create();
public override int Next() => LocalRandom.Next();
public override int Next(int maxValue) => LocalRandom.Next(maxValue);
Expand Down
11 changes: 10 additions & 1 deletion src/Split/netstandard2.1/RandomPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ sealed class ThreadSafeRandom : Random
{
[ThreadStatic]
static Random? random;
static readonly Random seedSource = new();
[MethodImpl(MethodImplOptions.NoInlining)]
static Random Create() => random = new();
static Random Create()
{
int seed;
lock (seedSource)
{
seed = seedSource.Next();
}
return random = new(seed);
}
static Random LocalRandom => random ?? Create();
public override int Next() => LocalRandom.Next();
public override int Next(int maxValue) => LocalRandom.Next(maxValue);
Expand Down
11 changes: 10 additions & 1 deletion src/Split/uap10.0/RandomPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ sealed class ThreadSafeRandom : Random
{
[ThreadStatic]
static Random? random;
static readonly Random seedSource = new();
[MethodImpl(MethodImplOptions.NoInlining)]
static Random Create() => random = new();
static Random Create()
{
int seed;
lock (seedSource)
{
seed = seedSource.Next();
}
return random = new(seed);
}
static Random LocalRandom => random ?? Create();
public override int Next() => LocalRandom.Next();
public override int Next(int maxValue) => LocalRandom.Next(maxValue);
Expand Down
28 changes: 28 additions & 0 deletions src/Tests/RandomPolyfillTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,32 @@ public async Task NextInt()

await Assert.That(number).IsGreaterThanOrEqualTo(0).And.IsLessThanOrEqualTo(10);
}

[Test]
public async Task SharedDiffersAcrossThreadsStartedTogether()
{
const int threadCount = 16;
var sequences = new string[threadCount];
using var barrier = new Barrier(threadCount);
var threads = new List<Thread>();
for (var index = 0; index < threadCount; index++)
{
var local = index;
var thread = new Thread(() =>
{
barrier.SignalAndWait();
var shared = Random.Shared;
sequences[local] = $"{shared.Next()},{shared.Next()},{shared.Next()},{shared.Next()}";
});
threads.Add(thread);
thread.Start();
}

foreach (var thread in threads)
{
thread.Join();
}

await Assert.That(sequences.Distinct().Count()).IsEqualTo(threadCount);
}
}
Loading