From 922b960e7eaf26980729f7b258ddc4fcc93bdc70 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Mon, 21 Sep 2026 13:31:35 -0700 Subject: [PATCH 1/3] Serialize process-wide memory barriers under Rosetta Concurrent Mach thread state queries can deadlock when an x64 process runs under Rosetta. Serialize the Apple process-wide barrier only in translated processes and add a concurrent-call regression test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6de45ab0-f614-494b-9fcf-b38d01823145 --- .../tests/InterlockedTests.cs | 33 ++++++++++++++++++ src/native/minipal/memorybarrierprocesswide.c | 34 +++++++++++++++++-- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Threading/tests/InterlockedTests.cs b/src/libraries/System.Threading/tests/InterlockedTests.cs index cd394521158631..7b3460d64e3445 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,38 @@ public void MemoryBarrierProcessWide() Assert.Equal(1000*1000, count); } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + 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) From 1c71f1ad4ffa9c66f3982c55936c447127ee44d1 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 2 Oct 2026 14:38:58 -0700 Subject: [PATCH 2/3] Add conditional fact for threading support check Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/libraries/System.Threading/tests/InterlockedTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Threading/tests/InterlockedTests.cs b/src/libraries/System.Threading/tests/InterlockedTests.cs index 7b3460d64e3445..851f3038a3fd32 100644 --- a/src/libraries/System.Threading/tests/InterlockedTests.cs +++ b/src/libraries/System.Threading/tests/InterlockedTests.cs @@ -1493,7 +1493,10 @@ public void MemoryBarrierProcessWide() Assert.Equal(1000*1000, count); } - [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + private static bool IsThreadingAndRemoteExecutorSupported => + PlatformDetection.IsMultithreadingSupported && RemoteExecutor.IsSupported; + + [ConditionalFact(typeof(InterlockedTests), nameof(IsThreadingAndRemoteExecutorSupported))] public void MemoryBarrierProcessWide_ConcurrentCalls() { RemoteExecutor.Invoke(static () => From ed3bc08392c14faed28bbe367681b0c1bc698c43 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 9 Oct 2026 22:01:23 +0000 Subject: [PATCH 3/3] Invert ifdef for apple --- src/native/minipal/memorybarrierprocesswide.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/native/minipal/memorybarrierprocesswide.c b/src/native/minipal/memorybarrierprocesswide.c index 1679f9cd59c7d9..bcaa22e3ce5b91 100644 --- a/src/native/minipal/memorybarrierprocesswide.c +++ b/src/native/minipal/memorybarrierprocesswide.c @@ -79,14 +79,14 @@ static bool CanFlushUsingMembarrier(void) static bool s_flushUsingMemBarrier = false; #endif // HAVE_SYS_MEMBARRIER_H -#ifndef HOST_APPLE +#ifdef HOST_APPLE +static bool s_serializeAppleMemoryBarrier = false; +#else // Helper memory page used by the fallback path static uint8_t* g_helperPage = NULL; static size_t s_pageSize = 0; -#else -static bool s_serializeAppleMemoryBarrier = false; -#endif // !HOST_APPLE +#endif // HOST_APPLE #endif // !HOST_WASM static bool s_initializedMemoryBarrierSuccessfullyInitialized = false;