From 4d58f145de0f2ec2331fd87f10270da1718b0f8a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 16 Sep 2026 22:56:33 +0000 Subject: [PATCH 01/10] Fix concurrent Unix named mutex creation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Threading/NamedMutex.Unix.cs | 13 +++++- .../System.Threading/tests/MutexTests.cs | 44 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs index 077f68c63b23e2..01512bcfcf8044 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs @@ -101,7 +101,12 @@ internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader< public MutexTryAcquireLockResult TryAcquireLock(WaitSubsystem.ThreadWaitInfo waitInfo, int timeoutMilliseconds, ref WaitSubsystem.LockHolder holder) { SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); - holder.Dispose(); + bool keepCreationDeletionProcessLock = timeoutMilliseconds == 0; + if (!keepCreationDeletionProcessLock) + { + holder.Dispose(); + } + MutexTryAcquireLockResult result = AcquireLockCore(timeoutMilliseconds); if (result == MutexTryAcquireLockResult.AcquiredLockRecursively) @@ -115,7 +120,11 @@ public MutexTryAcquireLockResult TryAcquireLock(WaitSubsystem.ThreadWaitInfo wai return result; } - holder = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); + if (!keepCreationDeletionProcessLock) + { + holder = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); + } + SetLockOwnerToCurrentThread(); _lockCount = 1; _lockOwnerThread = waitInfo.Thread; diff --git a/src/libraries/System.Threading/tests/MutexTests.cs b/src/libraries/System.Threading/tests/MutexTests.cs index 3354ac0f587e1c..1f89bb62805245 100644 --- a/src/libraries/System.Threading/tests/MutexTests.cs +++ b/src/libraries/System.Threading/tests/MutexTests.cs @@ -912,6 +912,50 @@ public void CrossProcess_NamedMutex_ConcurrentCreateOrOpen(bool currentUserOnly, } } + [Fact] + [PlatformSpecific(TestPlatforms.AnyUnix)] + public async Task ConcurrentCreateOrOpenWithInitialOwnership() + { + const int IterationCount = 10_000; + string mutexName = Guid.NewGuid().ToString("N"); + int createdCount = 0; + using var barrier = new Barrier(2); + using var cancellationTokenSource = new CancellationTokenSource(); + + Task CreateOrOpenMutex() + { + return Task.Run( + () => + { + try + { + for (int i = 0; i < IterationCount; i++) + { + barrier.SignalAndWait(cancellationTokenSource.Token); + using (var mutex = new Mutex(initiallyOwned: true, mutexName, out bool createdNew)) + { + if (createdNew) + { + Interlocked.Increment(ref createdCount); + mutex.ReleaseMutex(); + } + } + + barrier.SignalAndWait(cancellationTokenSource.Token); + } + } + catch + { + cancellationTokenSource.Cancel(); + throw; + } + }); + } + + await Task.WhenAll(CreateOrOpenMutex(), CreateOrOpenMutex()); + Assert.True(createdCount > 0); + } + private static void IncrementValueInFileNTimes(Mutex mutex, string fileName, int n) { for (int i = 0; i < n; i++) From 260ea1cfe24654652979bfb1db9826fc516ccc99 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 17 Sep 2026 02:08:49 +0000 Subject: [PATCH 02/10] Guard named mutex test capabilities Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> --- src/libraries/System.Threading/tests/MutexTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Threading/tests/MutexTests.cs b/src/libraries/System.Threading/tests/MutexTests.cs index 1f89bb62805245..484f00f1ef5659 100644 --- a/src/libraries/System.Threading/tests/MutexTests.cs +++ b/src/libraries/System.Threading/tests/MutexTests.cs @@ -912,7 +912,7 @@ public void CrossProcess_NamedMutex_ConcurrentCreateOrOpen(bool currentUserOnly, } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMobile), nameof(PlatformDetection.IsMultithreadingSupported))] [PlatformSpecific(TestPlatforms.AnyUnix)] public async Task ConcurrentCreateOrOpenWithInitialOwnership() { From 3671d1c30edaa991e1566c6a87525f69062b3693 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 17 Sep 2026 14:57:07 -0700 Subject: [PATCH 03/10] Adjust test to actually ensure that the creating thread always captures the mutex. Preserve the assert as an exception-throw in release builds instead of a debug-only assert. --- .../src/System/Threading/NamedMutex.Unix.cs | 5 ++++- src/libraries/System.Threading/tests/MutexTests.cs | 13 +++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs index 01512bcfcf8044..fec38c484b92d5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs @@ -258,7 +258,10 @@ public void Abandon(NamedMutexOwnershipChain chain, Thread abandonedThread) if (created && acquireLockIfCreated) { MutexTryAcquireLockResult acquireResult = processDataHeader._processData.TryAcquireLock(Thread.CurrentThread.WaitInfo, timeoutMilliseconds: 0, ref creationDeletionProcessLock); - Debug.Assert(acquireResult == MutexTryAcquireLockResult.AcquiredLock); + if (acquireResult != MutexTryAcquireLockResult.AcquiredLock) + { + throw new InvalidOperationException(); + } } } diff --git a/src/libraries/System.Threading/tests/MutexTests.cs b/src/libraries/System.Threading/tests/MutexTests.cs index 484f00f1ef5659..b96dd1ad763827 100644 --- a/src/libraries/System.Threading/tests/MutexTests.cs +++ b/src/libraries/System.Threading/tests/MutexTests.cs @@ -914,11 +914,10 @@ public void CrossProcess_NamedMutex_ConcurrentCreateOrOpen(bool currentUserOnly, [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMobile), nameof(PlatformDetection.IsMultithreadingSupported))] [PlatformSpecific(TestPlatforms.AnyUnix)] - public async Task ConcurrentCreateOrOpenWithInitialOwnership() + public async Task NamedMutex_ConcurrentCreateOrOpen_CreatingThreadGetsInitialOwnership() { const int IterationCount = 10_000; string mutexName = Guid.NewGuid().ToString("N"); - int createdCount = 0; using var barrier = new Barrier(2); using var cancellationTokenSource = new CancellationTokenSource(); @@ -936,12 +935,15 @@ Task CreateOrOpenMutex() { if (createdNew) { - Interlocked.Increment(ref createdCount); + barrier.SignalAndWait(cancellationTokenSource.Token); mutex.ReleaseMutex(); } + else + { + Assert.False(mutex.WaitOne(0)); + barrier.SignalAndWait(cancellationTokenSource.Token); + } } - - barrier.SignalAndWait(cancellationTokenSource.Token); } } catch @@ -953,7 +955,6 @@ Task CreateOrOpenMutex() } await Task.WhenAll(CreateOrOpenMutex(), CreateOrOpenMutex()); - Assert.True(createdCount > 0); } private static void IncrementValueInFileNTimes(Mutex mutex, string fileName, int n) From 4e2506f8cca6623f2a0d9db0db8b28f0e78eb6db Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 22 Sep 2026 21:25:13 +0000 Subject: [PATCH 04/10] Use explicit parameter instead of inferring lock-hold behavior from timeout Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System/Threading/NamedMutex.Unix.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs index fec38c484b92d5..f8c46fada790ac 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs @@ -98,11 +98,10 @@ internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader< protected abstract void SetLockOwnerToCurrentThread(); - public MutexTryAcquireLockResult TryAcquireLock(WaitSubsystem.ThreadWaitInfo waitInfo, int timeoutMilliseconds, ref WaitSubsystem.LockHolder holder) + public MutexTryAcquireLockResult TryAcquireLock(WaitSubsystem.ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool keepCreationDeletionProcessLockHeld, ref WaitSubsystem.LockHolder holder) { SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); - bool keepCreationDeletionProcessLock = timeoutMilliseconds == 0; - if (!keepCreationDeletionProcessLock) + if (!keepCreationDeletionProcessLockHeld) { holder.Dispose(); } @@ -120,7 +119,7 @@ public MutexTryAcquireLockResult TryAcquireLock(WaitSubsystem.ThreadWaitInfo wai return result; } - if (!keepCreationDeletionProcessLock) + if (!keepCreationDeletionProcessLockHeld) { holder = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); } @@ -257,7 +256,7 @@ public void Abandon(NamedMutexOwnershipChain chain, Thread abandonedThread) if (created && acquireLockIfCreated) { - MutexTryAcquireLockResult acquireResult = processDataHeader._processData.TryAcquireLock(Thread.CurrentThread.WaitInfo, timeoutMilliseconds: 0, ref creationDeletionProcessLock); + MutexTryAcquireLockResult acquireResult = processDataHeader._processData.TryAcquireLock(Thread.CurrentThread.WaitInfo, timeoutMilliseconds: 0, keepCreationDeletionProcessLockHeld: true, ref creationDeletionProcessLock); if (acquireResult != MutexTryAcquireLockResult.AcquiredLock) { throw new InvalidOperationException(); @@ -665,7 +664,7 @@ public int Wait_Locked(ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool in LockHolder scope = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); try { - MutexTryAcquireLockResult result = _processDataHeader._processData!.TryAcquireLock(waitInfo, timeoutMilliseconds, ref scope); + MutexTryAcquireLockResult result = _processDataHeader._processData!.TryAcquireLock(waitInfo, timeoutMilliseconds, keepCreationDeletionProcessLockHeld: false, ref scope); return result switch { MutexTryAcquireLockResult.AcquiredLock => WaitHandle.WaitSuccess, From 5ccc9ef48519be151ea3eb5806913a1ccefe5755 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 22 Sep 2026 22:55:21 +0000 Subject: [PATCH 05/10] Refactor away inverted lock holder usage and use a wrapper struct to enforce correct usage to get the final lock result. --- .../src/System/IO/SharedMemoryManager.Unix.cs | 5 ++ .../src/System/Threading/NamedMutex.Unix.cs | 87 ++++++++++--------- 2 files changed, 53 insertions(+), 39 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs index f33075278d6648..d5c94c045da2b8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs @@ -810,6 +810,11 @@ public void VerifyCreationDeletionProcessLockIsLocked() _creationDeletionProcessLock.VerifyIsLocked(); } + public void VerifyCreationDeletionProcessLockIsNotLocked() + { + _creationDeletionProcessLock.VerifyIsNotLocked(); + } + public AutoReleaseFileLock AcquireCreationDeletionLockForId(SharedMemoryId id) { _creationDeletionProcessLock.VerifyIsLocked(); diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs index f8c46fada790ac..92065b17a6c9cf 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs @@ -98,46 +98,17 @@ internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader< protected abstract void SetLockOwnerToCurrentThread(); - public MutexTryAcquireLockResult TryAcquireLock(WaitSubsystem.ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool keepCreationDeletionProcessLockHeld, ref WaitSubsystem.LockHolder holder) + public UnrecordedMutexAcquisition TryAcquireLock(int timeoutMilliseconds) { - SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); - if (!keepCreationDeletionProcessLockHeld) - { - holder.Dispose(); - } - - MutexTryAcquireLockResult result = AcquireLockCore(timeoutMilliseconds); - - if (result == MutexTryAcquireLockResult.AcquiredLockRecursively) - { - return MutexTryAcquireLockResult.AcquiredLock; - } - - if (result == MutexTryAcquireLockResult.TimedOut) + if (timeoutMilliseconds != 0) { - // If the lock was not acquired, we don't have any more work to do. - return result; - } - - if (!keepCreationDeletionProcessLockHeld) - { - holder = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); - } - - SetLockOwnerToCurrentThread(); - _lockCount = 1; - _lockOwnerThread = waitInfo.Thread; - // Add the ref count for the thread's wait info. - _processDataHeader.IncrementRefCount(); - waitInfo.NamedMutexOwnershipChain.Add(this); - - if (IsAbandoned) - { - IsAbandoned = false; - result = MutexTryAcquireLockResult.AcquiredLockButMutexWasAbandoned; + // We can't have the creation/deletion process lock held while trying to acquire a lock with a non-zero timeout + // as this can lead to a deadlock or livelock situation between multiple different cross-process mutexes + // used across multiple threads within the same process and across processes. + SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsNotLocked(); } - return result; + return new UnrecordedMutexAcquisition(this, AcquireLockCore(timeoutMilliseconds)); } public void ReleaseLock() @@ -256,10 +227,12 @@ public void Abandon(NamedMutexOwnershipChain chain, Thread abandonedThread) if (created && acquireLockIfCreated) { - MutexTryAcquireLockResult acquireResult = processDataHeader._processData.TryAcquireLock(Thread.CurrentThread.WaitInfo, timeoutMilliseconds: 0, keepCreationDeletionProcessLockHeld: true, ref creationDeletionProcessLock); + NamedMutexProcessDataBase processData = processDataHeader._processData; + MutexTryAcquireLockResult acquireResult = processDataHeader._processData.TryAcquireLock(timeoutMilliseconds: 0).RecordMutexAcquisition(Thread.CurrentThread.WaitInfo); if (acquireResult != MutexTryAcquireLockResult.AcquiredLock) { - throw new InvalidOperationException(); + // We created the mutex so we should be able to acquire it immediately. + throw new ApplicationException(); } } } @@ -300,6 +273,39 @@ public virtual void Close(bool releaseSharedData) Thread.CurrentThread.WaitInfo.NamedMutexOwnershipChain.Remove(this); } } + + public struct UnrecordedMutexAcquisition(NamedMutexProcessDataBase mutexData, MutexTryAcquireLockResult rawAcquireResult) + { + public MutexTryAcquireLockResult RecordMutexAcquisition(WaitSubsystem.ThreadWaitInfo waitInfo) + { + if (rawAcquireResult == MutexTryAcquireLockResult.AcquiredLockRecursively) + { + return MutexTryAcquireLockResult.AcquiredLock; + } + + if (rawAcquireResult == MutexTryAcquireLockResult.TimedOut) + { + return MutexTryAcquireLockResult.TimedOut; + } + + SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); + + mutexData.SetLockOwnerToCurrentThread(); + mutexData._lockCount = 1; + mutexData._lockOwnerThread = waitInfo.Thread; + // Add the ref count for the thread's wait info. + mutexData._processDataHeader.IncrementRefCount(); + waitInfo.NamedMutexOwnershipChain.Add(mutexData); + + if (mutexData.IsAbandoned) + { + mutexData.IsAbandoned = false; + return MutexTryAcquireLockResult.AcquiredLockButMutexWasAbandoned; + } + + return MutexTryAcquireLockResult.AcquiredLock; + } + } } internal sealed unsafe class NamedMutexProcessDataWithPThreads(SharedMemoryProcessDataHeader processDataHeader) : NamedMutexProcessDataBase(processDataHeader) @@ -661,10 +667,13 @@ public NamedMutex(SharedMemoryProcessDataHeader proce public int Wait_Locked(ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool interruptible, ref LockHolder lockHolder) { lockHolder.Dispose(); + NamedMutexProcessDataBase processData = _processDataHeader._processData!; + NamedMutexProcessDataBase.UnrecordedMutexAcquisition acquisition = processData.TryAcquireLock(timeoutMilliseconds); LockHolder scope = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); try { - MutexTryAcquireLockResult result = _processDataHeader._processData!.TryAcquireLock(waitInfo, timeoutMilliseconds, keepCreationDeletionProcessLockHeld: false, ref scope); + MutexTryAcquireLockResult result = acquisition.RecordMutexAcquisition(waitInfo); + return result switch { MutexTryAcquireLockResult.AcquiredLock => WaitHandle.WaitSuccess, From 4f7da3ff38fffcf195eaab47a53f1634aa5f6487 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 23 Sep 2026 18:56:41 +0000 Subject: [PATCH 06/10] Add synchronization point that the previous implementation had --- .../src/System/Threading/NamedMutex.Unix.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs index 92065b17a6c9cf..5e92ab33b0afa5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs @@ -667,8 +667,12 @@ public NamedMutex(SharedMemoryProcessDataHeader proce public int Wait_Locked(ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool interruptible, ref LockHolder lockHolder) { lockHolder.Dispose(); - NamedMutexProcessDataBase processData = _processDataHeader._processData!; - NamedMutexProcessDataBase.UnrecordedMutexAcquisition acquisition = processData.TryAcquireLock(timeoutMilliseconds); + + // Synchronize on with any other threads currently handling creation or deletion of named mutexes. + // It's possible that a thread is currently abandoning a mutex on the finalizer thread while we are trying to acquire it. + SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock().Dispose(); + + NamedMutexProcessDataBase.UnrecordedMutexAcquisition acquisition = _processDataHeader._processData!.TryAcquireLock(timeoutMilliseconds); LockHolder scope = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); try { From 0d306cf6eccaf361bf6ebd3d14b24f2b3dbe522b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 23 Sep 2026 14:09:18 -0700 Subject: [PATCH 07/10] Wrap in if MONO --- .../src/System/Threading/NamedMutex.Unix.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs index 5e92ab33b0afa5..b42c46105532f2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs @@ -668,9 +668,13 @@ public int Wait_Locked(ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool in { lockHolder.Dispose(); - // Synchronize on with any other threads currently handling creation or deletion of named mutexes. +#if MONO + // Synchronize with any other threads currently handling creation or deletion of named mutexes. // It's possible that a thread is currently abandoning a mutex on the finalizer thread while we are trying to acquire it. + // Mono doesn't use the shared Thread.Join implementation that ensures we have this handled in the right order, + // so we synchronize here. SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock().Dispose(); +#endif NamedMutexProcessDataBase.UnrecordedMutexAcquisition acquisition = _processDataHeader._processData!.TryAcquireLock(timeoutMilliseconds); LockHolder scope = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); From 8c9d8a7bbcf0ebf1a178e91a6d75676c422b487e Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 23 Sep 2026 21:39:32 +0000 Subject: [PATCH 08/10] Disable test on Mono --- .../src/System/Threading/NamedMutex.Unix.cs | 8 -------- src/libraries/System.Threading/tests/MutexTests.cs | 1 + 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs index b42c46105532f2..a4ea162d5102fe 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs @@ -668,14 +668,6 @@ public int Wait_Locked(ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool in { lockHolder.Dispose(); -#if MONO - // Synchronize with any other threads currently handling creation or deletion of named mutexes. - // It's possible that a thread is currently abandoning a mutex on the finalizer thread while we are trying to acquire it. - // Mono doesn't use the shared Thread.Join implementation that ensures we have this handled in the right order, - // so we synchronize here. - SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock().Dispose(); -#endif - NamedMutexProcessDataBase.UnrecordedMutexAcquisition acquisition = _processDataHeader._processData!.TryAcquireLock(timeoutMilliseconds); LockHolder scope = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); try diff --git a/src/libraries/System.Threading/tests/MutexTests.cs b/src/libraries/System.Threading/tests/MutexTests.cs index b96dd1ad763827..6fe4dcae85cfc4 100644 --- a/src/libraries/System.Threading/tests/MutexTests.cs +++ b/src/libraries/System.Threading/tests/MutexTests.cs @@ -598,6 +598,7 @@ public static IEnumerable AbandonExisting_MemberData() [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsMultithreadingSupported))] [MemberData(nameof(AbandonExisting_MemberData))] [ActiveIssue("https://github.com/dotnet/runtime/issues/117760",platforms: TestPlatforms.Android, runtimes: TestRuntimes.CoreCLR)] + [SkipOnMono("Mono is not officially supported on multithreading platforms and this test hits bad interactions with Mono's custom Thread.Join implementation.")] public void AbandonExisting( string name, WaitHandleWaitType waitType, From d8d7a8b2be99a981a2ec19d03723a04c3d4664c2 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 23 Sep 2026 14:50:43 -0700 Subject: [PATCH 09/10] Return raw acquire result in NamedMutex --- .../src/System/Threading/NamedMutex.Unix.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs index a4ea162d5102fe..12bece4c8e13cd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs @@ -303,7 +303,7 @@ public MutexTryAcquireLockResult RecordMutexAcquisition(WaitSubsystem.ThreadWait return MutexTryAcquireLockResult.AcquiredLockButMutexWasAbandoned; } - return MutexTryAcquireLockResult.AcquiredLock; + return rawAcquireResult; } } } From 4a6cef14e89b418bfac0d9a552e139dcab7f95b8 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 23 Sep 2026 22:21:52 +0000 Subject: [PATCH 10/10] Don't skip on mono now that we fixed "native abandonment is not recognized" --- src/libraries/System.Threading/tests/MutexTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Threading/tests/MutexTests.cs b/src/libraries/System.Threading/tests/MutexTests.cs index 6fe4dcae85cfc4..b96dd1ad763827 100644 --- a/src/libraries/System.Threading/tests/MutexTests.cs +++ b/src/libraries/System.Threading/tests/MutexTests.cs @@ -598,7 +598,6 @@ public static IEnumerable AbandonExisting_MemberData() [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsMultithreadingSupported))] [MemberData(nameof(AbandonExisting_MemberData))] [ActiveIssue("https://github.com/dotnet/runtime/issues/117760",platforms: TestPlatforms.Android, runtimes: TestRuntimes.CoreCLR)] - [SkipOnMono("Mono is not officially supported on multithreading platforms and this test hits bad interactions with Mono's custom Thread.Join implementation.")] public void AbandonExisting( string name, WaitHandleWaitType waitType,