diff --git a/src/System.Threading/ref/System.Threading.cs b/src/System.Threading/ref/System.Threading.cs
index 2347a4f1d824..a564bfde4ca7 100644
--- a/src/System.Threading/ref/System.Threading.cs
+++ b/src/System.Threading/ref/System.Threading.cs
@@ -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
diff --git a/src/System.Threading/tests/Configurations.props b/src/System.Threading/tests/Configurations.props
index c398e42e8994..77a4b65bc94a 100644
--- a/src/System.Threading/tests/Configurations.props
+++ b/src/System.Threading/tests/Configurations.props
@@ -3,6 +3,7 @@
netstandard;
+ netcoreapp;
\ No newline at end of file
diff --git a/src/System.Threading/tests/InterlockedTests.cs b/src/System.Threading/tests/InterlockedTests.cs
index ebdd8e6ae16d..83cac4605280 100644
--- a/src/System.Threading/tests/InterlockedTests.cs
+++ b/src/System.Threading/tests/InterlockedTests.cs
@@ -8,7 +8,7 @@
namespace System.Threading.Tests
{
- public class InterlockedTests
+ public partial class InterlockedTests
{
[Fact]
public void IncrementDecrement_int()
diff --git a/src/System.Threading/tests/InterlockedTests.netcoreapp.cs b/src/System.Threading/tests/InterlockedTests.netcoreapp.cs
new file mode 100644
index 000000000000..8349ffcf6ca9
--- /dev/null
+++ b/src/System.Threading/tests/InterlockedTests.netcoreapp.cs
@@ -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(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 threads = new List();
+ 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);
+ }
+ }
+}
diff --git a/src/System.Threading/tests/System.Threading.Tests.csproj b/src/System.Threading/tests/System.Threading.Tests.csproj
index 0986636e9d74..48bf771e14f0 100644
--- a/src/System.Threading/tests/System.Threading.Tests.csproj
+++ b/src/System.Threading/tests/System.Threading.Tests.csproj
@@ -17,6 +17,7 @@
+