Skip to content
Open
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
36 changes: 36 additions & 0 deletions src/libraries/System.Threading/tests/InterlockedTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
34 changes: 31 additions & 3 deletions src/native/minipal/memorybarrierprocesswide.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#ifdef __APPLE__
#include <stdlib.h>
#include <mach/thread_state.h>
#include <minipal/cpufeatures.h>

#define CHECK_MACH(_msg, machret) do { \
if (machret != KERN_SUCCESS) \
Expand All @@ -40,6 +41,9 @@
#include <sys/membarrier.h>
#endif

// Mutex to serialize implementations that require it
static pthread_mutex_t g_flushProcessWriteBuffersMutex;

#if HAVE_SYS_MEMBARRIER_H
static bool CanFlushUsingMembarrier(void)
{
Expand Down Expand Up @@ -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

Expand All @@ -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())
Expand Down Expand Up @@ -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);
Expand All @@ -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)
Expand Down
Loading