diff --git a/src/libraries/System.Threading/tests/InterlockedTests.cs b/src/libraries/System.Threading/tests/InterlockedTests.cs index cd394521158631..851f3038a3fd32 100644 --- a/src/libraries/System.Threading/tests/InterlockedTests.cs +++ b/src/libraries/System.Threading/tests/InterlockedTests.cs @@ -7,6 +7,7 @@ using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; +using Microsoft.DotNet.RemoteExecutor; using Xunit; namespace System.Threading.Tests @@ -1492,6 +1493,41 @@ public void MemoryBarrierProcessWide() Assert.Equal(1000*1000, count); } + private static bool IsThreadingAndRemoteExecutorSupported => + PlatformDetection.IsMultithreadingSupported && RemoteExecutor.IsSupported; + + [ConditionalFact(typeof(InterlockedTests), nameof(IsThreadingAndRemoteExecutorSupported))] + public void MemoryBarrierProcessWide_ConcurrentCalls() + { + RemoteExecutor.Invoke(static () => + { + const int ThreadCount = 4; + const int IterationCount = 100; + + using Barrier startBarrier = new Barrier(ThreadCount); + Thread[] threads = new Thread[ThreadCount]; + + for (int i = 0; i < threads.Length; i++) + { + threads[i] = new Thread(() => + { + startBarrier.SignalAndWait(); + + for (int j = 0; j < IterationCount; j++) + { + Interlocked.MemoryBarrierProcessWide(); + } + }); + threads[i].Start(); + } + + foreach (Thread thread in threads) + { + thread.Join(); + } + }).Dispose(); + } + // 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 diff --git a/src/native/minipal/memorybarrierprocesswide.c b/src/native/minipal/memorybarrierprocesswide.c index 2ab74040c09256..1679f9cd59c7d9 100644 --- a/src/native/minipal/memorybarrierprocesswide.c +++ b/src/native/minipal/memorybarrierprocesswide.c @@ -17,6 +17,7 @@ #ifdef __APPLE__ #include #include +#include #define CHECK_MACH(_msg, machret) do { \ if (machret != KERN_SUCCESS) \ @@ -40,6 +41,9 @@ #include #endif +// Mutex to serialize implementations that require it +static pthread_mutex_t g_flushProcessWriteBuffersMutex; + #if HAVE_SYS_MEMBARRIER_H static bool CanFlushUsingMembarrier(void) { @@ -79,10 +83,9 @@ static bool s_flushUsingMemBarrier = false; // Helper memory page used by the fallback path static uint8_t* g_helperPage = NULL; -// Mutex to make the fallback path thread safe -static pthread_mutex_t g_flushProcessWriteBuffersMutex; - static size_t s_pageSize = 0; +#else +static bool s_serializeAppleMemoryBarrier = false; #endif // !HOST_APPLE #endif // !HOST_WASM @@ -99,6 +102,16 @@ bool minipal_initialize_memory_barrier_process_wide(void) // browser/wasm is currently single threaded #elif defined(HOST_APPLE) // Apple platforms do not support membarrier, so we use a different mechanism + if (minipal_detect_rosetta()) + { + int status = pthread_mutex_init(&g_flushProcessWriteBuffersMutex, NULL); + if (status != 0) + { + return false; + } + + s_serializeAppleMemoryBarrier = true; + } #else #if HAVE_SYS_MEMBARRIER_H if (CanFlushUsingMembarrier()) @@ -155,6 +168,14 @@ void minipal_memory_barrier_process_wide(void) #ifdef HOST_WASM // browser/wasm is currently single threaded #elif defined(HOST_APPLE) + int status; + if (s_serializeAppleMemoryBarrier) + { + status = pthread_mutex_lock(&g_flushProcessWriteBuffersMutex); + (void)status; // unused in release config + assert(status == 0 && "Failed to lock the flushProcessWriteBuffersMutex lock"); + } + mach_msg_type_number_t cThreads; thread_act_t *pThreads; kern_return_t machret = task_threads(mach_task_self(), &pThreads, &cThreads); @@ -181,6 +202,13 @@ void minipal_memory_barrier_process_wide(void) // Deallocate the thread list now we're done with it. machret = vm_deallocate(mach_task_self(), (vm_address_t)pThreads, cThreads * sizeof(thread_act_t)); CHECK_MACH("vm_deallocate()", machret); + + if (s_serializeAppleMemoryBarrier) + { + status = pthread_mutex_unlock(&g_flushProcessWriteBuffersMutex); + (void)status; // unused in release config + assert(status == 0 && "Failed to unlock the flushProcessWriteBuffersMutex lock"); + } #else // !HOST_APPLE && !HOST_WASM #if HAVE_SYS_MEMBARRIER_H if (s_flushUsingMemBarrier)