Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
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
1 change: 1 addition & 0 deletions src/System.Threading/ref/System.Threading.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ public static partial class Interlocked
public static int Increment(ref int location) { throw null; }
public static long Increment(ref long location) { throw null; }
public static void MemoryBarrier() { }
public static void MemoryBarrierProcessWide() { }
public static long Read(ref long location) { throw null; }
}
public static partial class LazyInitializer
Expand Down
1 change: 1 addition & 0 deletions src/System.Threading/tests/Configurations.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<BuildConfigurations>
netstandard;
netcoreapp;
</BuildConfigurations>
</PropertyGroup>
</Project>
2 changes: 1 addition & 1 deletion src/System.Threading/tests/InterlockedTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace System.Threading.Tests
{
public class InterlockedTests
public partial class InterlockedTests
{
[Fact]
public void IncrementDecrement_int()
Expand Down
124 changes: 124 additions & 0 deletions src/System.Threading/tests/InterlockedTests.netcoreapp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using Xunit;

namespace System.Threading.Tests
{
public partial class InterlockedTests
{
// Taking this lock on the same thread repeatedly is very fast because it has no interlocked operations.
// Switching the thread where the lock is taken is expensive because of allocation and FlushProcessWriteBuffers.
private class AsymmetricLock
{
public class LockCookie
{
internal LockCookie(int threadId)
{
ThreadId = threadId;
Taken = false;
}

public void Exit()
{
Debug.Assert(ThreadId == Environment.CurrentManagedThreadId);
Taken = false;
}

internal readonly int ThreadId;
internal bool Taken;
}

private LockCookie _current = new LockCookie(-1);

[MethodImpl(MethodImplOptions.NoInlining)]
private static T VolatileReadWithoutBarrier<T>(ref T location)
{
return location;
}

// Returning LockCookie to call Exit on is the fastest implementation because of it works naturally with the RCU pattern.
// The traditional Enter/Exit lock interface would require thread local storage or some other scheme to reclaim the cookie.
public LockCookie Enter()
{
int currentThreadId = Environment.CurrentManagedThreadId;

LockCookie entry = _current;

if (entry.ThreadId == currentThreadId)
{
entry.Taken = true;

//
// If other thread started stealing the ownership, we need to take slow path.
//
// Make sure that the compiler won't reorder the read with the above write by wrapping the read in no-inline method.
// RyuJIT won't reorder them today, but more advanced optimizers might. Regular Volatile.Read would be too big of
// a hammer because of it will result into memory barrier on ARM that we do not need here.
//
//
if (VolatileReadWithoutBarrier(ref _current) == entry)
{
return entry;
}

entry.Taken = false;
}

return EnterSlow();
}

private LockCookie EnterSlow()
{
// Attempt to steal the ownership. Take a regular lock to ensure that only one thread is trying to steal it at a time.
lock (this)
{
// We are the new fast thread now!
var oldEntry = _current;
_current = new LockCookie(Environment.CurrentManagedThreadId);

// After MemoryBarrierProcessWide, we can be sure that the Volatile.Read done by the fast thread will see that it is not a fast
// thread anymore, and thus it will not attempt to enter the lock.
Interlocked.MemoryBarrierProcessWide();

// Keep looping as long as the lock is taken by other thread
SpinWait sw = new SpinWait();
while (oldEntry.Taken)
sw.SpinOnce();

_current.Taken = true;
return _current;
}
}
}

[Fact]
public void MemoryBarrierProcessWide()
{
// Stress MemoryBarrierProcessWide correctness using a simple AsymmetricLock

AsymmetricLock asymmetricLock = new AsymmetricLock();
List<Task> threads = new List<Task>();
int count = 0;
for (int i = 0; i < 1000; i++)
{
threads.Add(Task.Run(() =>
{
for (int j = 0; j < 1000; j++)
{
var cookie = asymmetricLock.Enter();
count++;
cookie.Exit();
}
}));
}
Task.WaitAll(threads.ToArray());
Assert.Equal(1000*1000, count);
}
}
}
1 change: 1 addition & 0 deletions src/System.Threading/tests/System.Threading.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<Compile Include="EtwTests.cs" />
<Compile Include="EventWaitHandleTests.cs" />
<Compile Include="InterlockedTests.cs" />
<Compile Include="InterlockedTests.netcoreapp.cs" Condition="'$(TargetGroup)'=='netcoreapp'" />
<Compile Include="HostExecutionContextTests.cs" />
<Compile Include="HostExecutionContextManagerTests.cs" />
<Compile Include="ManualResetEventTests.cs" />
Expand Down