File: src\libraries\System.Private.CoreLib\src\System\Threading\NamedMutex.Unix.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
 
namespace System.Threading
{
    internal sealed class NamedMutexOwnershipChain(Thread thread)
    {
        private readonly Thread _thread = thread;
        private NamedMutexProcessDataBase? _head;
 
        public void Add(NamedMutexProcessDataBase namedMutex)
        {
            SharedMemoryManager<NamedMutexProcessDataBase>.Instance.VerifyCreationDeletionProcessLockIsLocked();
            namedMutex.NextOwnedNamedMutex = _head;
            _head = namedMutex;
        }
 
        public void Remove(NamedMutexProcessDataBase namedMutex)
        {
            SharedMemoryManager<NamedMutexProcessDataBase>.Instance.VerifyCreationDeletionProcessLockIsLocked();
            if (_head == namedMutex)
            {
                _head = namedMutex.NextOwnedNamedMutex;
            }
            else
            {
                NamedMutexProcessDataBase? previous = _head;
                while (previous?.NextOwnedNamedMutex != namedMutex)
                {
                    previous = previous?.NextOwnedNamedMutex;
                }
 
                previous?.NextOwnedNamedMutex = namedMutex.NextOwnedNamedMutex;
            }
 
            namedMutex.NextOwnedNamedMutex = null;
        }
 
        public void Abandon()
        {
            WaitSubsystem.LockHolder scope = SharedMemoryManager<NamedMutexProcessDataBase>.Instance.AcquireCreationDeletionProcessLock();
            try
            {
                while (true)
                {
                    NamedMutexProcessDataBase? namedMutex = _head;
                    if (namedMutex == null)
                    {
                        break;
                    }
 
                    namedMutex.Abandon(this, _thread);
                    Debug.Assert(_head != namedMutex);
                }
            }
            finally
            {
                scope.Dispose();
            }
        }
    }
 
    internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader<NamedMutexProcessDataBase> header) : ISharedMemoryProcessData
    {
        private const byte SyncSystemVersion = 1;
        protected const int PollLoopMaximumSleepMilliseconds = 100;
        protected const uint InvalidProcessId = unchecked((uint)-1);
        protected const uint InvalidThreadId = unchecked((uint)-1);
 
        // Use PThread mutex-backed named mutexes if possible.
        // macOS has support for the features we need in the pthread mutexes on arm64
        // but not in the Rosetta 2 x64 emulation layer.
        // Until Rosetta 2 is removed, we need to use the non-PThread mutexes on Apple platforms.
        // On FreeBSD, pthread process-shared robust mutexes cannot be placed in shared memory mapped
        // independently by the processes involved. See https://github.com/dotnet/runtime/issues/10519.
        private static bool UsePThreadMutexes => !OperatingSystem.IsApplePlatform() && !OperatingSystem.IsFreeBSD();
 
        private readonly SharedMemoryProcessDataHeader<NamedMutexProcessDataBase> _processDataHeader = header;
        protected nuint _lockCount;
        private Thread? _lockOwnerThread;
 
        public SharedMemoryId Id => _processDataHeader._id;
 
        public NamedMutexProcessDataBase? NextOwnedNamedMutex { get; set; }
 
        public bool IsLockOwnedByCurrentThread => IsLockOwnedByThreadInThisProcess(Thread.CurrentThread);
        protected abstract bool IsLockOwnedByThreadInThisProcess(Thread thread);
        public bool IsLockOwnedByAnyThreadInThisProcess => _lockOwnerThread is not null;
 
        protected abstract void SetLockOwnerToCurrentThread();
 
        public MutexTryAcquireLockResult TryAcquireLock(WaitSubsystem.ThreadWaitInfo waitInfo, int timeoutMilliseconds, ref WaitSubsystem.LockHolder holder)
        {
            SharedMemoryManager<NamedMutexProcessDataBase>.Instance.VerifyCreationDeletionProcessLockIsLocked();
            holder.Dispose();
            MutexTryAcquireLockResult result = AcquireLockCore(timeoutMilliseconds);
 
            if (result == MutexTryAcquireLockResult.AcquiredLockRecursively)
            {
                return MutexTryAcquireLockResult.AcquiredLock;
            }
 
            if (result == MutexTryAcquireLockResult.TimedOut)
            {
                // If the lock was not acquired, we don't have any more work to do.
                return result;
            }
 
            holder = SharedMemoryManager<NamedMutexProcessDataBase>.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;
            }
 
            return result;
        }
 
        public void ReleaseLock()
        {
            if (!IsLockOwnedByCurrentThread)
            {
                throw new InvalidOperationException(SR.Arg_InvalidOperationException_CannotReleaseUnownedMutex);
            }
 
            --_lockCount;
            if (_lockCount != 0)
            {
                return;
            }
 
            WaitSubsystem.LockHolder scope = SharedMemoryManager<NamedMutexProcessDataBase>.Instance.AcquireCreationDeletionProcessLock();
            try
            {
                Thread.CurrentThread.WaitInfo.NamedMutexOwnershipChain.Remove(this);
                _lockOwnerThread = null;
                ReleaseLockCore(abandoning: false);
                // Remove the refcount from the thread's wait info.
                _processDataHeader.DecrementRefCount();
            }
            finally
            {
                scope.Dispose();
            }
        }
 
        public void Abandon(NamedMutexOwnershipChain chain, Thread abandonedThread)
        {
            SharedMemoryManager<NamedMutexProcessDataBase>.Instance.VerifyCreationDeletionProcessLockIsLocked();
 
            // This method can be called from one of two threads:
            // 1. The dead thread that owns the mutex.
            //   In the first case, we know that no one else can acquire the lock, so we can safely
            //   set the lock count to 0 and abandon the mutex.
            // 2. The finalizer thread after the owning thread has died.
            //   In this case, its possible for the mutex to have been acquired by another thread
            //   after the owning thread died if it is backed by a pthread mutex.
            //   A pthread mutex doesn't need our Abandon() call to be abandoned, it will be automatically abandoned
            //   when the owning thread dies.
            //   In this case, we don't want to do anything. Our named mutex implementation will handle the abandonment
            //   as part of acquisition.
            Debug.Assert(IsLockOwnedByCurrentThread || (!abandonedThread.IsAlive && Thread.CurrentThreadIsFinalizerThread()),
                "Abandon can only be called from the thread that owns the lock or from the finalizer thread when the owning thread is dead.");
 
            if (!IsLockOwnedByThreadInThisProcess(abandonedThread))
            {
                Debug.Assert(Thread.CurrentThreadIsFinalizerThread());
                // Lock is owned by a different thread already, so we don't need to do anything.
                // Just remove it from the list of owned named mutexes.
                chain.Remove(this);
                return;
            }
 
            IsAbandoned = true;
 
            _lockCount = 0;
 
            Debug.Assert(_lockOwnerThread is not null);
 
            ReleaseLockCore(abandoning: true);
 
            chain.Remove(this);
            _lockOwnerThread = null;
            // Remove the refcount from the thread's wait info.
            _processDataHeader.DecrementRefCount();
        }
 
        protected abstract MutexTryAcquireLockResult AcquireLockCore(int timeoutMilliseconds);
 
        protected abstract bool IsAbandoned { get; set; }
 
        protected abstract void ReleaseLockCore(bool abandoning);
 
        internal static unsafe SharedMemoryProcessDataHeader<NamedMutexProcessDataBase>? CreateOrOpen(string name, bool isUserScope, bool createIfNotExist, bool acquireLockIfCreated, out bool created)
        {
            WaitSubsystem.LockHolder creationDeletionProcessLock = SharedMemoryManager<NamedMutexProcessDataBase>.Instance.AcquireCreationDeletionProcessLock();
            try
            {
 
                SharedMemoryProcessDataHeader<NamedMutexProcessDataBase>? processDataHeader = SharedMemoryProcessDataHeader<NamedMutexProcessDataBase>.CreateOrOpen(
                    name,
                    isUserScope,
                    new SharedMemorySharedDataHeader(SharedMemoryType.Mutex, SyncSystemVersion),
                    UsePThreadMutexes ? NamedMutexProcessDataWithPThreads.SharedDataSize : (nuint)sizeof(NamedMutexProcessDataNoPThreads.SharedData),
                    createIfNotExist,
                    acquireLockIfCreated,
                    out created,
                    out AutoReleaseFileLock creationDeletionLockFileScope);
 
                if (processDataHeader is null)
                {
                    return null;
                }
 
                using (creationDeletionLockFileScope)
                {
                    if (created)
                    {
                        InitializeSharedData(SharedMemoryProcessDataHeader<NamedMutexProcessDataBase>.GetDataPointer(processDataHeader));
                    }
 
                    if (processDataHeader._processData is null)
                    {
                        if (UsePThreadMutexes)
                        {
                            processDataHeader._processData = new NamedMutexProcessDataWithPThreads(processDataHeader);
                        }
                        else
                        {
                            processDataHeader._processData = new NamedMutexProcessDataNoPThreads(processDataHeader, created);
                        }
 
                        if (created && acquireLockIfCreated)
                        {
                            MutexTryAcquireLockResult acquireResult = processDataHeader._processData.TryAcquireLock(Thread.CurrentThread.WaitInfo, timeoutMilliseconds: 0, ref creationDeletionProcessLock);
                            Debug.Assert(acquireResult == MutexTryAcquireLockResult.AcquiredLock);
                        }
                    }
 
                    return processDataHeader;
                }
            }
            finally
            {
                creationDeletionProcessLock.Dispose();
            }
        }
 
        private static unsafe void InitializeSharedData(void* v)
        {
            if (UsePThreadMutexes)
            {
                if (Interop.Sys.LowLevelCrossProcessMutex_Init(v) != 0)
                {
                    Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
                    throw Interop.GetExceptionForIoErrno(errorInfo, SR.Arg_FailedToInitializePThreadMutex);
                }
            }
            else
            {
                NamedMutexProcessDataNoPThreads.SharedData* sharedData = (NamedMutexProcessDataNoPThreads.SharedData*)v;
                sharedData->LockOwnerProcessId = InvalidProcessId;
                sharedData->LockOwnerThreadId = InvalidThreadId;
                sharedData->IsAbandoned = false;
                sharedData->TimedWaiterCount = 0;
            }
        }
 
        public virtual void Close(bool releaseSharedData)
        {
            if (IsLockOwnedByCurrentThread)
            {
                Thread.CurrentThread.WaitInfo.NamedMutexOwnershipChain.Remove(this);
            }
        }
    }
 
    internal sealed unsafe class NamedMutexProcessDataWithPThreads(SharedMemoryProcessDataHeader<NamedMutexProcessDataBase> processDataHeader) : NamedMutexProcessDataBase(processDataHeader)
    {
        public static nuint SharedDataSize { get; } = (nuint)Interop.Sys.LowLevelCrossProcessMutex_Size();
        private readonly void* _sharedData = SharedMemoryProcessDataHeader<NamedMutexProcessDataBase>.GetDataPointer(processDataHeader);
 
        protected override bool IsLockOwnedByThreadInThisProcess(Thread thread)
        {
            Interop.Sys.LowLevelCrossProcessMutex_GetOwnerProcessAndThreadId(_sharedData, out uint ownerProcessId, out uint ownerThreadId);
            return ownerProcessId == (uint)Environment.ProcessId &&
                   ownerThreadId == (uint)thread.ManagedThreadId;
        }
 
        protected override void SetLockOwnerToCurrentThread()
        {
            Interop.Sys.LowLevelCrossProcessMutex_SetOwnerProcessAndThreadId(
                _sharedData,
                (uint)Environment.ProcessId,
                (uint)Thread.CurrentThread.ManagedThreadId);
        }
 
        protected override bool IsAbandoned
        {
            get => Interop.Sys.LowLevelCrossProcessMutex_IsAbandoned(_sharedData);
            set => Interop.Sys.LowLevelCrossProcessMutex_SetAbandoned(_sharedData, value);
        }
 
        protected override MutexTryAcquireLockResult AcquireLockCore(int timeoutMilliseconds)
        {
            Interop.Error lockResult = (Interop.Error)Interop.Sys.LowLevelCrossProcessMutex_Acquire(_sharedData, timeoutMilliseconds);
 
            MutexTryAcquireLockResult result = lockResult switch
            {
                Interop.Error.SUCCESS => MutexTryAcquireLockResult.AcquiredLock,
                Interop.Error.EBUSY => MutexTryAcquireLockResult.TimedOut,
                Interop.Error.ETIMEDOUT => MutexTryAcquireLockResult.TimedOut,
                Interop.Error.EOWNERDEAD => MutexTryAcquireLockResult.AcquiredLockButMutexWasAbandoned,
                Interop.Error.EAGAIN => throw new OutOfMemoryException(),
                _ => throw new Win32Exception((int)lockResult)
            };
 
            if (result == MutexTryAcquireLockResult.TimedOut)
            {
                return MutexTryAcquireLockResult.TimedOut;
            }
 
            if (result == MutexTryAcquireLockResult.AcquiredLockButMutexWasAbandoned)
            {
                // If the underlying pthread mutex was abandoned, treat this as an abandoned mutex.
                // Its possible that the underlying pthread mutex was abandoned, but the shared data and process data
                // is out of sync. This can happen if the dead thread did not call Abandon() before it exited
                // and is relying on the finalizer to clean it up.
                return result;
            }
 
            if (_lockCount != 0)
            {
                Debug.Assert(IsLockOwnedByCurrentThread);
                Debug.Assert(!IsAbandoned);
                try
                {
                    checked
                    {
                        _lockCount++;
                    }
                    return MutexTryAcquireLockResult.AcquiredLockRecursively;
                }
                finally
                {
                    // The lock is released upon acquiring a recursive lock from the thread that already owns the lock
                    Interop.Sys.LowLevelCrossProcessMutex_Release(_sharedData);
                }
            }
 
            return result;
        }
 
        protected override void ReleaseLockCore(bool abandoning)
        {
            Debug.Assert(_lockCount == 0);
            Interop.Sys.LowLevelCrossProcessMutex_SetOwnerProcessAndThreadId(
                _sharedData,
                InvalidProcessId,
                InvalidThreadId);
 
            Interop.Sys.LowLevelCrossProcessMutex_Release(_sharedData);
        }
 
        public override void Close(bool releaseSharedData)
        {
            base.Close(releaseSharedData);
 
            if (releaseSharedData)
            {
                // Release the pthread mutex.
                Interop.Sys.LowLevelCrossProcessMutex_Destroy(_sharedData);
            }
        }
    }
 
    internal sealed unsafe class NamedMutexProcessDataNoPThreads : NamedMutexProcessDataBase
    {
        private const string SharedMemoryLockFilesDirectoryName = "lockfiles";
        private readonly Mutex _processLevelMutex = new Mutex(initiallyOwned: false);
        private readonly SafeFileHandle _sharedLockFileHandle;
 
        private readonly SharedData* _sharedData;
 
        public NamedMutexProcessDataNoPThreads(SharedMemoryProcessDataHeader<NamedMutexProcessDataBase> processDataHeader, bool created) : base(processDataHeader)
        {
            _sharedData = (SharedData*)SharedMemoryProcessDataHeader<NamedMutexProcessDataBase>.GetDataPointer(processDataHeader);
            string lockFileDirectory = Path.Combine(
                SharedMemoryHelpers.SharedFilesPath,
                Id.GetRuntimeTempDirectoryName(),
                SharedMemoryLockFilesDirectoryName
            );
 
            if (created)
            {
                SharedMemoryHelpers.EnsureDirectoryExists(lockFileDirectory, Id, isGlobalLockAcquired: true);
            }
 
            string sessionDirectory = Path.Combine(lockFileDirectory, Id.GetSessionDirectoryName());
 
            if (created)
            {
                SharedMemoryHelpers.EnsureDirectoryExists(sessionDirectory, Id, isGlobalLockAcquired: true);
            }
 
            string lockFilePath = Path.Combine(sessionDirectory, Id.Name);
            _sharedLockFileHandle = SharedMemoryHelpers.CreateOrOpenFile(lockFilePath, Id, created, out _);
        }
 
        protected override bool IsLockOwnedByThreadInThisProcess(Thread thread)
        {
            return _sharedData->LockOwnerProcessId == (uint)Environment.ProcessId &&
                   _sharedData->LockOwnerThreadId == (uint)thread.ManagedThreadId;
        }
 
        protected override void SetLockOwnerToCurrentThread()
        {
            _sharedData->LockOwnerProcessId = (uint)Environment.ProcessId;
            _sharedData->LockOwnerThreadId = (uint)Thread.CurrentThread.ManagedThreadId;
        }
 
        private bool IsLockOwnedByAnyThread
        {
            get
            {
                return _sharedData->LockOwnerProcessId != InvalidProcessId &&
                       _sharedData->LockOwnerThreadId != InvalidThreadId;
            }
        }
 
        protected override void ReleaseLockCore(bool abandoning)
        {
            Debug.Assert(_lockCount == 0);
            _sharedData->LockOwnerProcessId = InvalidProcessId;
            _sharedData->LockOwnerThreadId = InvalidThreadId;
 
            Interop.Sys.FLock(_sharedLockFileHandle, Interop.Sys.LockOperations.LOCK_UN);
            if (!abandoning)
            {
                // We're going to abandon this mutex, so don't release it.
                // We might not be on the owning thread, so we can't be sure we can release it.
                _processLevelMutex.ReleaseMutex();
            }
        }
 
        protected override bool IsAbandoned
        {
            get => _sharedData->IsAbandoned;
            set => _sharedData->IsAbandoned = value;
        }
 
        public override void Close(bool releaseSharedData)
        {
            base.Close(releaseSharedData);
 
            _sharedLockFileHandle.Dispose();
            _processLevelMutex.Dispose();
        }
 
        protected override unsafe MutexTryAcquireLockResult AcquireLockCore(int timeoutMilliseconds)
        {
            int startTime = 0;
            if (timeoutMilliseconds > 0)
            {
                startTime = Environment.TickCount;
            }
 
            // Acquire the process lock. A file lock can only be acquired once per file descriptor, so to synchronize the threads of
            // this process, the process lock is used.
            bool releaseProcessLock = true;
            try
            {
                if (!_processLevelMutex.WaitOne(timeoutMilliseconds))
                {
                    return MutexTryAcquireLockResult.TimedOut;
                }
            }
            catch (AbandonedMutexException)
            {
                // If the process-level lock was abandoned, the shared mutex will also have been abandoned.
                // We don't need to do anything here. We'll acquire the shared lock below and throw for abandonment as expected.
            }
 
            try
            {
                if (_lockCount > 0)
                {
                    Debug.Assert(IsLockOwnedByCurrentThread);
                    // The lock is already owned by the current thread.
                    checked
                    {
                        _lockCount++;
                    }
                    return MutexTryAcquireLockResult.AcquiredLockRecursively;
                }
 
                switch (timeoutMilliseconds)
                {
                    case -1:
                        bool acquiredLock = false;
                        while (_sharedData->TimedWaiterCount > 0)
                        {
                            if (SharedMemoryHelpers.TryAcquireFileLock(_sharedLockFileHandle, nonBlocking: true))
                            {
                                acquiredLock = true;
                                break;
                            }
                            Thread.Sleep(PollLoopMaximumSleepMilliseconds);
                        }
 
                        if (acquiredLock)
                        {
                            break;
                        }
 
                        acquiredLock = SharedMemoryHelpers.TryAcquireFileLock(_sharedLockFileHandle, nonBlocking: false);
                        Debug.Assert(acquiredLock);
                        break;
                    case 0:
                        if (!SharedMemoryHelpers.TryAcquireFileLock(_sharedLockFileHandle, nonBlocking: true))
                        {
                            return MutexTryAcquireLockResult.TimedOut;
                        }
                        break;
                    default:
                    {
                        if (SharedMemoryHelpers.TryAcquireFileLock(_sharedLockFileHandle, nonBlocking: true))
                        {
                            break;
                        }
 
                        _sharedData->TimedWaiterCount++;
 
                        do
                        {
                            int elapsedMilliseconds = Environment.TickCount - startTime;
                            if (elapsedMilliseconds >= timeoutMilliseconds)
                            {
                                _sharedData->TimedWaiterCount--;
                                return MutexTryAcquireLockResult.TimedOut;
                            }
 
                            int remainingTimeoutMilliseconds = timeoutMilliseconds - elapsedMilliseconds;
                            int sleepMilliseconds = Math.Min(PollLoopMaximumSleepMilliseconds, remainingTimeoutMilliseconds);
                            Thread.Sleep(sleepMilliseconds);
                        } while (!SharedMemoryHelpers.TryAcquireFileLock(_sharedLockFileHandle, nonBlocking: true));
                        _sharedData->TimedWaiterCount--;
                        break;
                    }
                }
                releaseProcessLock = false;
 
                // Detect abandoned lock that isn't marked as abandoned.
                if (IsLockOwnedByAnyThread)
                    return MutexTryAcquireLockResult.AcquiredLockButMutexWasAbandoned;
 
                return MutexTryAcquireLockResult.AcquiredLock;
            }
            finally
            {
                if (releaseProcessLock)
                {
                    _processLevelMutex.ReleaseMutex();
                }
            }
        }
 
        [StructLayout(LayoutKind.Sequential)]
        internal ref struct SharedData
        {
            private uint _timedWaiterCount;
            private uint _lockOwnerProcessId;
            private uint _lockOwnerThreadId;
            private byte _isAbandoned;
 
            public uint TimedWaiterCount { get => _timedWaiterCount; set => _timedWaiterCount = value; }
            public uint LockOwnerProcessId
            {
                get
                {
                    return _lockOwnerProcessId;
                }
                set
                {
                    _lockOwnerProcessId = value;
                }
            }
 
            public uint LockOwnerThreadId
            {
                get
                {
                    return _lockOwnerThreadId;
                }
                set
                {
                    _lockOwnerThreadId = value;
                }
            }
 
            public bool IsAbandoned
            {
                get
                {
                    return _isAbandoned != 0;
                }
                set
                {
                    _isAbandoned = value ? (byte)1 : (byte)0;
                }
            }
        }
    }
 
    internal enum MutexTryAcquireLockResult : byte
    {
        AcquiredLock,
        AcquiredLockButMutexWasAbandoned,
        TimedOut,
        AcquiredLockRecursively,
    }
 
    internal static partial class WaitSubsystem
    {
        private sealed class NamedMutex : IWaitableObject
        {
            private readonly SharedMemoryProcessDataHeader<NamedMutexProcessDataBase> _processDataHeader;
 
            public NamedMutex(SharedMemoryProcessDataHeader<NamedMutexProcessDataBase> processDataHeader)
            {
                _processDataHeader = processDataHeader;
            }
 
            public int Wait_Locked(ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool interruptible, bool prioritize, ref LockHolder lockHolder)
            {
                lockHolder.Dispose();
                LockHolder scope = SharedMemoryManager<NamedMutexProcessDataBase>.Instance.AcquireCreationDeletionProcessLock();
                try
                {
                    MutexTryAcquireLockResult result = _processDataHeader._processData!.TryAcquireLock(waitInfo, timeoutMilliseconds, ref scope);
                    return result switch
                    {
                        MutexTryAcquireLockResult.AcquiredLock => WaitHandle.WaitSuccess,
                        MutexTryAcquireLockResult.AcquiredLockButMutexWasAbandoned => WaitHandle.WaitAbandoned,
                        MutexTryAcquireLockResult.TimedOut => WaitHandle.WaitTimeout,
                        _ => throw new UnreachableException()
                    };
                }
                finally
                {
                    scope.Dispose();
                }
            }
 
            public void Signal(int count, ref LockHolder lockHolder)
            {
                lockHolder.Dispose();
                _processDataHeader._processData!.ReleaseLock();
            }
 
            public void OnDeleteHandle()
            {
                LockHolder scope = SharedMemoryManager<NamedMutexProcessDataBase>.Instance.AcquireCreationDeletionProcessLock();
                try
                {
                    _processDataHeader.DecrementRefCount();
                }
                finally
                {
                    scope.Dispose();
                }
            }
 
            public static NamedMutex? CreateNamedMutex(string name, bool isUserScope, bool initiallyOwned, out bool createdNew)
            {
                var namedMutexProcessData = NamedMutexProcessDataBase.CreateOrOpen(name, isUserScope, createIfNotExist: true, acquireLockIfCreated: initiallyOwned, out createdNew);
                if (namedMutexProcessData is null)
                {
                    createdNew = false;
                    return null;
                }
 
                Debug.Assert(namedMutexProcessData._processData is not null);
                return new NamedMutex(namedMutexProcessData);
            }
 
            public static OpenExistingResult OpenNamedMutex(string name, bool isUserScope, out NamedMutex? result)
            {
                var namedMutexProcessData = NamedMutexProcessDataBase.CreateOrOpen(name, isUserScope, createIfNotExist: false, acquireLockIfCreated: false, out _);
                if (namedMutexProcessData is null)
                {
                    result = null;
                    return OpenExistingResult.NameNotFound;
                }
 
                Debug.Assert(namedMutexProcessData._processData is not null);
                result = new NamedMutex(namedMutexProcessData);
                return OpenExistingResult.Success;
            }
        }
    }
}