From 02db598f36b37349a36be4a0af4009fb0bc506d5 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 8 Jul 2025 17:00:11 -0700 Subject: [PATCH 01/47] Start working on porting shared mutexes to managed in a line-by-line port of the logic. --- .../System.Private.CoreLib.csproj | 1 + .../src/System/Threading/MutexSharedMemory.cs | 606 ++++++++++++++++++ 2 files changed, 607 insertions(+) create mode 100644 src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index c12d6c854ac0c0..3f2025f8126539 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -292,6 +292,7 @@ + diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs new file mode 100644 index 00000000000000..7b9b6d65724ba2 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs @@ -0,0 +1,606 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace System.Threading +{ + internal unsafe struct SharedMemoryId + { + private byte* _name; + private nint _nameCharLength; + private byte _isSessionScope; + + private byte _isUserScope; + private uint _uid; + + + public bool IsSessionScope => _isSessionScope != 0; + public bool IsUserScope => _isUserScope != 0; + } + + internal enum SharedMemoryType : byte + { + Mutex + } + + [StructLayout(LayoutKind.Explicit)] + internal struct SharedMemorySharedDataHeader + { + private struct SharedMemoryAndVersion + { + public SharedMemoryType Type; + public uint Version; + } + + [FieldOffset(0)] + private SharedMemoryAndVersion _data; + + [FieldOffset(0)] + private ulong _raw; + + public SharedMemoryType Type => _data.Type; + public uint Version => _data.Version; + } + + [StructLayout(LayoutKind.Sequential)] + internal ref struct NamedMutexSharedDataNoPThread + { + uint _timedWaiterCount; + uint _lockOwnerProcessId; + uint _lockOwnerThreadId; + byte _isAbandoned; + + public uint TimedWaiterCount {get => _timedWaiterCount; set => _timedWaiterCount = value; } + public uint LockOwnerProcessId => _lockOwnerProcessId; + public uint LockOwnerThreadId => _lockOwnerThreadId; + + public bool IsAbandoned => _isAbandoned != 0; + } + + [StructLayout(LayoutKind.Sequential)] + internal ref struct NamedMutexSharedDataWithPThread + { + public static readonly nuint Size; + private static readonly nuint PThreadMutexSize; + + private const uint LockOwnerProcessIdOffset = 0x0; + private const uint LockOwnerThreadIdOffset = 0x4; + private const uint IsAbandonedOffset = 0x8; + + public uint LockOwnerProcessId => *(uint*)(Unsafe.AsPointer(ref this) + PThreadMutexSize + LockOwnerProcessIdOffset); + public uint LockOwnerThreadId => *(uint*)(Unsafe.AsPointer(ref this) + PThreadMutexSize + LockOwnerThreadIdOffset); + public bool IsAbandoned => *(byte*)(Unsafe.AsPointer(ref this) + PThreadMutexSize + IsAbandonedOffset) != 0; + + public void* LockAddress => Unsafe.AsPointer(ref this); + } + + internal unsafe class SharedMemoryProcessDataHeader + { + private nuint _refCount; + private SharedMemoryId _id; + internal ISharedMemoryProcessData _data; + private SafeFileHandle _fileHandle; + internal SharedMemorySharedDataHeader* _sharedDataHeader; + private nuint _sharedDataTotalByteCount; + + public static void* GetDataPointer(SharedMemorySharedDataHeader processDataHeader) + { + if (processDataHeader == null) + { + return null; + } + + return processDataHeader._sharedDataHeader + 1; + } + } + + internal interface ISharedMemoryProcessData + { + bool CanClose { get; } + bool HasImplicitRef { get; set; } + void Close(bool isAbruptShutdown, bool releaseSharedData); + } + + abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader header) : ISharedMemoryProcessData + { + private const byte SyncSystemVersion = 1; + private const int PollLoopMaximumSleepMilliseconds = 100; + + private SharedMemoryProcessDataHeader _processDataHeader = header; + protected nuint _lockCount; + Thread? _lockOwnerThread; + NamedMutexProcessDataBase? _nextInThreadOwnedNamedMutexList; + bool _hasRefFromLockOwnerThread; + + public abstract bool IsLockOwnedByCurrentThread { get; } + + protected abstract void SetLockOwnerToCurrentThread(); + + public MutexTryAcquireResult TryAcquireLock(int timeoutMilliseconds) + { + MutexTryAcquireResult result = AcquireLockCore(timeoutMilliseconds); + + if (result == MutexTryAcquireResult.AcquiredLockRecursively) + { + return MutexTryAcquireResult.AcquiredLock; + } + + SetLockOwnerToCurrentThread(); + _lockCount = 1; + _lockOwnerThread = Thread.CurrentThread; + AddOwnedNamedMutex(Thread.CurrentThread, this); + + if (IsAbandoned) + { + IsAbandoned = false; + result = MutexTryAcquireResult.AcquiredLockButMutexWasAbandoned; + } + + return result; + } + + public void ReleaseLock() + { + if (!IsLockOwnedByCurrentThread) + { + throw new InvalidOperationException("Cannot release a lock that is not owned by the current thread."); + } + + --_lockCount; + if (_lockCount != 0) + { + return; + } + + RemoveOwnedNamedMutex(Thread.CurrentThread, this); + _lockOwnerThread = null; + ReleaseLockCore(); + } + + public void Abandon() + { + IsAbandoned = true; + _lockCount = 0; + _lockOwnerThread = null; + ReleaseLockCore(); + + if (_hasRefFromLockOwnerThread) + { + _hasRefFromLockOwnerThread = false; + _processDataHeader.DecRefCount(); + } + } + + protected abstract MutexTryAcquireLockResult AcquireLockCore(int timeoutMilliseconds); + + protected abstract bool IsAbandoned { get; set; } + + protected abstract void ReleaseLockCore(); + + public bool CanClose => _lockOwnerThread == null || _lockOwnerThread == Thread.CurrentThread; + + public bool HasImplicitRef + { + get => _hasRefFromLockOwnerThread; + set => _hasRefFromLockOwnerThread = value; + } + + public virtual void Close(bool isAbruptShutdown, bool releaseSharedData) + { + if (!isAbruptShutdown) + { + Debug.Assert(CanClose); + Debug.Assert(!_hasRefFromLockOwnerThread); + + if (_lockOwnerThread == Thread.CurrentThread) + { + RemoveOwnedNamedMutex(Thread.CurrentThread, this); + Abandon(); + } + else + { + Debug.Assert(_lockOwnerThread == null); + } + + if (releaseSharedData) + { + ReleaseSharedData(); + } + } + } + + protected abstract void ReleaseSharedData(); + + private static unsafe SharedMemoryProcessDataHeader CreateOrOpen(string name, bool isUserScope, bool createIfNotExist, bool acquireLockIfCreated, out bool created) + { + using var creationDeletionProcessLock = SharedMemoryManager.Instance.AcquireCreationDeletionLock(); + + SharedMemoryProcessDataHeader processDataHeader = SharedMemoryProcessDataHeader.CreateOrOpen(name, isUserScope, new SharedMemorySharedDataHeader + { + Type = SharedMemoryType.Mutex, + Version = SyncSystemVersion, + }, + SharedMemoryManager.UsePThreadMutexes ? NamedMutexSharedDataWithPThread.Size : sizeof(NamedMutexSharedDataNoPThread), + createIfNotExist, acquireLockIfCreated, out created, out IDisposable? creationDeletionLockFileHandle); + + if (processDataHeader == null) + { + return null; + } + + using IDisposable? creationDeletionLockFileHandleScope = created ? creationDeletionLockFileHandle : null; + + if (created) + { + InitializeSharedData(SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader._sharedDataHeader)); + } + + if (processDataHeader._data is null) + { + if (SharedMemoryManager.UsePThreadMutexes) + { + processDataHeader._data = new NamedMutexProcessDataWithPThread(processDataHeader); + } + else + { + processDataHeader._data = new NamedMutexProcessDataNoPThreads(processDataHeader, created); + } + + if (created && acquireLockIfCreated) + { + MutexTryAcquireResult acquireResult = processDataHeader._data.TryAcquireLock(); + Debug.Assert(acquireResult != MutexTryAcquireResult.AcquiredLock); + } + } + + return processDataHeader; + } + } + + class NamedMutexProcessDataWithPThreads : NamedMutexProcessDataBase + { + public NamedMutexProcessDataWithPThreads(SharedMemoryProcessDataHeader processDataHeader) : base(processDataHeader) + { + // Initialize the shared data header for pthread mutexes. + NamedMutexSharedDataWithPThread* sharedData = (NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); + } + + public override bool IsLockOwnedByCurrentThread + { + get + { + NamedMutexSharedDataWithPThread* sharedData = (NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); + return sharedData->LockOwnerProcessId == (uint)Environment.ProcessId && + sharedData->LockOwnerThreadId == (uint)Thread.CurrentThread.ManagedThreadId; + } + } + + protected override void SetLockOwnerToCurrentThread() + { + NamedMutexSharedDataWithPThread* sharedData = (NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); + sharedData->LockOwnerProcessId = (uint)Environment.ProcessId; + sharedData->LockOwnerThreadId = (uint)Thread.CurrentThread.ManagedThreadId; + } + + private bool IsLockOwnedByAnyThread + { + get + { + NamedMutexSharedDataWithPThread* sharedData = (NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); + return sharedData->LockOwnerProcessId != SharedMemoryHelpers.InvalidProcessId && + sharedData->LockOwnerThreadId != SharedMemoryHelpers.InvalidThreadId; + } + } + + protected override bool IsAbandoned + { + get => ((NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader))->IsAbandoned; + set => ((NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader))->IsAbandoned = value ? 1 : 0; + } + + protected override void ReleaseSharedData() + { + } + + protected override MutexTryAcquireLockResult AcquireLockCore(System.Int32 timeoutMilliseconds) + { + var sharedData = (NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); + MutexTryAcquireResult result = Interop.Sys.PThreadMutex_TryAcquireLock(sharedData->LockAddress, timeoutMilliseconds); + + if (result == MutexTryAcquireResult.TimedOut) + { + return MutexTryAcquireLockResult.TimedOut; + } + + if (_lockCount != 0) + { + Debug.Assert(IsLockOwnedByCurrentThread); + 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.PThreadMutex_ReleaseLock(sharedData->LockAddress); + } + Debug.Assert(result != MutexTryAcquireResult.AcquiredLockButMutexWasAbandoned); + Debug.Assert(!IsAbandoned); + } + + return result; + } + + protected override void ReleaseLockCore() + { + Debug.Assert(IsLockOwnedByCurrentThread); + Debug.Assert(_lockCount == 0); + NamedMutexSharedDataWithPThread* sharedData = (NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); + sharedData->LockOwnerProcessId = 0; + sharedData->LockOwnerThreadId = 0; + + Interop.Sys.PThreadMutex_ReleaseLock(sharedData->LockAddress); + } + } + + class NamedMutexProcessDataNoPThreads : NamedMutexProcessDataBase + { + private Lock _processLockHandle = new(); + private SafeFileHandle _sharedLockFileHandle; + public NamedMutexProcessDataNoPThreads(SharedMemoryProcessDataHeader processDataHeader, bool created) : base(processDataHeader) + { + SharedMemoryId id = processDataHeader._id; + string lockFileDirectory = Path.Combine( + SharedMemoryManager.SharedFilesPath, + id.GetRuntimeTempDirectoryName(), + SharedMemoryManager.SharedMemoryLockFilesDirectoryName + ); + + if (created) + { + SharedMemoryHelpers.EnsureDirectoryExists(lockFileDirectory); + } + + string sessionDirectory = Path.Combine(lockFileDirectory, id.GetSessionDirectoryName()); + + if (created) + { + SharedMemoryHelpers.EnsureDirectoryExists(sessionDirectory); + } + + string lockFilePath = Path.Combine(sessionDirectory, id.GetName()); + _sharedLockFileHandle = SharedMemoryHelpers.CreateOrOpenLockFile(lockFilePath, id, out bool createdNew); + } + + public override bool IsLockOwnedByCurrentThread + { + get + { + NamedMutexSharedDataNoPThread* sharedData = (NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); + return sharedData->LockOwnerProcessId == (uint)Environment.ProcessId && + sharedData->LockOwnerThreadId == (uint)Thread.CurrentThread.ManagedThreadId; + } + } + + protected override void SetLockOwnerToCurrentThread() + { + NamedMutexSharedDataNoPThread* sharedData = (NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); + sharedData->LockOwnerProcessId = (uint)Environment.ProcessId; + sharedData->LockOwnerThreadId = (uint)Thread.CurrentThread.ManagedThreadId; + } + + private bool IsLockOwnedByAnyThread + { + get + { + NamedMutexSharedDataNoPThread* sharedData = (NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); + return sharedData->LockOwnerProcessId != SharedMemoryHelpers.InvalidProcessId && + sharedData->LockOwnerThreadId != SharedMemoryHelpers.InvalidThreadId; + } + } + + protected override void ReleaseLockCore() + { + Debug.Assert(IsLockOwnedByCurrentThread); + Debug.Assert(_lockCount == 0); + NamedMutexSharedDataNoPThread* sharedData = (NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); + sharedData->LockOwnerProcessId = 0; + sharedData->LockOwnerThreadId = 0; + + SharedMemoryHelpers.ReleaseFileLock(_sharedLockFileHandle); + _processLockHandle.Release(); + } + + protected override bool IsAbandoned + { + get => ((NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader))->IsAbandoned; + set => ((NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader))->IsAbandoned = value ? 1 : 0; + } + + public override void Close(System.Boolean isAbruptShutdown, System.Boolean releaseSharedData) + { + base.Close(isAbruptShutdown, releaseSharedData); + + if (!releaseSharedData) + { + return; + } + + string sessionDirectory = Path.Combine( + SharedMemoryManager.SharedFilesPath, + _processDataHeader._id.GetRuntimeTempDirectoryName(), + SharedMemoryManager.SharedMemoryLockFilesDirectoryName, + _processDataHeader._id.GetSessionDirectoryName() + ); + + try + { + // Delete the lock file. + File.Delete(Path.Combine(sessionDirectory, _processDataHeader._id.GetName())); + // Delete the session directory if it's empty. + Directory.Delete(sessionDirectory); + } + catch (Exception) + { + // Ignore the error, just don't release the shared data. + } + } + + protected override void ReleaseSharedData() + { + _sharedLockFileHandle.Dispose(); + _sharedLockFileHandle = null; + } + + protected override MutexTryAcquireLockResult AcquireLockCore(int timeoutMilliseconds) + { + int startTime = 0; + if (timeoutMilliseconds > 0) + { + startTime = Environment.TickCount; + } + + using var lockScope = _processLockHandle.Acquire(); + + if (_lockCount > 0) + { + Debug.Assert(IsLockOwnedByCurrentThread); + // The lock is already owned by the current thread. + checked + { + _lockCount++; + } + return MutexTryAcquireLockResult.AcquiredLockRecursively; + } + + NamedMutexSharedDataNoPThread* sharedData = (NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); + + 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--; + } + } + + // Detect abandoned lock that isn't marked as abandoned. + if (IsLockOwnedByAnyThread) + return MutexTryAcquireLockResult.AcquiredLockButMutexWasAbandoned; + + return MutexTryAcquireLockResult.AcquiredLock; + } + } + + enum MutexTryAcquireResult : byte + { + AcquiredLock, + AcquiredLockButMutexWasAbandoned, + TimedOut, + AcquiredLockRecursively, + } + + class SharedMemoryManager + { + [FeatureSwitchDefinition("System.Threading.NamedMutexUsePThreadMutex", "Use shared pthread mutexes for named mutexes")] + internal static bool UsePThreadMutexes { get; } = AppContext.TryGetSwitch("System.Threading.NamedMutexUsePThreadMutex", out bool value) + ? value + : false; + + internal const string SharedMemoryLockFilesDirectoryName = "lockfiles"; + + private Lock _creationDeletionProcessLock = new Lock(); + private SafeFileHandle _creationDeletionLockFileHandle; + private Dictionary _uidToFileHandleMap = []; + + public Lock.Scope AcquireCreationDeletionLock() + { + return _creationDeletionProcessLock.Acquire(); + } + + public IDisposable AcquireCreationDeletionLockFileHandle(SharedMemoryId id) + { + throw new NotImplementedException(); + } + + public void AddUserScopeCreationDeletionLockFileHandle(uint uid, SafeFileHandle fileHandle) + { + _uidToFileHandleMap[uid] = fileHandle; + } + + public SafeFileHandle? GetUserScopeCreationDeletionLockFileHandle(uint uid) + { + _uidToFileHandleMap.TryGetValue(uid, out SafeFileHandle? fileHandle); + return fileHandle; + } + + private Dictionary _processDataHeaders = new Dictionary(); + + public void AddProcessDataHeader(SharedMemoryProcessDataHeader processDataHeader) + { + _processDataHeaders[processDataHeader._id] = processDataHeader; + } + + public void RemoveProcessDataHeader(SharedMemoryProcessDataHeader processDataHeader) + { + _processDataHeaders.Remove(processDataHeader._id); + } + + public SharedMemoryProcessDataHeader? FindProcessDataHeader(SharedMemoryId id) + { + _processDataHeaders.TryGetValue(id, out SharedMemoryProcessDataHeader? header); + return header; + } + } +} \ No newline at end of file From b855928639df5741e82a2f02a09eea42da3f83b6 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 9 Jul 2025 16:42:56 -0700 Subject: [PATCH 02/47] Fill out a lot of the rest of the logic. --- .../System.Private.CoreLib.csproj | 2 +- .../src/System/Threading/MutexSharedMemory.cs | 916 +++++++++++++++--- .../System.Private.CoreLib.Shared.projitems | 9 + 3 files changed, 792 insertions(+), 135 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index 3f2025f8126539..9be8e337ea7be8 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -292,7 +292,7 @@ - + diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs index 7b9b6d65724ba2..f3769ddf447d22 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs @@ -2,22 +2,48 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using System.Text; +using Microsoft.Win32.SafeHandles; namespace System.Threading { internal unsafe struct SharedMemoryId { - private byte* _name; - private nint _nameCharLength; - private byte _isSessionScope; - - private byte _isUserScope; - private uint _uid; + public string Name { get; set; } + public bool IsSessionScope { get; set; } + public bool IsUserScope { get; set; } + public uint Uid { get; set; } + internal readonly string GetRuntimeTempDirectoryName() + { + if (IsUserScope) + { + return $"{SharedMemoryManager.UserScopedRuntimeTempDirectoryName}{Uid}"; + } + else + { + return SharedMemoryManager.UserUnscopedRuntimeTempDirectoryName; + } + } - public bool IsSessionScope => _isSessionScope != 0; - public bool IsUserScope => _isUserScope != 0; + internal readonly string GetSessionDirectoryName() + { + if (IsSessionScope) + { + return $"{SharedMemoryManager.SharedMemorySessionDirectoryName}{SharedMemoryManager.SessionId}"; + } + else + { + return SharedMemoryManager.SharedMemoryGlobalDirectoryName; + } + } } internal enum SharedMemoryType : byte @@ -40,27 +66,67 @@ private struct SharedMemoryAndVersion [FieldOffset(0)] private ulong _raw; - public SharedMemoryType Type => _data.Type; - public uint Version => _data.Version; + public readonly SharedMemoryType Type => _data.Type; + public readonly uint Version => _data.Version; + + public SharedMemorySharedDataHeader(SharedMemoryType type, uint version) + { + _data = new SharedMemoryAndVersion + { + Type = type, + Version = version + }; + } } [StructLayout(LayoutKind.Sequential)] internal ref struct NamedMutexSharedDataNoPThread { - uint _timedWaiterCount; - uint _lockOwnerProcessId; - uint _lockOwnerThreadId; - byte _isAbandoned; + private uint _timedWaiterCount; + private uint _lockOwnerProcessId; + private uint _lockOwnerThreadId; + private byte _isAbandoned; - public uint TimedWaiterCount {get => _timedWaiterCount; set => _timedWaiterCount = value; } - public uint LockOwnerProcessId => _lockOwnerProcessId; - public uint LockOwnerThreadId => _lockOwnerThreadId; + 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 => _isAbandoned != 0; + public bool IsAbandoned + { + get + { + return _isAbandoned != 0; + } + set + { + _isAbandoned = value ? (byte)1 : (byte)0; + } + } } [StructLayout(LayoutKind.Sequential)] - internal ref struct NamedMutexSharedDataWithPThread + internal unsafe ref struct NamedMutexSharedDataWithPThread { public static readonly nuint Size; private static readonly nuint PThreadMutexSize; @@ -69,30 +135,214 @@ internal ref struct NamedMutexSharedDataWithPThread private const uint LockOwnerThreadIdOffset = 0x4; private const uint IsAbandonedOffset = 0x8; - public uint LockOwnerProcessId => *(uint*)(Unsafe.AsPointer(ref this) + PThreadMutexSize + LockOwnerProcessIdOffset); - public uint LockOwnerThreadId => *(uint*)(Unsafe.AsPointer(ref this) + PThreadMutexSize + LockOwnerThreadIdOffset); - public bool IsAbandoned => *(byte*)(Unsafe.AsPointer(ref this) + PThreadMutexSize + IsAbandonedOffset) != 0; + [UnscopedRef] + public ref uint LockOwnerProcessId => ref *(uint*)((byte*)Unsafe.AsPointer(ref this) + PThreadMutexSize + LockOwnerProcessIdOffset); + [UnscopedRef] + public ref uint LockOwnerThreadId => ref *(uint*)((byte*)Unsafe.AsPointer(ref this) + PThreadMutexSize + LockOwnerThreadIdOffset); + + public bool IsAbandoned + { + get + { + return *((byte*)Unsafe.AsPointer(ref this) + PThreadMutexSize + IsAbandonedOffset) != 0; + } + set + { + *((byte*)Unsafe.AsPointer(ref this) + PThreadMutexSize + IsAbandonedOffset) = value ? (byte)1 : (byte)0; + } + } public void* LockAddress => Unsafe.AsPointer(ref this); } internal unsafe class SharedMemoryProcessDataHeader { - private nuint _refCount; - private SharedMemoryId _id; - internal ISharedMemoryProcessData _data; + internal SharedMemoryId _id; + internal ISharedMemoryProcessData? _data; private SafeFileHandle _fileHandle; internal SharedMemorySharedDataHeader* _sharedDataHeader; private nuint _sharedDataTotalByteCount; - public static void* GetDataPointer(SharedMemorySharedDataHeader processDataHeader) + public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandle, SharedMemorySharedDataHeader* sharedDataHeader, nuint sharedDataTotalByteCount) + { + _id = id; + _fileHandle = fileHandle; + _sharedDataHeader = sharedDataHeader; + _sharedDataTotalByteCount = sharedDataTotalByteCount; + _data = null; // Will be initialized later + } + + public static void* GetDataPointer(SharedMemoryProcessDataHeader processDataHeader) + { + return processDataHeader is null + ? null + : (void*)((byte*)processDataHeader._sharedDataHeader + sizeof(SharedMemorySharedDataHeader)); + } + + internal static SharedMemoryProcessDataHeader? CreateOrOpen( + string name, + bool isUserScope, + SharedMemorySharedDataHeader requiredSharedDataHeader, + nuint sharedMemoryDataSize, + bool createIfNotExist, + bool acquireLockIfCreated, + out bool created, + out IDisposable? creationDeletionLockFileHandle) { - if (processDataHeader == null) + created = false; + creationDeletionLockFileHandle = null; + SharedMemoryId id = new() + { + Name = name, + IsUserScope = isUserScope + }; + + nuint sharedDataUsedByteCount = (nuint)sizeof(SharedMemorySharedDataHeader) + sharedMemoryDataSize; + nuint sharedDataTotalByteCount = AlignUp(sharedDataUsedByteCount, SharedMemoryHelpers.GetVirtualPageSize()); + + SharedMemoryProcessDataHeader? processDataHeader = SharedMemoryManager.Instance.FindProcessDataHeader(id); + + if (processDataHeader is not null) + { + Debug.Assert(processDataHeader._sharedDataTotalByteCount == sharedDataTotalByteCount); + return processDataHeader; + } + + creationDeletionLockFileHandle = SharedMemoryManager.Instance.AcquireCreationDeletionLockFileHandle(id); + + string sessionDirectory = Path.Combine( + SharedMemoryManager.SharedFilesPath, + id.GetRuntimeTempDirectoryName(), + SharedMemoryManager.SharedMemorySharedMemoryDirectoryName, + id.GetSessionDirectoryName() + ); + + if (!SharedMemoryHelpers.EnsureDirectoryExists(sessionDirectory, id, isGlobalLockAcquired: true, createIfNotExist)) + { + Debug.Assert(!createIfNotExist); + return null; + } + + string sharedMemoryFilePath = Path.Combine(sessionDirectory, id.Name); + + SafeFileHandle fileHandle = SharedMemoryHelpers.CreateOrOpenFile(sharedMemoryFilePath, id, createIfNotExist, out bool createdFile); + if (fileHandle.IsInvalid) { return null; } - return processDataHeader._sharedDataHeader + 1; + bool clearContents = false; + if (!createdFile) + { + // A shared file lock on the shared memory file would be held by any process that has opened the same file. Try to take + // an exclusive lock on the file. Successfully acquiring an exclusive lock indicates that no process has a reference to + // the shared memory file, and this process can reinitialize its contents. + if (SharedMemoryHelpers.TryAcquireFileLock(fileHandle, nonBlocking: true)) + { + // The shared memory file is not being used, flag it as created so that its contents will be reinitialized + Interop.Sys.FLock(fileHandle, Interop.Sys.LockOperations.LOCK_UN); + if (!createIfNotExist) + { + return null; + } + createdFile = true; + clearContents = true; + } + } + + if (createdFile) + { + SetFileSize(fileHandle, sharedMemoryFilePath, (long)sharedDataTotalByteCount); + } + else + { + if (Interop.Sys.FStat(fileHandle, out Interop.Sys.FileStatus fileStatus) != 0) + { + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath); + } + + if (fileStatus.Size < (long)sharedDataUsedByteCount) + { + throw new InvalidOperationException("Header mismatch"); + } + + SetFileSize(fileHandle, sharedMemoryFilePath, (long)sharedDataTotalByteCount); + } + + // Acquire and hold a shared file lock on the shared memory file as long as it is open, to indicate that this process is + // using the file. An exclusive file lock is attempted above to detect whether the file contents are valid, for the case + // where a process crashes or is killed after the file is created. Since we already hold the creation/deletion locks, a + // non-blocking file lock should succeed. + + if (!SharedMemoryHelpers.TryAcquireFileLock(fileHandle, nonBlocking: true, exclusive: false)) + { + Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); + throw Interop.GetExceptionForIoErrno(errorInfo, sharedMemoryFilePath); + } + + using AutoReleaseFileLock autoReleaseFileLock = new(fileHandle); + + using MemoryMappedFileHolder memory = SharedMemoryHelpers.MemoryMapFile(fileHandle, sharedDataTotalByteCount); + + SharedMemorySharedDataHeader* sharedDataHeader = (SharedMemorySharedDataHeader*)memory.Pointer; + if (createdFile && clearContents) + { + NativeMemory.Clear(memory.Pointer, sharedDataUsedByteCount); + *sharedDataHeader = requiredSharedDataHeader; + } + else + { + if (sharedDataHeader->Type != requiredSharedDataHeader.Type || + sharedDataHeader->Version != requiredSharedDataHeader.Version) + { + throw new InvalidOperationException("Header mismatch"); + } + } + + if (!createdFile) + { + creationDeletionLockFileHandle.Dispose(); + creationDeletionLockFileHandle = null; + } + + processDataHeader = new SharedMemoryProcessDataHeader( + id, + fileHandle, + sharedDataHeader, + sharedDataTotalByteCount + ); + + autoReleaseFileLock.SuppressRelease(); + memory.SuppressRelease(); + + if (createdFile) + { + created = true; + } + + return processDataHeader; + + static nuint AlignUp(nuint value, nuint alignment) + { + nuint alignMask = alignment - 1; + return (nuint)((value + alignMask) & ~alignMask); + } + + static void SetFileSize(SafeFileHandle fd, string path, long size) + { + if (Interop.Sys.FTruncate(fd, 0) < 0) + { + Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); + if (errorInfo.Error != Interop.Error.EBADF && errorInfo.Error != Interop.Error.EINVAL) + { + // We know the file descriptor is valid and we know the size argument to FTruncate is correct, + // so if EBADF or EINVAL is returned, it means we're dealing with a special file that can't be + // truncated. Ignore the error in such cases; in all others, throw. + throw Interop.GetExceptionForIoErrno(errorInfo, path); + } + } + } } } @@ -103,28 +353,30 @@ internal interface ISharedMemoryProcessData void Close(bool isAbruptShutdown, bool releaseSharedData); } - abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader header) : ISharedMemoryProcessData + internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader header) : ISharedMemoryProcessData { private const byte SyncSystemVersion = 1; - private const int PollLoopMaximumSleepMilliseconds = 100; + protected const int PollLoopMaximumSleepMilliseconds = 100; private SharedMemoryProcessDataHeader _processDataHeader = header; protected nuint _lockCount; - Thread? _lockOwnerThread; - NamedMutexProcessDataBase? _nextInThreadOwnedNamedMutexList; - bool _hasRefFromLockOwnerThread; + private Thread? _lockOwnerThread; + private NamedMutexProcessDataBase? _nextInThreadOwnedNamedMutexList; + private bool _hasRefFromLockOwnerThread; + + protected SharedMemoryId Id => _processDataHeader._id; public abstract bool IsLockOwnedByCurrentThread { get; } protected abstract void SetLockOwnerToCurrentThread(); - public MutexTryAcquireResult TryAcquireLock(int timeoutMilliseconds) + public MutexTryAcquireLockResult TryAcquireLock(int timeoutMilliseconds) { - MutexTryAcquireResult result = AcquireLockCore(timeoutMilliseconds); + MutexTryAcquireLockResult result = AcquireLockCore(timeoutMilliseconds); - if (result == MutexTryAcquireResult.AcquiredLockRecursively) + if (result == MutexTryAcquireLockResult.AcquiredLockRecursively) { - return MutexTryAcquireResult.AcquiredLock; + return MutexTryAcquireLockResult.AcquiredLock; } SetLockOwnerToCurrentThread(); @@ -135,7 +387,7 @@ public MutexTryAcquireResult TryAcquireLock(int timeoutMilliseconds) if (IsAbandoned) { IsAbandoned = false; - result = MutexTryAcquireResult.AcquiredLockButMutexWasAbandoned; + result = MutexTryAcquireLockResult.AcquiredLockButMutexWasAbandoned; } return result; @@ -169,7 +421,6 @@ public void Abandon() if (_hasRefFromLockOwnerThread) { _hasRefFromLockOwnerThread = false; - _processDataHeader.DecRefCount(); } } @@ -213,19 +464,21 @@ public virtual void Close(bool isAbruptShutdown, bool releaseSharedData) protected abstract void ReleaseSharedData(); - private static unsafe SharedMemoryProcessDataHeader CreateOrOpen(string name, bool isUserScope, bool createIfNotExist, bool acquireLockIfCreated, out bool created) + private static unsafe SharedMemoryProcessDataHeader? CreateOrOpen(string name, bool isUserScope, bool createIfNotExist, bool acquireLockIfCreated, out bool created) { using var creationDeletionProcessLock = SharedMemoryManager.Instance.AcquireCreationDeletionLock(); - SharedMemoryProcessDataHeader processDataHeader = SharedMemoryProcessDataHeader.CreateOrOpen(name, isUserScope, new SharedMemorySharedDataHeader - { - Type = SharedMemoryType.Mutex, - Version = SyncSystemVersion, - }, - SharedMemoryManager.UsePThreadMutexes ? NamedMutexSharedDataWithPThread.Size : sizeof(NamedMutexSharedDataNoPThread), - createIfNotExist, acquireLockIfCreated, out created, out IDisposable? creationDeletionLockFileHandle); - - if (processDataHeader == null) + SharedMemoryProcessDataHeader? processDataHeader = SharedMemoryProcessDataHeader.CreateOrOpen( + name, + isUserScope, + new SharedMemorySharedDataHeader(SharedMemoryType.Mutex, SyncSystemVersion), + SharedMemoryManager.UsePThreadMutexes ? NamedMutexSharedDataWithPThread.Size : (nuint)sizeof(NamedMutexSharedDataNoPThread), + createIfNotExist, + acquireLockIfCreated, + out created, + out IDisposable? creationDeletionLockFileHandle); + + if (processDataHeader is null) { return null; } @@ -234,14 +487,14 @@ private static unsafe SharedMemoryProcessDataHeader CreateOrOpen(string name, bo if (created) { - InitializeSharedData(SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader._sharedDataHeader)); + InitializeSharedData(SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader)); } if (processDataHeader._data is null) { if (SharedMemoryManager.UsePThreadMutexes) { - processDataHeader._data = new NamedMutexProcessDataWithPThread(processDataHeader); + processDataHeader._data = new NamedMutexProcessDataWithPThreads(processDataHeader); } else { @@ -250,66 +503,87 @@ private static unsafe SharedMemoryProcessDataHeader CreateOrOpen(string name, bo if (created && acquireLockIfCreated) { - MutexTryAcquireResult acquireResult = processDataHeader._data.TryAcquireLock(); - Debug.Assert(acquireResult != MutexTryAcquireResult.AcquiredLock); + MutexTryAcquireLockResult acquireResult = ((NamedMutexProcessDataBase)processDataHeader._data).TryAcquireLock(timeoutMilliseconds: 0); + Debug.Assert(acquireResult != MutexTryAcquireLockResult.AcquiredLock); } } return processDataHeader; } + + private static unsafe void InitializeSharedData(void* v) + { + if (SharedMemoryManager.UsePThreadMutexes) + { + NamedMutexSharedDataWithPThread* sharedData = (NamedMutexSharedDataWithPThread*)v; + if (Interop.Sys.PThreadMutex_Init(sharedData->LockAddress) != 0) + { + Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); + throw Interop.GetExceptionForIoErrno(errorInfo, "Failed to initialize pthread mutex"); + } + sharedData->LockOwnerProcessId = SharedMemoryHelpers.InvalidProcessId; + sharedData->LockOwnerThreadId = SharedMemoryHelpers.InvalidThreadId; + sharedData->IsAbandoned = false; + } + else + { + NamedMutexSharedDataNoPThread* sharedData = (NamedMutexSharedDataNoPThread*)v; + sharedData->LockOwnerProcessId = SharedMemoryHelpers.InvalidProcessId; + sharedData->LockOwnerThreadId = SharedMemoryHelpers.InvalidThreadId; + sharedData->IsAbandoned = false; + sharedData->TimedWaiterCount = 0; + } + } } - class NamedMutexProcessDataWithPThreads : NamedMutexProcessDataBase + internal unsafe class NamedMutexProcessDataWithPThreads : NamedMutexProcessDataBase { + private NamedMutexSharedDataWithPThread* _sharedData; public NamedMutexProcessDataWithPThreads(SharedMemoryProcessDataHeader processDataHeader) : base(processDataHeader) { // Initialize the shared data header for pthread mutexes. - NamedMutexSharedDataWithPThread* sharedData = (NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); + _sharedData = (NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader); } public override bool IsLockOwnedByCurrentThread { get { - NamedMutexSharedDataWithPThread* sharedData = (NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); - return sharedData->LockOwnerProcessId == (uint)Environment.ProcessId && - sharedData->LockOwnerThreadId == (uint)Thread.CurrentThread.ManagedThreadId; + return _sharedData->LockOwnerProcessId == (uint)Environment.ProcessId && + _sharedData->LockOwnerThreadId == (uint)Thread.CurrentThread.ManagedThreadId; } } protected override void SetLockOwnerToCurrentThread() { - NamedMutexSharedDataWithPThread* sharedData = (NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); - sharedData->LockOwnerProcessId = (uint)Environment.ProcessId; - sharedData->LockOwnerThreadId = (uint)Thread.CurrentThread.ManagedThreadId; + _sharedData->LockOwnerProcessId = (uint)Environment.ProcessId; + _sharedData->LockOwnerThreadId = (uint)Thread.CurrentThread.ManagedThreadId; } private bool IsLockOwnedByAnyThread { get { - NamedMutexSharedDataWithPThread* sharedData = (NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); - return sharedData->LockOwnerProcessId != SharedMemoryHelpers.InvalidProcessId && - sharedData->LockOwnerThreadId != SharedMemoryHelpers.InvalidThreadId; + return _sharedData->LockOwnerProcessId != SharedMemoryHelpers.InvalidProcessId && + _sharedData->LockOwnerThreadId != SharedMemoryHelpers.InvalidThreadId; } } protected override bool IsAbandoned { - get => ((NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader))->IsAbandoned; - set => ((NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader))->IsAbandoned = value ? 1 : 0; + get => _sharedData->IsAbandoned; + set => _sharedData->IsAbandoned = value; } protected override void ReleaseSharedData() { } - protected override MutexTryAcquireLockResult AcquireLockCore(System.Int32 timeoutMilliseconds) + protected override MutexTryAcquireLockResult AcquireLockCore(int timeoutMilliseconds) { - var sharedData = (NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); - MutexTryAcquireResult result = Interop.Sys.PThreadMutex_TryAcquireLock(sharedData->LockAddress, timeoutMilliseconds); + MutexTryAcquireLockResult result = Interop.Sys.PThreadMutex_TryAcquireLock(_sharedData->LockAddress, timeoutMilliseconds); - if (result == MutexTryAcquireResult.TimedOut) + if (result == MutexTryAcquireLockResult.TimedOut) { return MutexTryAcquireLockResult.TimedOut; } @@ -317,6 +591,8 @@ protected override MutexTryAcquireLockResult AcquireLockCore(System.Int32 timeou if (_lockCount != 0) { Debug.Assert(IsLockOwnedByCurrentThread); + Debug.Assert(result != MutexTryAcquireLockResult.AcquiredLockButMutexWasAbandoned); + Debug.Assert(!IsAbandoned); try { checked @@ -328,10 +604,8 @@ protected override MutexTryAcquireLockResult AcquireLockCore(System.Int32 timeou finally { // The lock is released upon acquiring a recursive lock from the thread that already owns the lock - Interop.Sys.PThreadMutex_ReleaseLock(sharedData->LockAddress); + Interop.Sys.PThreadMutex_ReleaseLock(_sharedData->LockAddress); } - Debug.Assert(result != MutexTryAcquireResult.AcquiredLockButMutexWasAbandoned); - Debug.Assert(!IsAbandoned); } return result; @@ -349,59 +623,59 @@ protected override void ReleaseLockCore() } } - class NamedMutexProcessDataNoPThreads : NamedMutexProcessDataBase + internal unsafe class NamedMutexProcessDataNoPThreads : NamedMutexProcessDataBase { private Lock _processLockHandle = new(); private SafeFileHandle _sharedLockFileHandle; + + private NamedMutexSharedDataNoPThread* _sharedData; + public NamedMutexProcessDataNoPThreads(SharedMemoryProcessDataHeader processDataHeader, bool created) : base(processDataHeader) { - SharedMemoryId id = processDataHeader._id; + _sharedData = (NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader); string lockFileDirectory = Path.Combine( SharedMemoryManager.SharedFilesPath, - id.GetRuntimeTempDirectoryName(), + Id.GetRuntimeTempDirectoryName(), SharedMemoryManager.SharedMemoryLockFilesDirectoryName ); if (created) { - SharedMemoryHelpers.EnsureDirectoryExists(lockFileDirectory); + SharedMemoryHelpers.EnsureDirectoryExists(lockFileDirectory, Id, isGlobalLockAcquired: true); } - string sessionDirectory = Path.Combine(lockFileDirectory, id.GetSessionDirectoryName()); + string sessionDirectory = Path.Combine(lockFileDirectory, Id.GetSessionDirectoryName()); if (created) { - SharedMemoryHelpers.EnsureDirectoryExists(sessionDirectory); + SharedMemoryHelpers.EnsureDirectoryExists(sessionDirectory, Id, isGlobalLockAcquired: true); } - string lockFilePath = Path.Combine(sessionDirectory, id.GetName()); - _sharedLockFileHandle = SharedMemoryHelpers.CreateOrOpenLockFile(lockFilePath, id, out bool createdNew); + string lockFilePath = Path.Combine(sessionDirectory, Id.Name); + _sharedLockFileHandle = SharedMemoryHelpers.CreateOrOpenFile(lockFilePath, Id, created, out _); } public override bool IsLockOwnedByCurrentThread { get { - NamedMutexSharedDataNoPThread* sharedData = (NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); - return sharedData->LockOwnerProcessId == (uint)Environment.ProcessId && - sharedData->LockOwnerThreadId == (uint)Thread.CurrentThread.ManagedThreadId; + return _sharedData->LockOwnerProcessId == (uint)Environment.ProcessId && + _sharedData->LockOwnerThreadId == (uint)Thread.CurrentThread.ManagedThreadId; } } protected override void SetLockOwnerToCurrentThread() { - NamedMutexSharedDataNoPThread* sharedData = (NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); - sharedData->LockOwnerProcessId = (uint)Environment.ProcessId; - sharedData->LockOwnerThreadId = (uint)Thread.CurrentThread.ManagedThreadId; + _sharedData->LockOwnerProcessId = (uint)Environment.ProcessId; + _sharedData->LockOwnerThreadId = (uint)Thread.CurrentThread.ManagedThreadId; } private bool IsLockOwnedByAnyThread { get { - NamedMutexSharedDataNoPThread* sharedData = (NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); - return sharedData->LockOwnerProcessId != SharedMemoryHelpers.InvalidProcessId && - sharedData->LockOwnerThreadId != SharedMemoryHelpers.InvalidThreadId; + return _sharedData->LockOwnerProcessId != SharedMemoryHelpers.InvalidProcessId && + _sharedData->LockOwnerThreadId != SharedMemoryHelpers.InvalidThreadId; } } @@ -409,21 +683,20 @@ protected override void ReleaseLockCore() { Debug.Assert(IsLockOwnedByCurrentThread); Debug.Assert(_lockCount == 0); - NamedMutexSharedDataNoPThread* sharedData = (NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); - sharedData->LockOwnerProcessId = 0; - sharedData->LockOwnerThreadId = 0; + _sharedData->LockOwnerProcessId = 0; + _sharedData->LockOwnerThreadId = 0; - SharedMemoryHelpers.ReleaseFileLock(_sharedLockFileHandle); - _processLockHandle.Release(); + Interop.Sys.FLock(_sharedLockFileHandle, Interop.Sys.LockOperations.LOCK_UN); + _processLockHandle.Exit(); } protected override bool IsAbandoned { - get => ((NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader))->IsAbandoned; - set => ((NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader))->IsAbandoned = value ? 1 : 0; + get => _sharedData->IsAbandoned; + set => _sharedData->IsAbandoned = value; } - public override void Close(System.Boolean isAbruptShutdown, System.Boolean releaseSharedData) + public override void Close(bool isAbruptShutdown, bool releaseSharedData) { base.Close(isAbruptShutdown, releaseSharedData); @@ -434,15 +707,15 @@ public override void Close(System.Boolean isAbruptShutdown, System.Boolean relea string sessionDirectory = Path.Combine( SharedMemoryManager.SharedFilesPath, - _processDataHeader._id.GetRuntimeTempDirectoryName(), + Id.GetRuntimeTempDirectoryName(), SharedMemoryManager.SharedMemoryLockFilesDirectoryName, - _processDataHeader._id.GetSessionDirectoryName() + Id.GetSessionDirectoryName() ); try { // Delete the lock file. - File.Delete(Path.Combine(sessionDirectory, _processDataHeader._id.GetName())); + File.Delete(Path.Combine(sessionDirectory, Id.Name)); // Delete the session directory if it's empty. Directory.Delete(sessionDirectory); } @@ -455,10 +728,10 @@ public override void Close(System.Boolean isAbruptShutdown, System.Boolean relea protected override void ReleaseSharedData() { _sharedLockFileHandle.Dispose(); - _sharedLockFileHandle = null; + _sharedLockFileHandle = null!; } - protected override MutexTryAcquireLockResult AcquireLockCore(int timeoutMilliseconds) + protected override unsafe MutexTryAcquireLockResult AcquireLockCore(int timeoutMilliseconds) { int startTime = 0; if (timeoutMilliseconds > 0) @@ -466,7 +739,7 @@ protected override MutexTryAcquireLockResult AcquireLockCore(int timeoutMillisec startTime = Environment.TickCount; } - using var lockScope = _processLockHandle.Acquire(); + using var lockScope = _processLockHandle.EnterScope(); if (_lockCount > 0) { @@ -479,13 +752,11 @@ protected override MutexTryAcquireLockResult AcquireLockCore(int timeoutMillisec return MutexTryAcquireLockResult.AcquiredLockRecursively; } - NamedMutexSharedDataNoPThread* sharedData = (NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); - switch (timeoutMilliseconds) { case -1: bool acquiredLock = false; - while (sharedData->TimedWaiterCount > 0) + while (_sharedData->TimedWaiterCount > 0) { if (SharedMemoryHelpers.TryAcquireFileLock(_sharedLockFileHandle, nonBlocking: true)) { @@ -510,29 +781,30 @@ protected override MutexTryAcquireLockResult AcquireLockCore(int timeoutMillisec } break; default: + { + if (SharedMemoryHelpers.TryAcquireFileLock(_sharedLockFileHandle, nonBlocking: true)) { - if (SharedMemoryHelpers.TryAcquireFileLock(_sharedLockFileHandle, nonBlocking: true)) - { - break; - } + break; + } - sharedData->TimedWaiterCount++; + _sharedData->TimedWaiterCount++; - do + do + { + int elapsedMilliseconds = Environment.TickCount - startTime; + if (elapsedMilliseconds >= timeoutMilliseconds) { - 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--; - } + _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; + } } // Detect abandoned lock that isn't marked as abandoned. @@ -543,7 +815,318 @@ protected override MutexTryAcquireLockResult AcquireLockCore(int timeoutMillisec } } - enum MutexTryAcquireResult : byte + internal static class SharedMemoryHelpers + { + public const uint InvalidProcessId = unchecked((uint)-1); + public const uint InvalidThreadId = unchecked((uint)-1); + + private const UnixFileMode PermissionsMask_OwnerUser_ReadWrite = UnixFileMode.UserRead | UnixFileMode.UserWrite; + private const UnixFileMode PermissionsMask_OwnerUser_ReadWriteExecute = PermissionsMask_OwnerUser_ReadWrite | UnixFileMode.UserExecute; + private const UnixFileMode PermissionsMask_NonOwnerUsers_Write = UnixFileMode.GroupWrite | UnixFileMode.OtherWrite; + private const UnixFileMode PermissionsMask_AllUsers_ReadWrite = UnixFileMode.UserRead | UnixFileMode.UserRead | UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.OtherRead | UnixFileMode.OtherWrite; + private const UnixFileMode PermissionsMask_AllUsers_ReadWriteExecute = PermissionsMask_AllUsers_ReadWrite | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; + private const UnixFileMode PermissionsMask_Sticky = UnixFileMode.StickyBit; + + internal static SafeFileHandle CreateOrOpenFile(string sharedMemoryFilePath, SharedMemoryId id, bool createIfNotExist, out bool createdFile) + { + SafeFileHandle fd = Interop.Sys.Open(sharedMemoryFilePath, Interop.Sys.OpenFlags.O_RDWR, 0); + if (!fd.IsInvalid) + { + if (id.IsUserScope) + { + if (Interop.Sys.FStat(fd, out Interop.Sys.FileStatus fileStatus) != 0) + { + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + fd.Dispose(); + throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath); + } + + if (fileStatus.Uid != id.Uid) + { + fd.Dispose(); + throw new IOException($"The file '{sharedMemoryFilePath}' is not owned by the current user with UID {id.Uid}."); + } + + if ((fileStatus.Mode & (int)PermissionsMask_AllUsers_ReadWriteExecute) != (int)PermissionsMask_OwnerUser_ReadWrite) + { + fd.Dispose(); + throw new IOException($"The file '{sharedMemoryFilePath}' does not have the expected permissions for user scope: {PermissionsMask_OwnerUser_ReadWrite}."); + } + } + createdFile = false; + return fd; + } + + Debug.Assert(Interop.Sys.GetLastError() == Interop.Error.ENOENT); + if (!createIfNotExist) + { + createdFile = false; + return fd; + } + + fd.Dispose(); + + UnixFileMode permissionsMask = id.IsUserScope + ? PermissionsMask_OwnerUser_ReadWrite + : PermissionsMask_AllUsers_ReadWrite; + + fd = Interop.Sys.Open( + sharedMemoryFilePath, + Interop.Sys.OpenFlags.O_RDWR | Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL, + (int)permissionsMask); + + int result = Interop.Sys.FChMod(fd, (int)permissionsMask); + + if (result != 0) + { + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + fd.Dispose(); + Interop.Sys.Unlink(sharedMemoryFilePath); + throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath); + } + + createdFile = true; + return fd; + } + + internal static bool EnsureDirectoryExists(string directoryPath, SharedMemoryId id, bool isGlobalLockAcquired, bool createIfNotExist = true, bool isSystemDirectory = false) + { + UnixFileMode permissionsMask = id.IsUserScope + ? PermissionsMask_OwnerUser_ReadWriteExecute + : PermissionsMask_AllUsers_ReadWriteExecute; + + int statResult = Interop.Sys.Stat(directoryPath, out Interop.Sys.FileStatus fileStatus); + + if (statResult != 0 && Interop.Sys.GetLastError() == Interop.Error.ENOENT) + { + if (!createIfNotExist) + { + // The directory does not exist and we are not allowed to create it. + return false; + } + + // The path does not exist, create the directory. The permissions mask passed to mkdir() is filtered by the process' + // permissions umask, so mkdir() may not set all of the requested permissions. We need to use chmod() to set the proper + // permissions. That creates a race when there is no global lock acquired when creating the directory. Another user's + // process may create the directory and this user's process may try to use it before the other process sets the full + // permissions. In that case, create a temporary directory first, set the permissions, and rename it to the actual + // directory name. + + if (isGlobalLockAcquired) + { + Directory.CreateDirectory(directoryPath, permissionsMask); + + try + { + FileSystem.SetUnixFileMode(directoryPath, permissionsMask); + } + catch (Exception) + { + Directory.Delete(directoryPath); + throw; + } + + return true; + } + + string tempPath = Path.Combine(SharedMemoryManager.SharedFilesPath, SharedMemoryManager.SharedMemoryUniqueTempNameTemplate); + + unsafe + { + byte* tempPathPtr = Utf8StringMarshaller.ConvertToUnmanaged(tempPath); + if (Interop.Sys.MkdTemp(tempPathPtr) == null) + { + Utf8StringMarshaller.Free(tempPathPtr); + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + throw Interop.GetExceptionForIoErrno(error, tempPath); + } + // Convert the path back to get the substituted path. + tempPath = Utf8StringMarshaller.ConvertToManaged(tempPathPtr)!; + Utf8StringMarshaller.Free(tempPathPtr); + } + + try + { + FileSystem.SetUnixFileMode(tempPath, permissionsMask); + } + catch (Exception) + { + Directory.Delete(tempPath); + throw; + } + + if (Interop.Sys.Rename(tempPath, directoryPath) == 0) + { + return true; + } + + // Another process may have beaten us to it. Delete the temp directory and continue to check the requested directory to + // see if it meets our needs. + Directory.Delete(tempPath); + statResult = Interop.Sys.Stat(directoryPath, out fileStatus); + } + + // If the path exists, check that it's a directory + if (statResult != 0 || (fileStatus.Mode & Interop.Sys.FileTypes.S_IFDIR) == 0) + { + if (statResult != 0) + { + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + if (error.Error != Interop.Error.ENOENT) + { + throw Interop.GetExceptionForIoErrno(error, directoryPath); + } + } + else + { + throw new IOException($"The path '{directoryPath}' exists but is not a directory."); + } + } + + if (isSystemDirectory) + { + // For system directories (such as TEMP_DIRECTORY_PATH), require sufficient permissions only for the + // owner user. For instance, "docker run --mount ..." to mount /tmp to some directory on the host mounts the + // destination directory with the same permissions as the source directory, which may not include some permissions for + // other users. In the docker container, other user permissions are typically not relevant and relaxing the permissions + // requirement allows for that scenario to work without having to work around it by first giving sufficient permissions + // for all users. + // + // If the directory is being used for user-scoped shared memory data, also ensure that either it has the sticky bit or + // it's owned by the current user and without write access for other users. + + permissionsMask = PermissionsMask_OwnerUser_ReadWriteExecute; + if ((fileStatus.Mode & (int)permissionsMask) == (int)permissionsMask + && ( + !id.IsUserScope || + (fileStatus.Mode & (int)PermissionsMask_Sticky) == (int)PermissionsMask_Sticky || + (fileStatus.Uid == id.Uid && (fileStatus.Mode & (int)PermissionsMask_NonOwnerUsers_Write) == 0) + )) + { + return true; + } + + throw new IOException($"The directory '{directoryPath}' does not have the expected owner or permissions for system scope: {fileStatus.Uid}, {Convert.ToString(fileStatus.Mode, 8)}."); + } + + // For non-system directories (such as gSharedFilesPath/SHARED_MEMORY_USER_UNSCOPED_RUNTIME_TEMP_DIRECTORY_NAME), + // require the sufficient permissions and try to update them if requested to create the directory, so that + // shared memory files may be shared according to its scope. + + // For user-scoped directories, verify the owner UID + if (id.IsUserScope && fileStatus.Uid != id.Uid) + { + throw new IOException($"The directory '{directoryPath}' is not owned by the current user with UID {id.Uid}."); + } + + // Verify the permissions, or try to change them if possible + if ((fileStatus.Mode & (int)PermissionsMask_AllUsers_ReadWriteExecute) == (int)permissionsMask + || (createIfNotExist && Interop.Sys.ChMod(directoryPath, (int)permissionsMask) == 0)) + { + return true; + } + + // We were not able to verify or set the necessary permissions. For user-scoped directories, this is treated as a failure + // since other users aren't sufficiently restricted in permissions. + if (id.IsUserScope) + { + throw new IOException($"The directory '{directoryPath}' does not have the expected permissions for user scope: {Convert.ToString(fileStatus.Mode, 8)}."); + } + + + // For user-unscoped directories, as a last resort, check that at least the owner user has full access. + permissionsMask = PermissionsMask_OwnerUser_ReadWriteExecute; + if ((fileStatus.Mode & (int)permissionsMask) != (int)permissionsMask) + { + throw new IOException($"The directory '{directoryPath}' does not have the expected owner permissions: {Convert.ToString(fileStatus.Mode, 8)}."); + } + + return true; + } + + internal static nuint GetVirtualPageSize() => throw new NotImplementedException(); + internal static MemoryMappedFileHolder MemoryMapFile(SafeFileHandle fileHandle, nuint sharedDataTotalByteCount) + { + nint addr = Interop.Sys.MMap( + 0, + sharedDataTotalByteCount, + Interop.Sys.MemoryMappedProtections.PROT_READ | Interop.Sys.MemoryMappedProtections.PROT_WRITE, + Interop.Sys.MemoryMappedFlags.MAP_SHARED, + fileHandle, + (long)sharedDataTotalByteCount); + + if (addr == -1) + { + Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); + throw Interop.GetExceptionForIoErrno(errorInfo, "Failed to memory map the file"); + } + + return new MemoryMappedFileHolder(addr, sharedDataTotalByteCount); + } + + internal static bool TryAcquireFileLock(SafeFileHandle sharedLockFileHandle, bool nonBlocking, bool exclusive = true) + { + Interop.Sys.LockOperations lockOperation = exclusive ? Interop.Sys.LockOperations.LOCK_EX : Interop.Sys.LockOperations.LOCK_SH; + if (nonBlocking) + { + lockOperation |= Interop.Sys.LockOperations.LOCK_NB; + } + int result = Interop.Sys.FLock(sharedLockFileHandle, lockOperation); + + if (result == 0) + { + return true; + } + + Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); + if (errorInfo.Error == Interop.Error.EWOULDBLOCK) + { + return false; + } + + throw Interop.GetExceptionForIoErrno(errorInfo); + } + } + + internal unsafe ref struct MemoryMappedFileHolder(nint addr, nuint length) + { + private bool _suppressed; + + public void SuppressRelease() + { + _suppressed = true; + } + + public void Dispose() + { + if (!_suppressed) + { + Interop.Sys.MUnmap(addr, length); + } + } + + public void* Pointer => (void*)addr; + } + + internal unsafe ref struct AutoReleaseFileLock(SafeFileHandle fd) + { + private bool _suppressed; + + public void SuppressRelease() + { + _suppressed = true; + } + + public void Dispose() + { + if (!_suppressed) + { + Interop.Sys.FLock(fd, Interop.Sys.LockOperations.LOCK_UN); + } + } + } + + internal enum MutexTryAcquireLockResult : byte { AcquiredLock, AcquiredLockButMutexWasAbandoned, @@ -551,27 +1134,92 @@ enum MutexTryAcquireResult : byte AcquiredLockRecursively, } - class SharedMemoryManager + internal sealed class SharedMemoryManager { - [FeatureSwitchDefinition("System.Threading.NamedMutexUsePThreadMutex", "Use shared pthread mutexes for named mutexes")] + [FeatureSwitchDefinition("System.Threading.NamedMutexUsePThreadMutex")] internal static bool UsePThreadMutexes { get; } = AppContext.TryGetSwitch("System.Threading.NamedMutexUsePThreadMutex", out bool value) ? value : false; + internal static SharedMemoryManager Instance { get; } = new SharedMemoryManager(); + public static string SharedFilesPath { get; } = InitalizeSharedFilesPath(); + public static int SessionId { get; } = Interop.Sys.GetSid(Environment.ProcessId); + + private static string InitalizeSharedFilesPath() => throw new NotImplementedException(); + internal const string SharedMemoryLockFilesDirectoryName = "lockfiles"; + internal const string SharedMemorySharedMemoryDirectoryName = "shm"; + + internal const string UserUnscopedRuntimeTempDirectoryName = ".dotnet"; + + internal const string UserScopedRuntimeTempDirectoryName = ".dotnet-uid"; + + internal const string SharedMemoryGlobalDirectoryName = "global"; + + internal const string SharedMemorySessionDirectoryName = "session"; + + internal const string SharedMemoryUniqueTempNameTemplate = ".dotnet.XXXXXX"; + private Lock _creationDeletionProcessLock = new Lock(); - private SafeFileHandle _creationDeletionLockFileHandle; + private SafeFileHandle? _creationDeletionLockFileHandle; private Dictionary _uidToFileHandleMap = []; public Lock.Scope AcquireCreationDeletionLock() { - return _creationDeletionProcessLock.Acquire(); + return _creationDeletionProcessLock.EnterScope(); } public IDisposable AcquireCreationDeletionLockFileHandle(SharedMemoryId id) { - throw new NotImplementedException(); + Debug.Assert(_creationDeletionProcessLock.IsHeldByCurrentThread); + SafeFileHandle? fd = id.IsUserScope ? GetUserScopeCreationDeletionLockFileHandle(id.Uid) : _creationDeletionLockFileHandle; + if (fd is null) + { + if (!SharedMemoryHelpers.EnsureDirectoryExists(SharedFilesPath, id, isGlobalLockAcquired: false, createIfNotExist: false, isSystemDirectory: true)) + { + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + throw Interop.GetExceptionForIoErrno(error, SharedFilesPath); + } + string runtimeTempDirectory = Path.Combine( + SharedFilesPath, + id.GetRuntimeTempDirectoryName()); + + SharedMemoryHelpers.EnsureDirectoryExists(runtimeTempDirectory, id, isGlobalLockAcquired: false); + + string sharedMemoryDirectory = Path.Combine( + runtimeTempDirectory, + SharedMemorySharedMemoryDirectoryName); + + SharedMemoryHelpers.EnsureDirectoryExists(sharedMemoryDirectory, id, isGlobalLockAcquired: false); + + fd = Interop.Sys.Open(sharedMemoryDirectory, Interop.Sys.OpenFlags.O_RDONLY, 0); + if (fd.IsInvalid) + { + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + fd.Dispose(); + throw Interop.GetExceptionForIoErrno(error, sharedMemoryDirectory); + } + } + + if (id.IsUserScope) + { + _uidToFileHandleMap.Add(id.Uid, fd); + } + + _creationDeletionLockFileHandle = fd; + + bool acquired = SharedMemoryHelpers.TryAcquireFileLock(fd, nonBlocking: true, exclusive: true); + Debug.Assert(acquired); + return new CreationDeletionLockFileScope(fd); + } + + private sealed class CreationDeletionLockFileScope(SafeFileHandle fd) : IDisposable + { + public void Dispose() + { + Interop.Sys.FLock(fd, Interop.Sys.LockOperations.LOCK_UN); + } } public void AddUserScopeCreationDeletionLockFileHandle(uint uid, SafeFileHandle fileHandle) @@ -585,7 +1233,7 @@ public void AddUserScopeCreationDeletionLockFileHandle(uint uid, SafeFileHandle return fileHandle; } - private Dictionary _processDataHeaders = new Dictionary(); + private Dictionary _processDataHeaders = []; public void AddProcessDataHeader(SharedMemoryProcessDataHeader processDataHeader) { @@ -603,4 +1251,4 @@ public void RemoveProcessDataHeader(SharedMemoryProcessDataHeader processDataHea return header; } } -} \ No newline at end of file +} diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 1c2303c097a6aa..9199925b5bb6b0 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2459,6 +2459,12 @@ Common\Interop\Unix\System.Native\Interop.MksTemps.cs + + Common\Interop\Unix\System.Native\Interop.MMap.cs + + + Common\Interop\Unix\System.Native\Interop.MUnmap.cs + Common\Interop\Unix\System.Native\Interop.MountPoints.cs @@ -2620,6 +2626,9 @@ Common\Interop\Unix\System.Native\Interop.GetPid.cs + + Common\Interop\Unix\System.Native\Interop.GetSid.cs + From 9ec79b89555cac9bd04c21fb4dfa5ce6cf5004e3 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 10 Jul 2025 20:12:43 +0000 Subject: [PATCH 03/47] Finish filling in the holes in the shared memory impl. --- .../src/System/Threading/MutexSharedMemory.cs | 209 +++++++++++++----- .../src/System/Threading/Thread.CoreCLR.cs | 2 + .../System.Native/Interop.PThreadMutex.cs | 19 ++ .../System.Private.CoreLib.Shared.projitems | 3 + .../src/System/IO/PersistedFiles.Unix.cs | 11 +- src/native/libs/System.Native/entrypoints.c | 3 + src/native/libs/System.Native/pal_threading.c | 70 ++++++ src/native/libs/System.Native/pal_threading.h | 6 + 8 files changed, 265 insertions(+), 58 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Unix/System.Native/Interop.PThreadMutex.cs diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs index f3769ddf447d22..f9c4668a5dda43 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs @@ -3,23 +3,53 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; +using System.Runtime.Versioning; using System.Text; using Microsoft.Win32.SafeHandles; namespace System.Threading { - internal unsafe struct SharedMemoryId + [UnsupportedOSPlatform("windows")] + internal readonly unsafe struct SharedMemoryId { - public string Name { get; set; } - public bool IsSessionScope { get; set; } - public bool IsUserScope { get; set; } - public uint Uid { get; set; } + public SharedMemoryId(string name, bool isUserScope) + { + if (name.StartsWith("Global\\", StringComparison.Ordinal)) + { + IsSessionScope = false; + name = name.Substring("Global\\".Length); + } + else + { + IsSessionScope = true; + if (name.StartsWith("Local\\", StringComparison.Ordinal)) + { + name = name.Substring("Local\\".Length); + } + } + + Name = name; + + if (name.ContainsAny(['\\', '/'])) + { + throw new ArgumentException("Name cannot contain path separators after prefixes.", nameof(name)); + } + + IsUserScope = isUserScope; + Uid = IsUserScope ? Interop.Sys.GetEUid() : 0; + } + + public string Name { get; } + public bool IsSessionScope { get; } + public bool IsUserScope { get; } + public uint Uid { get; } internal readonly string GetRuntimeTempDirectoryName() { @@ -79,6 +109,7 @@ public SharedMemorySharedDataHeader(SharedMemoryType type, uint version) } } + [UnsupportedOSPlatform("windows")] [StructLayout(LayoutKind.Sequential)] internal ref struct NamedMutexSharedDataNoPThread { @@ -125,6 +156,7 @@ public bool IsAbandoned } } + [UnsupportedOSPlatform("windows")] [StructLayout(LayoutKind.Sequential)] internal unsafe ref struct NamedMutexSharedDataWithPThread { @@ -155,6 +187,7 @@ public bool IsAbandoned public void* LockAddress => Unsafe.AsPointer(ref this); } + [UnsupportedOSPlatform("windows")] internal unsafe class SharedMemoryProcessDataHeader { internal SharedMemoryId _id; @@ -187,15 +220,11 @@ public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandl bool createIfNotExist, bool acquireLockIfCreated, out bool created, - out IDisposable? creationDeletionLockFileHandle) + out AutoReleaseFileLock creationDeletionLockFileHandle) { created = false; - creationDeletionLockFileHandle = null; - SharedMemoryId id = new() - { - Name = name, - IsUserScope = isUserScope - }; + creationDeletionLockFileHandle = new AutoReleaseFileLock(new SafeFileHandle()); + SharedMemoryId id = new(name, isUserScope); nuint sharedDataUsedByteCount = (nuint)sizeof(SharedMemorySharedDataHeader) + sharedMemoryDataSize; nuint sharedDataTotalByteCount = AlignUp(sharedDataUsedByteCount, SharedMemoryHelpers.GetVirtualPageSize()); @@ -208,7 +237,7 @@ public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandl return processDataHeader; } - creationDeletionLockFileHandle = SharedMemoryManager.Instance.AcquireCreationDeletionLockFileHandle(id); + creationDeletionLockFileHandle = SharedMemoryManager.Instance.AcquireCreationDeletionLockForId(id); string sessionDirectory = Path.Combine( SharedMemoryManager.SharedFilesPath, @@ -303,7 +332,6 @@ public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandl if (!createdFile) { creationDeletionLockFileHandle.Dispose(); - creationDeletionLockFileHandle = null; } processDataHeader = new SharedMemoryProcessDataHeader( @@ -346,6 +374,7 @@ static void SetFileSize(SafeFileHandle fd, string path, long size) } } + [UnsupportedOSPlatform("windows")] internal interface ISharedMemoryProcessData { bool CanClose { get; } @@ -353,6 +382,7 @@ internal interface ISharedMemoryProcessData void Close(bool isAbruptShutdown, bool releaseSharedData); } + [UnsupportedOSPlatform("windows")] internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader header) : ISharedMemoryProcessData { private const byte SyncSystemVersion = 1; @@ -393,6 +423,12 @@ public MutexTryAcquireLockResult TryAcquireLock(int timeoutMilliseconds) return result; } + private static void AddOwnedNamedMutex(Thread currentThread, NamedMutexProcessDataBase namedMutexProcessDataBase) + { + namedMutexProcessDataBase._nextInThreadOwnedNamedMutexList = currentThread._ownedSharedNamedMutexes; + currentThread._ownedSharedNamedMutexes = namedMutexProcessDataBase; + } + public void ReleaseLock() { if (!IsLockOwnedByCurrentThread) @@ -411,6 +447,29 @@ public void ReleaseLock() ReleaseLockCore(); } + private static void RemoveOwnedNamedMutex(Thread currentThread, NamedMutexProcessDataBase namedMutexProcessDataBase) + { + if (currentThread._ownedSharedNamedMutexes == namedMutexProcessDataBase) + { + currentThread._ownedSharedNamedMutexes = namedMutexProcessDataBase._nextInThreadOwnedNamedMutexList; + } + else + { + NamedMutexProcessDataBase? previous = currentThread._ownedSharedNamedMutexes; + while (previous?._nextInThreadOwnedNamedMutexList != namedMutexProcessDataBase) + { + previous = previous?._nextInThreadOwnedNamedMutexList; + } + + if (previous is not null) + { + previous._nextInThreadOwnedNamedMutexList = namedMutexProcessDataBase._nextInThreadOwnedNamedMutexList; + } + } + + namedMutexProcessDataBase._nextInThreadOwnedNamedMutexList = null; + } + public void Abandon() { IsAbandoned = true; @@ -466,7 +525,7 @@ public virtual void Close(bool isAbruptShutdown, bool releaseSharedData) private static unsafe SharedMemoryProcessDataHeader? CreateOrOpen(string name, bool isUserScope, bool createIfNotExist, bool acquireLockIfCreated, out bool created) { - using var creationDeletionProcessLock = SharedMemoryManager.Instance.AcquireCreationDeletionLock(); + using var creationDeletionProcessLock = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); SharedMemoryProcessDataHeader? processDataHeader = SharedMemoryProcessDataHeader.CreateOrOpen( name, @@ -476,39 +535,40 @@ public virtual void Close(bool isAbruptShutdown, bool releaseSharedData) createIfNotExist, acquireLockIfCreated, out created, - out IDisposable? creationDeletionLockFileHandle); + out AutoReleaseFileLock creationDeletionLockFileScope); if (processDataHeader is null) { return null; } - using IDisposable? creationDeletionLockFileHandleScope = created ? creationDeletionLockFileHandle : null; - - if (created) - { - InitializeSharedData(SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader)); - } - - if (processDataHeader._data is null) + using (creationDeletionLockFileScope) { - if (SharedMemoryManager.UsePThreadMutexes) - { - processDataHeader._data = new NamedMutexProcessDataWithPThreads(processDataHeader); - } - else + if (created) { - processDataHeader._data = new NamedMutexProcessDataNoPThreads(processDataHeader, created); + InitializeSharedData(SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader)); } - if (created && acquireLockIfCreated) + if (processDataHeader._data is null) { - MutexTryAcquireLockResult acquireResult = ((NamedMutexProcessDataBase)processDataHeader._data).TryAcquireLock(timeoutMilliseconds: 0); - Debug.Assert(acquireResult != MutexTryAcquireLockResult.AcquiredLock); + if (SharedMemoryManager.UsePThreadMutexes) + { + processDataHeader._data = new NamedMutexProcessDataWithPThreads(processDataHeader); + } + else + { + processDataHeader._data = new NamedMutexProcessDataNoPThreads(processDataHeader, created); + } + + if (created && acquireLockIfCreated) + { + MutexTryAcquireLockResult acquireResult = ((NamedMutexProcessDataBase)processDataHeader._data).TryAcquireLock(timeoutMilliseconds: 0); + Debug.Assert(acquireResult != MutexTryAcquireLockResult.AcquiredLock); + } } - } - return processDataHeader; + return processDataHeader; + } } private static unsafe void InitializeSharedData(void* v) @@ -536,6 +596,7 @@ private static unsafe void InitializeSharedData(void* v) } } + [UnsupportedOSPlatform("windows")] internal unsafe class NamedMutexProcessDataWithPThreads : NamedMutexProcessDataBase { private NamedMutexSharedDataWithPThread* _sharedData; @@ -581,7 +642,17 @@ protected override void ReleaseSharedData() protected override MutexTryAcquireLockResult AcquireLockCore(int timeoutMilliseconds) { - MutexTryAcquireLockResult result = Interop.Sys.PThreadMutex_TryAcquireLock(_sharedData->LockAddress, timeoutMilliseconds); + Interop.Error lockResult = (Interop.Error)Interop.Sys.PThreadMutex_Acquire(_sharedData->LockAddress, 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) { @@ -604,7 +675,7 @@ protected override MutexTryAcquireLockResult AcquireLockCore(int timeoutMillisec finally { // The lock is released upon acquiring a recursive lock from the thread that already owns the lock - Interop.Sys.PThreadMutex_ReleaseLock(_sharedData->LockAddress); + Interop.Sys.PThreadMutex_Release(_sharedData->LockAddress); } } @@ -615,14 +686,14 @@ protected override void ReleaseLockCore() { Debug.Assert(IsLockOwnedByCurrentThread); Debug.Assert(_lockCount == 0); - NamedMutexSharedDataWithPThread* sharedData = (NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(_processDataHeader._sharedDataHeader); - sharedData->LockOwnerProcessId = 0; - sharedData->LockOwnerThreadId = 0; + _sharedData->LockOwnerProcessId = 0; + _sharedData->LockOwnerThreadId = 0; - Interop.Sys.PThreadMutex_ReleaseLock(sharedData->LockAddress); + Interop.Sys.PThreadMutex_Release(_sharedData->LockAddress); } } + [UnsupportedOSPlatform("windows")] internal unsafe class NamedMutexProcessDataNoPThreads : NamedMutexProcessDataBase { private Lock _processLockHandle = new(); @@ -815,6 +886,7 @@ protected override unsafe MutexTryAcquireLockResult AcquireLockCore(int timeoutM } } + [UnsupportedOSPlatform("windows")] internal static class SharedMemoryHelpers { public const uint InvalidProcessId = unchecked((uint)-1); @@ -1009,7 +1081,7 @@ internal static bool EnsureDirectoryExists(string directoryPath, SharedMemoryId throw new IOException($"The directory '{directoryPath}' does not have the expected owner or permissions for system scope: {fileStatus.Uid}, {Convert.ToString(fileStatus.Mode, 8)}."); } - // For non-system directories (such as gSharedFilesPath/SHARED_MEMORY_USER_UNSCOPED_RUNTIME_TEMP_DIRECTORY_NAME), + // For non-system directories (such as SharedFilesPath/UserUnscopedRuntimeTempDirectoryName), // require the sufficient permissions and try to update them if requested to create the directory, so that // shared memory files may be shared according to its scope. @@ -1119,7 +1191,7 @@ public void SuppressRelease() public void Dispose() { - if (!_suppressed) + if (!_suppressed && !fd.IsInvalid) { Interop.Sys.FLock(fd, Interop.Sys.LockOperations.LOCK_UN); } @@ -1134,6 +1206,7 @@ internal enum MutexTryAcquireLockResult : byte AcquiredLockRecursively, } + [UnsupportedOSPlatform("windows")] internal sealed class SharedMemoryManager { [FeatureSwitchDefinition("System.Threading.NamedMutexUsePThreadMutex")] @@ -1145,7 +1218,41 @@ internal sealed class SharedMemoryManager public static string SharedFilesPath { get; } = InitalizeSharedFilesPath(); public static int SessionId { get; } = Interop.Sys.GetSid(Environment.ProcessId); - private static string InitalizeSharedFilesPath() => throw new NotImplementedException(); + private static string InitalizeSharedFilesPath() + { + if (OperatingSystem.IsApplePlatform()) + { + string? applicationGroupId = Environment.GetEnvironmentVariable("DOTNET_SHARED_MEMORY_APPLICATION_GROUP_ID"); + if (applicationGroupId is not null) + { + string sharedFilesPath = Path.Combine( + PersistedFiles.GetHomeDirectoryFromPasswd(), + ApplicationContainerBasePathSuffix, + applicationGroupId + ); + + if (File.Exists(sharedFilesPath)) + { + // If the path exists and is a file, throw an exception. + // If it's a directory, or does not exist, callers can correctly handle it. + throw new DirectoryNotFoundException(); + } + + return sharedFilesPath; + } + } + + if (OperatingSystem.IsAndroid()) + { + return "/data/local/tmp/"; + } + else + { + return "/tmp/"; + } + } + + private const string ApplicationContainerBasePathSuffix = "/Library/Group Containers/"; internal const string SharedMemoryLockFilesDirectoryName = "lockfiles"; @@ -1165,12 +1272,12 @@ internal sealed class SharedMemoryManager private SafeFileHandle? _creationDeletionLockFileHandle; private Dictionary _uidToFileHandleMap = []; - public Lock.Scope AcquireCreationDeletionLock() + public Lock.Scope AcquireCreationDeletionProcessLock() { return _creationDeletionProcessLock.EnterScope(); } - public IDisposable AcquireCreationDeletionLockFileHandle(SharedMemoryId id) + public AutoReleaseFileLock AcquireCreationDeletionLockForId(SharedMemoryId id) { Debug.Assert(_creationDeletionProcessLock.IsHeldByCurrentThread); SafeFileHandle? fd = id.IsUserScope ? GetUserScopeCreationDeletionLockFileHandle(id.Uid) : _creationDeletionLockFileHandle; @@ -1211,15 +1318,7 @@ public IDisposable AcquireCreationDeletionLockFileHandle(SharedMemoryId id) bool acquired = SharedMemoryHelpers.TryAcquireFileLock(fd, nonBlocking: true, exclusive: true); Debug.Assert(acquired); - return new CreationDeletionLockFileScope(fd); - } - - private sealed class CreationDeletionLockFileScope(SafeFileHandle fd) : IDisposable - { - public void Dispose() - { - Interop.Sys.FLock(fd, Interop.Sys.LockOperations.LOCK_UN); - } + return new AutoReleaseFileLock(fd); } public void AddUserScopeCreationDeletionLockFileHandle(uint uid, SafeFileHandle fileHandle) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index 99569377896a14..de831cd26ebb71 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -30,6 +30,8 @@ public sealed partial class Thread internal ExecutionContext? _executionContext; // this call context follows the logical thread internal SynchronizationContext? _synchronizationContext; // maintained separately from ExecutionContext + internal NamedMutexProcessDataBase? _ownedSharedNamedMutexes; // shared named mutexes owned by this thread + private string? _name; private StartHelper? _startHelper; diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PThreadMutex.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PThreadMutex.cs new file mode 100644 index 00000000000000..d9ecc83bb8407e --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PThreadMutex.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static unsafe partial class Sys + { + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PThreadMutex_Init", SetLastError = true)] + internal static partial int PThreadMutex_Init(void* mutex); + + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PThreadMutex_Acquire", SetLastError = true)] + internal static partial int PThreadMutex_Acquire(void* mutex, int timeoutMilliseconds); + + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PThreadMutex_Release", SetLastError = true)] + internal static partial int PThreadMutex_Release(void* mutex); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 9199925b5bb6b0..1c58c4bdf9f24e 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2481,6 +2481,9 @@ Common\Interop\Unix\System.Native\Interop.PathConf.cs + + Common\Interop\Unix\System.Native\Interop.PThreadMutex.cs + Common\Interop\Unix\System.Native\Interop.PosixFAdvise.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/PersistedFiles.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/PersistedFiles.Unix.cs index 651733d74ac5cf..9151a8a6568f55 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/PersistedFiles.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/PersistedFiles.Unix.cs @@ -56,6 +56,11 @@ private static void EnsureUserDirectories() // In initialization conditions, however, the "HOME" environment variable may // not yet be set. For such cases, consult with the password entry. + return GetHomeDirectoryFromPasswd(); + } + + internal static string GetHomeDirectoryFromPasswd() + { unsafe { // First try with a buffer that should suffice for 99% of cases. @@ -65,8 +70,8 @@ private static void EnsureUserDirectories() // what to do. const int BufLen = Interop.Sys.Passwd.InitialBufferSize; byte* stackBuf = stackalloc byte[BufLen]; - if (TryGetHomeDirectoryFromPasswd(stackBuf, BufLen, out userHomeDirectory)) - return userHomeDirectory; + if (TryGetHomeDirectoryFromPasswd(stackBuf, BufLen, out string? userHomeDirectory)) + return userHomeDirectory!; // Fallback to heap allocations if necessary, growing the buffer until // we succeed. TryGetHomeDirectory will throw if there's an unexpected error. @@ -78,7 +83,7 @@ private static void EnsureUserDirectories() fixed (byte* buf = &heapBuf[0]) { if (TryGetHomeDirectoryFromPasswd(buf, heapBuf.Length, out userHomeDirectory)) - return userHomeDirectory; + return userHomeDirectory!; } } } diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index 6d63c0b6888988..02d1347f2cb943 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -286,6 +286,9 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_GetUInt64OSThreadId) DllImportEntry(SystemNative_TryGetUInt32OSThreadId) DllImportEntry(SystemNative_Select) + DllImportEntry(SystemNative_PThreadMutex_Init) + DllImportEntry(SystemNative_PThreadMutex_Acquire) + DllImportEntry(SystemNative_PThreadMutex_Release) }; EXTERN_C const void* SystemResolveDllImport(const char* name); diff --git a/src/native/libs/System.Native/pal_threading.c b/src/native/libs/System.Native/pal_threading.c index 860be7121eb215..60e90fc1398b80 100644 --- a/src/native/libs/System.Native/pal_threading.c +++ b/src/native/libs/System.Native/pal_threading.c @@ -297,3 +297,73 @@ uint32_t SystemNative_TryGetUInt32OSThreadId(void) uint32_t result = (uint32_t)minipal_get_current_thread_id(); return result == 0 ? (uint32_t)-1 : result; } + +int32_t SystemNative_PThreadMutex_Init(void* mutex) +{ + pthread_mutexattr_t mutexAttributes; + int error = pthread_mutexattr_init(&mutexAttributes); + if (error != 0) + { + return error; + } + + error = pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_RECURSIVE); + assert(error == 0); + + error = pthread_mutexattr_setrobust(&mutexAttributes, PTHREAD_MUTEX_ROBUST); + assert(error == 0); + + error = pthread_mutexattr_setpshared(&mutexAttributes, PTHREAD_PROCESS_SHARED); + assert(error == 0); + + error = pthread_mutex_init((pthread_mutex_t*)mutex, &mutexAttributes); + return error; +} + +int32_t SystemNative_PThreadMutex_Acquire(void* mutex, int32_t timeoutMilliseconds) +{ + assert(mutex != NULL); + + if (timeoutMilliseconds == -1) + { + return pthread_mutex_lock((pthread_mutex_t*)mutex); + } + else if (timeoutMilliseconds == 0) + { + return pthread_mutex_trylock((pthread_mutex_t*)mutex); + } + + // Calculate the time at which a timeout should occur, and wait. Older versions of OSX don't support clock_gettime with + // CLOCK_MONOTONIC, so we instead compute the relative timeout duration, and use a relative variant of the timed wait. + struct timespec timeoutTimeSpec; +#if HAVE_CLOCK_GETTIME_NSEC_NP + timeoutTimeSpec.tv_sec = timeoutMilliseconds / 1000; + timeoutTimeSpec.tv_nsec = (timeoutMilliseconds % 1000) * 1000 * 1000; + + error = pthread_mutex_reltimedlock_np((pthread_mutex_t*)mutex, &timeoutTimeSpec); +#else +#if HAVE_PTHREAD_CONDATTR_SETCLOCK && HAVE_CLOCK_MONOTONIC + int error = clock_gettime(CLOCK_MONOTONIC, &timeoutTimeSpec); + assert(error == 0); +#else + struct timeval tv; + + error = gettimeofday(&tv, NULL); + assert(error == 0); + + timeoutTimeSpec.tv_sec = tv.tv_sec; + timeoutTimeSpec.tv_nsec = tv.tv_usec * 1000; +#endif + uint64_t nanoseconds = (uint64_t)timeoutMilliseconds * 1000 * 1000 + (uint64_t)timeoutTimeSpec.tv_nsec; + timeoutTimeSpec.tv_sec += nanoseconds / (1000 * 1000 * 1000); + timeoutTimeSpec.tv_nsec = nanoseconds % (1000 * 1000 * 1000); + + return pthread_mutex_timedlock((pthread_mutex_t*)mutex, &timeoutTimeSpec); +#endif +} + +int32_t SystemNative_PThreadMutex_Release(void* mutex) +{ + assert(mutex != NULL); + return pthread_mutex_unlock((pthread_mutex_t*)mutex); +} diff --git a/src/native/libs/System.Native/pal_threading.h b/src/native/libs/System.Native/pal_threading.h index fb79aaf5928751..c7890507e76777 100644 --- a/src/native/libs/System.Native/pal_threading.h +++ b/src/native/libs/System.Native/pal_threading.h @@ -32,3 +32,9 @@ PALEXPORT __attribute__((noreturn)) void SystemNative_Abort(void); PALEXPORT uint64_t SystemNative_GetUInt64OSThreadId(void); PALEXPORT uint32_t SystemNative_TryGetUInt32OSThreadId(void); + +PALEXPORT int32_t SystemNative_PThreadMutex_Init(void* mutex); + +PALEXPORT int32_t SystemNative_PThreadMutex_Acquire(void* mutex, int32_t timeoutMilliseconds); + +PALEXPORT int32_t SystemNative_PThreadMutex_Release(void* mutex); From 74d6db7aaea08369e4c3a2e00eb3a01e41714a33 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 10 Jul 2025 20:59:45 +0000 Subject: [PATCH 04/47] Fix process-level lock handling --- .../src/System/Threading/MutexSharedMemory.cs | 155 ++++++------------ 1 file changed, 46 insertions(+), 109 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs index f9c4668a5dda43..8fc6adbb7c9218 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs @@ -188,13 +188,14 @@ public bool IsAbandoned } [UnsupportedOSPlatform("windows")] - internal unsafe class SharedMemoryProcessDataHeader + internal sealed unsafe class SharedMemoryProcessDataHeader + where TSharedMemoryProcessData : class { internal SharedMemoryId _id; - internal ISharedMemoryProcessData? _data; - private SafeFileHandle _fileHandle; - internal SharedMemorySharedDataHeader* _sharedDataHeader; - private nuint _sharedDataTotalByteCount; + internal TSharedMemoryProcessData? _processData; + private readonly SafeFileHandle _fileHandle; + private readonly SharedMemorySharedDataHeader* _sharedDataHeader; + private readonly nuint _sharedDataTotalByteCount; public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandle, SharedMemorySharedDataHeader* sharedDataHeader, nuint sharedDataTotalByteCount) { @@ -202,17 +203,17 @@ public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandl _fileHandle = fileHandle; _sharedDataHeader = sharedDataHeader; _sharedDataTotalByteCount = sharedDataTotalByteCount; - _data = null; // Will be initialized later + _processData = null; // Will be initialized later } - public static void* GetDataPointer(SharedMemoryProcessDataHeader processDataHeader) + public static void* GetDataPointer(SharedMemoryProcessDataHeader processDataHeader) { return processDataHeader is null ? null : (void*)((byte*)processDataHeader._sharedDataHeader + sizeof(SharedMemorySharedDataHeader)); } - internal static SharedMemoryProcessDataHeader? CreateOrOpen( + internal static SharedMemoryProcessDataHeader? CreateOrOpen( string name, bool isUserScope, SharedMemorySharedDataHeader requiredSharedDataHeader, @@ -229,7 +230,7 @@ public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandl nuint sharedDataUsedByteCount = (nuint)sizeof(SharedMemorySharedDataHeader) + sharedMemoryDataSize; nuint sharedDataTotalByteCount = AlignUp(sharedDataUsedByteCount, SharedMemoryHelpers.GetVirtualPageSize()); - SharedMemoryProcessDataHeader? processDataHeader = SharedMemoryManager.Instance.FindProcessDataHeader(id); + SharedMemoryProcessDataHeader? processDataHeader = SharedMemoryManager.Instance.FindProcessDataHeader(id); if (processDataHeader is not null) { @@ -334,7 +335,7 @@ public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandl creationDeletionLockFileHandle.Dispose(); } - processDataHeader = new SharedMemoryProcessDataHeader( + processDataHeader = new SharedMemoryProcessDataHeader( id, fileHandle, sharedDataHeader, @@ -375,24 +376,15 @@ static void SetFileSize(SafeFileHandle fd, string path, long size) } [UnsupportedOSPlatform("windows")] - internal interface ISharedMemoryProcessData - { - bool CanClose { get; } - bool HasImplicitRef { get; set; } - void Close(bool isAbruptShutdown, bool releaseSharedData); - } - - [UnsupportedOSPlatform("windows")] - internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader header) : ISharedMemoryProcessData + internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader header) { private const byte SyncSystemVersion = 1; protected const int PollLoopMaximumSleepMilliseconds = 100; - private SharedMemoryProcessDataHeader _processDataHeader = header; + private SharedMemoryProcessDataHeader _processDataHeader = header; protected nuint _lockCount; private Thread? _lockOwnerThread; private NamedMutexProcessDataBase? _nextInThreadOwnedNamedMutexList; - private bool _hasRefFromLockOwnerThread; protected SharedMemoryId Id => _processDataHeader._id; @@ -476,11 +468,6 @@ public void Abandon() _lockCount = 0; _lockOwnerThread = null; ReleaseLockCore(); - - if (_hasRefFromLockOwnerThread) - { - _hasRefFromLockOwnerThread = false; - } } protected abstract MutexTryAcquireLockResult AcquireLockCore(int timeoutMilliseconds); @@ -489,45 +476,19 @@ public void Abandon() protected abstract void ReleaseLockCore(); - public bool CanClose => _lockOwnerThread == null || _lockOwnerThread == Thread.CurrentThread; - - public bool HasImplicitRef - { - get => _hasRefFromLockOwnerThread; - set => _hasRefFromLockOwnerThread = value; - } - - public virtual void Close(bool isAbruptShutdown, bool releaseSharedData) + ~NamedMutexProcessDataBase() { - if (!isAbruptShutdown) + if (_lockOwnerThread is not null) { - Debug.Assert(CanClose); - Debug.Assert(!_hasRefFromLockOwnerThread); - - if (_lockOwnerThread == Thread.CurrentThread) - { - RemoveOwnedNamedMutex(Thread.CurrentThread, this); - Abandon(); - } - else - { - Debug.Assert(_lockOwnerThread == null); - } - - if (releaseSharedData) - { - ReleaseSharedData(); - } + Abandon(); } } - protected abstract void ReleaseSharedData(); - - private static unsafe SharedMemoryProcessDataHeader? CreateOrOpen(string name, bool isUserScope, bool createIfNotExist, bool acquireLockIfCreated, out bool created) + private static unsafe SharedMemoryProcessDataHeader? CreateOrOpen(string name, bool isUserScope, bool createIfNotExist, bool acquireLockIfCreated, out bool created) { - using var creationDeletionProcessLock = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); + using Lock.Scope creationDeletionProcessLock = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); - SharedMemoryProcessDataHeader? processDataHeader = SharedMemoryProcessDataHeader.CreateOrOpen( + SharedMemoryProcessDataHeader? processDataHeader = SharedMemoryProcessDataHeader.CreateOrOpen( name, isUserScope, new SharedMemorySharedDataHeader(SharedMemoryType.Mutex, SyncSystemVersion), @@ -546,23 +507,23 @@ public virtual void Close(bool isAbruptShutdown, bool releaseSharedData) { if (created) { - InitializeSharedData(SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader)); + InitializeSharedData(SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader)); } - if (processDataHeader._data is null) + if (processDataHeader._processData is null) { if (SharedMemoryManager.UsePThreadMutexes) { - processDataHeader._data = new NamedMutexProcessDataWithPThreads(processDataHeader); + processDataHeader._processData = new NamedMutexProcessDataWithPThreads(processDataHeader); } else { - processDataHeader._data = new NamedMutexProcessDataNoPThreads(processDataHeader, created); + processDataHeader._processData = new NamedMutexProcessDataNoPThreads(processDataHeader, created); } if (created && acquireLockIfCreated) { - MutexTryAcquireLockResult acquireResult = ((NamedMutexProcessDataBase)processDataHeader._data).TryAcquireLock(timeoutMilliseconds: 0); + MutexTryAcquireLockResult acquireResult = processDataHeader._processData.TryAcquireLock(timeoutMilliseconds: 0); Debug.Assert(acquireResult != MutexTryAcquireLockResult.AcquiredLock); } } @@ -597,14 +558,9 @@ private static unsafe void InitializeSharedData(void* v) } [UnsupportedOSPlatform("windows")] - internal unsafe class NamedMutexProcessDataWithPThreads : NamedMutexProcessDataBase + internal unsafe class NamedMutexProcessDataWithPThreads(SharedMemoryProcessDataHeader processDataHeader) : NamedMutexProcessDataBase(processDataHeader) { - private NamedMutexSharedDataWithPThread* _sharedData; - public NamedMutexProcessDataWithPThreads(SharedMemoryProcessDataHeader processDataHeader) : base(processDataHeader) - { - // Initialize the shared data header for pthread mutexes. - _sharedData = (NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader); - } + private readonly NamedMutexSharedDataWithPThread* _sharedData = (NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader); public override bool IsLockOwnedByCurrentThread { @@ -621,25 +577,12 @@ protected override void SetLockOwnerToCurrentThread() _sharedData->LockOwnerThreadId = (uint)Thread.CurrentThread.ManagedThreadId; } - private bool IsLockOwnedByAnyThread - { - get - { - return _sharedData->LockOwnerProcessId != SharedMemoryHelpers.InvalidProcessId && - _sharedData->LockOwnerThreadId != SharedMemoryHelpers.InvalidThreadId; - } - } - protected override bool IsAbandoned { get => _sharedData->IsAbandoned; set => _sharedData->IsAbandoned = value; } - protected override void ReleaseSharedData() - { - } - protected override MutexTryAcquireLockResult AcquireLockCore(int timeoutMilliseconds) { Interop.Error lockResult = (Interop.Error)Interop.Sys.PThreadMutex_Acquire(_sharedData->LockAddress, timeoutMilliseconds); @@ -696,14 +639,14 @@ protected override void ReleaseLockCore() [UnsupportedOSPlatform("windows")] internal unsafe class NamedMutexProcessDataNoPThreads : NamedMutexProcessDataBase { - private Lock _processLockHandle = new(); - private SafeFileHandle _sharedLockFileHandle; + private readonly Lock _processLevelLock = new(); + private readonly SafeFileHandle _sharedLockFileHandle; - private NamedMutexSharedDataNoPThread* _sharedData; + private readonly NamedMutexSharedDataNoPThread* _sharedData; - public NamedMutexProcessDataNoPThreads(SharedMemoryProcessDataHeader processDataHeader, bool created) : base(processDataHeader) + public NamedMutexProcessDataNoPThreads(SharedMemoryProcessDataHeader processDataHeader, bool created) : base(processDataHeader) { - _sharedData = (NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader); + _sharedData = (NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader); string lockFileDirectory = Path.Combine( SharedMemoryManager.SharedFilesPath, Id.GetRuntimeTempDirectoryName(), @@ -758,7 +701,7 @@ protected override void ReleaseLockCore() _sharedData->LockOwnerThreadId = 0; Interop.Sys.FLock(_sharedLockFileHandle, Interop.Sys.LockOperations.LOCK_UN); - _processLockHandle.Exit(); + _processLevelLock.Exit(); } protected override bool IsAbandoned @@ -767,15 +710,8 @@ protected override bool IsAbandoned set => _sharedData->IsAbandoned = value; } - public override void Close(bool isAbruptShutdown, bool releaseSharedData) + ~NamedMutexProcessDataNoPThreads() { - base.Close(isAbruptShutdown, releaseSharedData); - - if (!releaseSharedData) - { - return; - } - string sessionDirectory = Path.Combine( SharedMemoryManager.SharedFilesPath, Id.GetRuntimeTempDirectoryName(), @@ -796,12 +732,6 @@ public override void Close(bool isAbruptShutdown, bool releaseSharedData) } } - protected override void ReleaseSharedData() - { - _sharedLockFileHandle.Dispose(); - _sharedLockFileHandle = null!; - } - protected override unsafe MutexTryAcquireLockResult AcquireLockCore(int timeoutMilliseconds) { int startTime = 0; @@ -810,7 +740,9 @@ protected override unsafe MutexTryAcquireLockResult AcquireLockCore(int timeoutM startTime = Environment.TickCount; } - using var lockScope = _processLockHandle.EnterScope(); + // 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. + using Lock.Scope scope = _processLevelLock.EnterScope(); if (_lockCount > 0) { @@ -878,6 +810,11 @@ protected override unsafe MutexTryAcquireLockResult AcquireLockCore(int timeoutM } } + // We've now acquired the lock. + // We're going to release the scoped process lock we took above, + // so recursively acquire it to maintain the lock ownership. + _processLevelLock.Enter(); + // Detect abandoned lock that isn't marked as abandoned. if (IsLockOwnedByAnyThread) return MutexTryAcquireLockResult.AcquiredLockButMutexWasAbandoned; @@ -1332,21 +1269,21 @@ public void AddUserScopeCreationDeletionLockFileHandle(uint uid, SafeFileHandle return fileHandle; } - private Dictionary _processDataHeaders = []; + private Dictionary> _processDataHeaders = []; - public void AddProcessDataHeader(SharedMemoryProcessDataHeader processDataHeader) + public void AddProcessDataHeader(SharedMemoryProcessDataHeader processDataHeader) { _processDataHeaders[processDataHeader._id] = processDataHeader; } - public void RemoveProcessDataHeader(SharedMemoryProcessDataHeader processDataHeader) + public void RemoveProcessDataHeader(SharedMemoryProcessDataHeader processDataHeader) { _processDataHeaders.Remove(processDataHeader._id); } - public SharedMemoryProcessDataHeader? FindProcessDataHeader(SharedMemoryId id) + public SharedMemoryProcessDataHeader? FindProcessDataHeader(SharedMemoryId id) { - _processDataHeaders.TryGetValue(id, out SharedMemoryProcessDataHeader? header); + _processDataHeaders.TryGetValue(id, out SharedMemoryProcessDataHeader? header); return header; } } From ab7b688bd72d763f55ad52d1f3327b88d0f2ebb5 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 10 Jul 2025 21:39:00 +0000 Subject: [PATCH 05/47] Split into two files and fill out other entrypoints --- .../System.Private.CoreLib.csproj | 1 + .../src/System/IO/SharedMemoryManager.cs | 749 +++++++++++++++ .../src/System/Threading/MutexSharedMemory.cs | 887 ++---------------- .../Unix/System.Native/Interop.GetPageSize.cs | 13 + .../System.Native/Interop.PThreadMutex.cs | 4 + .../System.Private.CoreLib.Shared.projitems | 3 + src/native/libs/System.Native/entrypoints.c | 2 + src/native/libs/System.Native/pal_process.c | 7 + src/native/libs/System.Native/pal_process.h | 2 + src/native/libs/System.Native/pal_threading.c | 5 + src/native/libs/System.Native/pal_threading.h | 2 + 11 files changed, 865 insertions(+), 810 deletions(-) create mode 100644 src/coreclr/System.Private.CoreLib/src/System/IO/SharedMemoryManager.cs create mode 100644 src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetPageSize.cs diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index 9be8e337ea7be8..9238ce848040f0 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -290,6 +290,7 @@ + diff --git a/src/coreclr/System.Private.CoreLib/src/System/IO/SharedMemoryManager.cs b/src/coreclr/System.Private.CoreLib/src/System/IO/SharedMemoryManager.cs new file mode 100644 index 00000000000000..decd4facf26b6f --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/IO/SharedMemoryManager.cs @@ -0,0 +1,749 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using System.Runtime.Versioning; +using System.Text; +using System.Threading; +using Microsoft.Win32.SafeHandles; + +namespace System.IO +{ + [UnsupportedOSPlatform("windows")] + internal readonly unsafe struct SharedMemoryId + { + private const string UserUnscopedRuntimeTempDirectoryName = ".dotnet"; + + private const string UserScopedRuntimeTempDirectoryName = ".dotnet-uid"; + + private const string SharedMemoryGlobalDirectoryName = "global"; + + private const string SharedMemorySessionDirectoryName = "session"; + private static int SessionId { get; } = Interop.Sys.GetSid(Environment.ProcessId); + + public SharedMemoryId(string name, bool isUserScope) + { + if (name.StartsWith("Global\\", StringComparison.Ordinal)) + { + IsSessionScope = false; + name = name.Substring("Global\\".Length); + } + else + { + IsSessionScope = true; + if (name.StartsWith("Local\\", StringComparison.Ordinal)) + { + name = name.Substring("Local\\".Length); + } + } + + Name = name; + + if (name.ContainsAny(['\\', '/'])) + { + throw new ArgumentException("Name cannot contain path separators after prefixes.", nameof(name)); + } + + IsUserScope = isUserScope; + Uid = IsUserScope ? Interop.Sys.GetEUid() : 0; + } + + public string Name { get; } + public bool IsSessionScope { get; } + public bool IsUserScope { get; } + public uint Uid { get; } + + internal readonly string GetRuntimeTempDirectoryName() + { + if (IsUserScope) + { + return $"{UserScopedRuntimeTempDirectoryName}{Uid}"; + } + else + { + return UserUnscopedRuntimeTempDirectoryName; + } + } + + internal readonly string GetSessionDirectoryName() + { + if (IsSessionScope) + { + return $"{SharedMemorySessionDirectoryName}{SessionId}"; + } + else + { + return SharedMemoryGlobalDirectoryName; + } + } + } + + internal enum SharedMemoryType : byte + { + Mutex + } + + [StructLayout(LayoutKind.Explicit)] + internal struct SharedMemorySharedDataHeader + { + private struct SharedMemoryAndVersion + { + public SharedMemoryType Type; + public uint Version; + } + + [FieldOffset(0)] + private SharedMemoryAndVersion _data; + + [FieldOffset(0)] + private ulong _raw; + + public readonly SharedMemoryType Type => _data.Type; + public readonly uint Version => _data.Version; + + public SharedMemorySharedDataHeader(SharedMemoryType type, uint version) + { + _data = new SharedMemoryAndVersion + { + Type = type, + Version = version + }; + } + } + + [UnsupportedOSPlatform("windows")] + internal sealed unsafe class SharedMemoryProcessDataHeader + where TSharedMemoryProcessData : class + { + internal SharedMemoryId _id; + internal TSharedMemoryProcessData? _processData; + private readonly SafeFileHandle _fileHandle; + private readonly SharedMemorySharedDataHeader* _sharedDataHeader; + private readonly nuint _sharedDataTotalByteCount; + + public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandle, SharedMemorySharedDataHeader* sharedDataHeader, nuint sharedDataTotalByteCount) + { + _id = id; + _fileHandle = fileHandle; + _sharedDataHeader = sharedDataHeader; + _sharedDataTotalByteCount = sharedDataTotalByteCount; + _processData = null; // Will be initialized later + } + + public static void* GetDataPointer(SharedMemoryProcessDataHeader processDataHeader) + { + return processDataHeader is null + ? null + : (void*)((byte*)processDataHeader._sharedDataHeader + sizeof(SharedMemorySharedDataHeader)); + } + + internal static SharedMemoryProcessDataHeader? CreateOrOpen( + string name, + bool isUserScope, + SharedMemorySharedDataHeader requiredSharedDataHeader, + nuint sharedMemoryDataSize, + bool createIfNotExist, + bool acquireLockIfCreated, + out bool created, + out AutoReleaseFileLock creationDeletionLockFileHandle) + { + created = false; + creationDeletionLockFileHandle = new AutoReleaseFileLock(new SafeFileHandle()); + SharedMemoryId id = new(name, isUserScope); + + nuint sharedDataUsedByteCount = (nuint)sizeof(SharedMemorySharedDataHeader) + sharedMemoryDataSize; + nuint sharedDataTotalByteCount = AlignUp(sharedDataUsedByteCount, (nuint)Interop.Sys.GetPageSize()); + + SharedMemoryProcessDataHeader? processDataHeader = SharedMemoryManager.Instance.FindProcessDataHeader(id); + + if (processDataHeader is not null) + { + Debug.Assert(processDataHeader._sharedDataTotalByteCount == sharedDataTotalByteCount); + return processDataHeader; + } + + creationDeletionLockFileHandle = SharedMemoryManager.Instance.AcquireCreationDeletionLockForId(id); + + string sessionDirectory = Path.Combine( + SharedMemoryHelpers.SharedFilesPath, + id.GetRuntimeTempDirectoryName(), + SharedMemoryManager.SharedMemorySharedMemoryDirectoryName, + id.GetSessionDirectoryName() + ); + + if (!SharedMemoryHelpers.EnsureDirectoryExists(sessionDirectory, id, isGlobalLockAcquired: true, createIfNotExist)) + { + Debug.Assert(!createIfNotExist); + return null; + } + + string sharedMemoryFilePath = Path.Combine(sessionDirectory, id.Name); + + SafeFileHandle fileHandle = SharedMemoryHelpers.CreateOrOpenFile(sharedMemoryFilePath, id, createIfNotExist, out bool createdFile); + if (fileHandle.IsInvalid) + { + return null; + } + + bool clearContents = false; + if (!createdFile) + { + // A shared file lock on the shared memory file would be held by any process that has opened the same file. Try to take + // an exclusive lock on the file. Successfully acquiring an exclusive lock indicates that no process has a reference to + // the shared memory file, and this process can reinitialize its contents. + if (SharedMemoryHelpers.TryAcquireFileLock(fileHandle, nonBlocking: true)) + { + // The shared memory file is not being used, flag it as created so that its contents will be reinitialized + Interop.Sys.FLock(fileHandle, Interop.Sys.LockOperations.LOCK_UN); + if (!createIfNotExist) + { + return null; + } + createdFile = true; + clearContents = true; + } + } + + if (createdFile) + { + SetFileSize(fileHandle, sharedMemoryFilePath, (long)sharedDataTotalByteCount); + } + else + { + if (Interop.Sys.FStat(fileHandle, out Interop.Sys.FileStatus fileStatus) != 0) + { + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath); + } + + if (fileStatus.Size < (long)sharedDataUsedByteCount) + { + throw new InvalidOperationException("Header mismatch"); + } + + SetFileSize(fileHandle, sharedMemoryFilePath, (long)sharedDataTotalByteCount); + } + + // Acquire and hold a shared file lock on the shared memory file as long as it is open, to indicate that this process is + // using the file. An exclusive file lock is attempted above to detect whether the file contents are valid, for the case + // where a process crashes or is killed after the file is created. Since we already hold the creation/deletion locks, a + // non-blocking file lock should succeed. + + if (!SharedMemoryHelpers.TryAcquireFileLock(fileHandle, nonBlocking: true, exclusive: false)) + { + Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); + throw Interop.GetExceptionForIoErrno(errorInfo, sharedMemoryFilePath); + } + + using AutoReleaseFileLock autoReleaseFileLock = new(fileHandle); + + using MemoryMappedFileHolder memory = SharedMemoryHelpers.MemoryMapFile(fileHandle, sharedDataTotalByteCount); + + SharedMemorySharedDataHeader* sharedDataHeader = (SharedMemorySharedDataHeader*)memory.Pointer; + if (createdFile && clearContents) + { + NativeMemory.Clear(memory.Pointer, sharedDataUsedByteCount); + *sharedDataHeader = requiredSharedDataHeader; + } + else + { + if (sharedDataHeader->Type != requiredSharedDataHeader.Type || + sharedDataHeader->Version != requiredSharedDataHeader.Version) + { + throw new InvalidOperationException("Header mismatch"); + } + } + + if (!createdFile) + { + creationDeletionLockFileHandle.Dispose(); + } + + processDataHeader = new SharedMemoryProcessDataHeader( + id, + fileHandle, + sharedDataHeader, + sharedDataTotalByteCount + ); + + autoReleaseFileLock.SuppressRelease(); + memory.SuppressRelease(); + + if (createdFile) + { + created = true; + } + + return processDataHeader; + + static nuint AlignUp(nuint value, nuint alignment) + { + nuint alignMask = alignment - 1; + return (nuint)((value + alignMask) & ~alignMask); + } + + static void SetFileSize(SafeFileHandle fd, string path, long size) + { + if (Interop.Sys.FTruncate(fd, 0) < 0) + { + Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); + if (errorInfo.Error != Interop.Error.EBADF && errorInfo.Error != Interop.Error.EINVAL) + { + // We know the file descriptor is valid and we know the size argument to FTruncate is correct, + // so if EBADF or EINVAL is returned, it means we're dealing with a special file that can't be + // truncated. Ignore the error in such cases; in all others, throw. + throw Interop.GetExceptionForIoErrno(errorInfo, path); + } + } + } + } + } + + [UnsupportedOSPlatform("windows")] + internal static class SharedMemoryHelpers + { + public const uint InvalidProcessId = unchecked((uint)-1); + public const uint InvalidThreadId = unchecked((uint)-1); + + private const UnixFileMode PermissionsMask_OwnerUser_ReadWrite = UnixFileMode.UserRead | UnixFileMode.UserWrite; + private const UnixFileMode PermissionsMask_OwnerUser_ReadWriteExecute = PermissionsMask_OwnerUser_ReadWrite | UnixFileMode.UserExecute; + private const UnixFileMode PermissionsMask_NonOwnerUsers_Write = UnixFileMode.GroupWrite | UnixFileMode.OtherWrite; + private const UnixFileMode PermissionsMask_AllUsers_ReadWrite = UnixFileMode.UserRead | UnixFileMode.UserRead | UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.OtherRead | UnixFileMode.OtherWrite; + private const UnixFileMode PermissionsMask_AllUsers_ReadWriteExecute = PermissionsMask_AllUsers_ReadWrite | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; + private const UnixFileMode PermissionsMask_Sticky = UnixFileMode.StickyBit; + + private const string SharedMemoryUniqueTempNameTemplate = ".dotnet.XXXXXX"; + private const string ApplicationContainerBasePathSuffix = "/Library/Group Containers/"; + + public static string SharedFilesPath { get; } = InitalizeSharedFilesPath(); + private static string InitalizeSharedFilesPath() + { + if (OperatingSystem.IsApplePlatform()) + { + string? applicationGroupId = Environment.GetEnvironmentVariable("DOTNET_SHARED_MEMORY_APPLICATION_GROUP_ID"); + if (applicationGroupId is not null) + { + string sharedFilesPath = Path.Combine( + PersistedFiles.GetHomeDirectoryFromPasswd(), + ApplicationContainerBasePathSuffix, + applicationGroupId + ); + + if (File.Exists(sharedFilesPath)) + { + // If the path exists and is a file, throw an exception. + // If it's a directory, or does not exist, callers can correctly handle it. + throw new DirectoryNotFoundException(); + } + + return sharedFilesPath; + } + } + + if (OperatingSystem.IsAndroid()) + { + return "/data/local/tmp/"; + } + else + { + return "/tmp/"; + } + } + + internal static SafeFileHandle CreateOrOpenFile(string sharedMemoryFilePath, SharedMemoryId id, bool createIfNotExist, out bool createdFile) + { + SafeFileHandle fd = Interop.Sys.Open(sharedMemoryFilePath, Interop.Sys.OpenFlags.O_RDWR, 0); + if (!fd.IsInvalid) + { + if (id.IsUserScope) + { + if (Interop.Sys.FStat(fd, out Interop.Sys.FileStatus fileStatus) != 0) + { + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + fd.Dispose(); + throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath); + } + + if (fileStatus.Uid != id.Uid) + { + fd.Dispose(); + throw new IOException($"The file '{sharedMemoryFilePath}' is not owned by the current user with UID {id.Uid}."); + } + + if ((fileStatus.Mode & (int)PermissionsMask_AllUsers_ReadWriteExecute) != (int)PermissionsMask_OwnerUser_ReadWrite) + { + fd.Dispose(); + throw new IOException($"The file '{sharedMemoryFilePath}' does not have the expected permissions for user scope: {PermissionsMask_OwnerUser_ReadWrite}."); + } + } + createdFile = false; + return fd; + } + + Debug.Assert(Interop.Sys.GetLastError() == Interop.Error.ENOENT); + if (!createIfNotExist) + { + createdFile = false; + return fd; + } + + fd.Dispose(); + + UnixFileMode permissionsMask = id.IsUserScope + ? PermissionsMask_OwnerUser_ReadWrite + : PermissionsMask_AllUsers_ReadWrite; + + fd = Interop.Sys.Open( + sharedMemoryFilePath, + Interop.Sys.OpenFlags.O_RDWR | Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL, + (int)permissionsMask); + + int result = Interop.Sys.FChMod(fd, (int)permissionsMask); + + if (result != 0) + { + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + fd.Dispose(); + Interop.Sys.Unlink(sharedMemoryFilePath); + throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath); + } + + createdFile = true; + return fd; + } + + internal static bool EnsureDirectoryExists(string directoryPath, SharedMemoryId id, bool isGlobalLockAcquired, bool createIfNotExist = true, bool isSystemDirectory = false) + { + UnixFileMode permissionsMask = id.IsUserScope + ? PermissionsMask_OwnerUser_ReadWriteExecute + : PermissionsMask_AllUsers_ReadWriteExecute; + + int statResult = Interop.Sys.Stat(directoryPath, out Interop.Sys.FileStatus fileStatus); + + if (statResult != 0 && Interop.Sys.GetLastError() == Interop.Error.ENOENT) + { + if (!createIfNotExist) + { + // The directory does not exist and we are not allowed to create it. + return false; + } + + // The path does not exist, create the directory. The permissions mask passed to mkdir() is filtered by the process' + // permissions umask, so mkdir() may not set all of the requested permissions. We need to use chmod() to set the proper + // permissions. That creates a race when there is no global lock acquired when creating the directory. Another user's + // process may create the directory and this user's process may try to use it before the other process sets the full + // permissions. In that case, create a temporary directory first, set the permissions, and rename it to the actual + // directory name. + + if (isGlobalLockAcquired) + { + Directory.CreateDirectory(directoryPath, permissionsMask); + + try + { + FileSystem.SetUnixFileMode(directoryPath, permissionsMask); + } + catch (Exception) + { + Directory.Delete(directoryPath); + throw; + } + + return true; + } + + string tempPath = Path.Combine(SharedFilesPath, SharedMemoryUniqueTempNameTemplate); + + unsafe + { + byte* tempPathPtr = Utf8StringMarshaller.ConvertToUnmanaged(tempPath); + if (Interop.Sys.MkdTemp(tempPathPtr) == null) + { + Utf8StringMarshaller.Free(tempPathPtr); + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + throw Interop.GetExceptionForIoErrno(error, tempPath); + } + // Convert the path back to get the substituted path. + tempPath = Utf8StringMarshaller.ConvertToManaged(tempPathPtr)!; + Utf8StringMarshaller.Free(tempPathPtr); + } + + try + { + FileSystem.SetUnixFileMode(tempPath, permissionsMask); + } + catch (Exception) + { + Directory.Delete(tempPath); + throw; + } + + if (Interop.Sys.Rename(tempPath, directoryPath) == 0) + { + return true; + } + + // Another process may have beaten us to it. Delete the temp directory and continue to check the requested directory to + // see if it meets our needs. + Directory.Delete(tempPath); + statResult = Interop.Sys.Stat(directoryPath, out fileStatus); + } + + // If the path exists, check that it's a directory + if (statResult != 0 || (fileStatus.Mode & Interop.Sys.FileTypes.S_IFDIR) == 0) + { + if (statResult != 0) + { + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + if (error.Error != Interop.Error.ENOENT) + { + throw Interop.GetExceptionForIoErrno(error, directoryPath); + } + } + else + { + throw new IOException($"The path '{directoryPath}' exists but is not a directory."); + } + } + + if (isSystemDirectory) + { + // For system directories (such as TEMP_DIRECTORY_PATH), require sufficient permissions only for the + // owner user. For instance, "docker run --mount ..." to mount /tmp to some directory on the host mounts the + // destination directory with the same permissions as the source directory, which may not include some permissions for + // other users. In the docker container, other user permissions are typically not relevant and relaxing the permissions + // requirement allows for that scenario to work without having to work around it by first giving sufficient permissions + // for all users. + // + // If the directory is being used for user-scoped shared memory data, also ensure that either it has the sticky bit or + // it's owned by the current user and without write access for other users. + + permissionsMask = PermissionsMask_OwnerUser_ReadWriteExecute; + if ((fileStatus.Mode & (int)permissionsMask) == (int)permissionsMask + && ( + !id.IsUserScope || + (fileStatus.Mode & (int)PermissionsMask_Sticky) == (int)PermissionsMask_Sticky || + (fileStatus.Uid == id.Uid && (fileStatus.Mode & (int)PermissionsMask_NonOwnerUsers_Write) == 0) + )) + { + return true; + } + + throw new IOException($"The directory '{directoryPath}' does not have the expected owner or permissions for system scope: {fileStatus.Uid}, {Convert.ToString(fileStatus.Mode, 8)}."); + } + + // For non-system directories (such as SharedFilesPath/UserUnscopedRuntimeTempDirectoryName), + // require the sufficient permissions and try to update them if requested to create the directory, so that + // shared memory files may be shared according to its scope. + + // For user-scoped directories, verify the owner UID + if (id.IsUserScope && fileStatus.Uid != id.Uid) + { + throw new IOException($"The directory '{directoryPath}' is not owned by the current user with UID {id.Uid}."); + } + + // Verify the permissions, or try to change them if possible + if ((fileStatus.Mode & (int)PermissionsMask_AllUsers_ReadWriteExecute) == (int)permissionsMask + || (createIfNotExist && Interop.Sys.ChMod(directoryPath, (int)permissionsMask) == 0)) + { + return true; + } + + // We were not able to verify or set the necessary permissions. For user-scoped directories, this is treated as a failure + // since other users aren't sufficiently restricted in permissions. + if (id.IsUserScope) + { + throw new IOException($"The directory '{directoryPath}' does not have the expected permissions for user scope: {Convert.ToString(fileStatus.Mode, 8)}."); + } + + + // For user-unscoped directories, as a last resort, check that at least the owner user has full access. + permissionsMask = PermissionsMask_OwnerUser_ReadWriteExecute; + if ((fileStatus.Mode & (int)permissionsMask) != (int)permissionsMask) + { + throw new IOException($"The directory '{directoryPath}' does not have the expected owner permissions: {Convert.ToString(fileStatus.Mode, 8)}."); + } + + return true; + } + + internal static MemoryMappedFileHolder MemoryMapFile(SafeFileHandle fileHandle, nuint sharedDataTotalByteCount) + { + nint addr = Interop.Sys.MMap( + 0, + sharedDataTotalByteCount, + Interop.Sys.MemoryMappedProtections.PROT_READ | Interop.Sys.MemoryMappedProtections.PROT_WRITE, + Interop.Sys.MemoryMappedFlags.MAP_SHARED, + fileHandle, + (long)sharedDataTotalByteCount); + + if (addr == -1) + { + Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); + throw Interop.GetExceptionForIoErrno(errorInfo, "Failed to memory map the file"); + } + + return new MemoryMappedFileHolder(addr, sharedDataTotalByteCount); + } + + internal static bool TryAcquireFileLock(SafeFileHandle sharedLockFileHandle, bool nonBlocking, bool exclusive = true) + { + Interop.Sys.LockOperations lockOperation = exclusive ? Interop.Sys.LockOperations.LOCK_EX : Interop.Sys.LockOperations.LOCK_SH; + if (nonBlocking) + { + lockOperation |= Interop.Sys.LockOperations.LOCK_NB; + } + int result = Interop.Sys.FLock(sharedLockFileHandle, lockOperation); + + if (result == 0) + { + return true; + } + + Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); + if (errorInfo.Error == Interop.Error.EWOULDBLOCK) + { + return false; + } + + throw Interop.GetExceptionForIoErrno(errorInfo); + } + } + + internal unsafe ref struct MemoryMappedFileHolder(nint addr, nuint length) + { + private bool _suppressed; + + public void SuppressRelease() + { + _suppressed = true; + } + + public void Dispose() + { + if (!_suppressed) + { + Interop.Sys.MUnmap(addr, length); + } + } + + public void* Pointer => (void*)addr; + } + + internal unsafe ref struct AutoReleaseFileLock(SafeFileHandle fd) + { + private bool _suppressed; + + public void SuppressRelease() + { + _suppressed = true; + } + + public void Dispose() + { + if (!_suppressed && !fd.IsInvalid) + { + Interop.Sys.FLock(fd, Interop.Sys.LockOperations.LOCK_UN); + } + } + } + + [UnsupportedOSPlatform("windows")] + internal sealed class SharedMemoryManager + where TSharedMemoryProcessData : class + { + internal static SharedMemoryManager Instance { get; } = new SharedMemoryManager(); + + internal const string SharedMemorySharedMemoryDirectoryName = "shm"; + + private Lock _creationDeletionProcessLock = new Lock(); + private SafeFileHandle? _creationDeletionLockFileHandle; + private Dictionary _uidToFileHandleMap = []; + + public Lock.Scope AcquireCreationDeletionProcessLock() + { + return _creationDeletionProcessLock.EnterScope(); + } + + public AutoReleaseFileLock AcquireCreationDeletionLockForId(SharedMemoryId id) + { + Debug.Assert(_creationDeletionProcessLock.IsHeldByCurrentThread); + SafeFileHandle? fd = id.IsUserScope ? GetUserScopeCreationDeletionLockFileHandle(id.Uid) : _creationDeletionLockFileHandle; + if (fd is null) + { + if (!SharedMemoryHelpers.EnsureDirectoryExists(SharedMemoryHelpers.SharedFilesPath, id, isGlobalLockAcquired: false, createIfNotExist: false, isSystemDirectory: true)) + { + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + throw Interop.GetExceptionForIoErrno(error, SharedMemoryHelpers.SharedFilesPath); + } + string runtimeTempDirectory = Path.Combine( + SharedMemoryHelpers.SharedFilesPath, + id.GetRuntimeTempDirectoryName()); + + SharedMemoryHelpers.EnsureDirectoryExists(runtimeTempDirectory, id, isGlobalLockAcquired: false); + + string sharedMemoryDirectory = Path.Combine( + runtimeTempDirectory, + SharedMemorySharedMemoryDirectoryName); + + SharedMemoryHelpers.EnsureDirectoryExists(sharedMemoryDirectory, id, isGlobalLockAcquired: false); + + fd = Interop.Sys.Open(sharedMemoryDirectory, Interop.Sys.OpenFlags.O_RDONLY, 0); + if (fd.IsInvalid) + { + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + fd.Dispose(); + throw Interop.GetExceptionForIoErrno(error, sharedMemoryDirectory); + } + } + + if (id.IsUserScope) + { + _uidToFileHandleMap.Add(id.Uid, fd); + } + + _creationDeletionLockFileHandle = fd; + + bool acquired = SharedMemoryHelpers.TryAcquireFileLock(fd, nonBlocking: true, exclusive: true); + Debug.Assert(acquired); + return new AutoReleaseFileLock(fd); + } + + public void AddUserScopeCreationDeletionLockFileHandle(uint uid, SafeFileHandle fileHandle) + { + _uidToFileHandleMap[uid] = fileHandle; + } + + public SafeFileHandle? GetUserScopeCreationDeletionLockFileHandle(uint uid) + { + _uidToFileHandleMap.TryGetValue(uid, out SafeFileHandle? fileHandle); + return fileHandle; + } + + private Dictionary> _processDataHeaders = []; + + public void AddProcessDataHeader(SharedMemoryProcessDataHeader processDataHeader) + { + _processDataHeaders[processDataHeader._id] = processDataHeader; + } + + public void RemoveProcessDataHeader(SharedMemoryProcessDataHeader processDataHeader) + { + _processDataHeaders.Remove(processDataHeader._id); + } + + public SharedMemoryProcessDataHeader? FindProcessDataHeader(SharedMemoryId id) + { + _processDataHeaders.TryGetValue(id, out SharedMemoryProcessDataHeader? header); + return header; + } + } +} diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs index 8fc6adbb7c9218..26681e097d7d68 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs @@ -16,371 +16,17 @@ namespace System.Threading { - [UnsupportedOSPlatform("windows")] - internal readonly unsafe struct SharedMemoryId - { - public SharedMemoryId(string name, bool isUserScope) - { - if (name.StartsWith("Global\\", StringComparison.Ordinal)) - { - IsSessionScope = false; - name = name.Substring("Global\\".Length); - } - else - { - IsSessionScope = true; - if (name.StartsWith("Local\\", StringComparison.Ordinal)) - { - name = name.Substring("Local\\".Length); - } - } - - Name = name; - - if (name.ContainsAny(['\\', '/'])) - { - throw new ArgumentException("Name cannot contain path separators after prefixes.", nameof(name)); - } - - IsUserScope = isUserScope; - Uid = IsUserScope ? Interop.Sys.GetEUid() : 0; - } - - public string Name { get; } - public bool IsSessionScope { get; } - public bool IsUserScope { get; } - public uint Uid { get; } - - internal readonly string GetRuntimeTempDirectoryName() - { - if (IsUserScope) - { - return $"{SharedMemoryManager.UserScopedRuntimeTempDirectoryName}{Uid}"; - } - else - { - return SharedMemoryManager.UserUnscopedRuntimeTempDirectoryName; - } - } - - internal readonly string GetSessionDirectoryName() - { - if (IsSessionScope) - { - return $"{SharedMemoryManager.SharedMemorySessionDirectoryName}{SharedMemoryManager.SessionId}"; - } - else - { - return SharedMemoryManager.SharedMemoryGlobalDirectoryName; - } - } - } - - internal enum SharedMemoryType : byte - { - Mutex - } - - [StructLayout(LayoutKind.Explicit)] - internal struct SharedMemorySharedDataHeader - { - private struct SharedMemoryAndVersion - { - public SharedMemoryType Type; - public uint Version; - } - - [FieldOffset(0)] - private SharedMemoryAndVersion _data; - - [FieldOffset(0)] - private ulong _raw; - - public readonly SharedMemoryType Type => _data.Type; - public readonly uint Version => _data.Version; - - public SharedMemorySharedDataHeader(SharedMemoryType type, uint version) - { - _data = new SharedMemoryAndVersion - { - Type = type, - Version = version - }; - } - } - - [UnsupportedOSPlatform("windows")] - [StructLayout(LayoutKind.Sequential)] - internal ref struct NamedMutexSharedDataNoPThread - { - 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; - } - } - } - - [UnsupportedOSPlatform("windows")] - [StructLayout(LayoutKind.Sequential)] - internal unsafe ref struct NamedMutexSharedDataWithPThread - { - public static readonly nuint Size; - private static readonly nuint PThreadMutexSize; - - private const uint LockOwnerProcessIdOffset = 0x0; - private const uint LockOwnerThreadIdOffset = 0x4; - private const uint IsAbandonedOffset = 0x8; - - [UnscopedRef] - public ref uint LockOwnerProcessId => ref *(uint*)((byte*)Unsafe.AsPointer(ref this) + PThreadMutexSize + LockOwnerProcessIdOffset); - [UnscopedRef] - public ref uint LockOwnerThreadId => ref *(uint*)((byte*)Unsafe.AsPointer(ref this) + PThreadMutexSize + LockOwnerThreadIdOffset); - - public bool IsAbandoned - { - get - { - return *((byte*)Unsafe.AsPointer(ref this) + PThreadMutexSize + IsAbandonedOffset) != 0; - } - set - { - *((byte*)Unsafe.AsPointer(ref this) + PThreadMutexSize + IsAbandonedOffset) = value ? (byte)1 : (byte)0; - } - } - - public void* LockAddress => Unsafe.AsPointer(ref this); - } - - [UnsupportedOSPlatform("windows")] - internal sealed unsafe class SharedMemoryProcessDataHeader - where TSharedMemoryProcessData : class - { - internal SharedMemoryId _id; - internal TSharedMemoryProcessData? _processData; - private readonly SafeFileHandle _fileHandle; - private readonly SharedMemorySharedDataHeader* _sharedDataHeader; - private readonly nuint _sharedDataTotalByteCount; - - public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandle, SharedMemorySharedDataHeader* sharedDataHeader, nuint sharedDataTotalByteCount) - { - _id = id; - _fileHandle = fileHandle; - _sharedDataHeader = sharedDataHeader; - _sharedDataTotalByteCount = sharedDataTotalByteCount; - _processData = null; // Will be initialized later - } - - public static void* GetDataPointer(SharedMemoryProcessDataHeader processDataHeader) - { - return processDataHeader is null - ? null - : (void*)((byte*)processDataHeader._sharedDataHeader + sizeof(SharedMemorySharedDataHeader)); - } - - internal static SharedMemoryProcessDataHeader? CreateOrOpen( - string name, - bool isUserScope, - SharedMemorySharedDataHeader requiredSharedDataHeader, - nuint sharedMemoryDataSize, - bool createIfNotExist, - bool acquireLockIfCreated, - out bool created, - out AutoReleaseFileLock creationDeletionLockFileHandle) - { - created = false; - creationDeletionLockFileHandle = new AutoReleaseFileLock(new SafeFileHandle()); - SharedMemoryId id = new(name, isUserScope); - - nuint sharedDataUsedByteCount = (nuint)sizeof(SharedMemorySharedDataHeader) + sharedMemoryDataSize; - nuint sharedDataTotalByteCount = AlignUp(sharedDataUsedByteCount, SharedMemoryHelpers.GetVirtualPageSize()); - - SharedMemoryProcessDataHeader? processDataHeader = SharedMemoryManager.Instance.FindProcessDataHeader(id); - - if (processDataHeader is not null) - { - Debug.Assert(processDataHeader._sharedDataTotalByteCount == sharedDataTotalByteCount); - return processDataHeader; - } - - creationDeletionLockFileHandle = SharedMemoryManager.Instance.AcquireCreationDeletionLockForId(id); - - string sessionDirectory = Path.Combine( - SharedMemoryManager.SharedFilesPath, - id.GetRuntimeTempDirectoryName(), - SharedMemoryManager.SharedMemorySharedMemoryDirectoryName, - id.GetSessionDirectoryName() - ); - - if (!SharedMemoryHelpers.EnsureDirectoryExists(sessionDirectory, id, isGlobalLockAcquired: true, createIfNotExist)) - { - Debug.Assert(!createIfNotExist); - return null; - } - - string sharedMemoryFilePath = Path.Combine(sessionDirectory, id.Name); - - SafeFileHandle fileHandle = SharedMemoryHelpers.CreateOrOpenFile(sharedMemoryFilePath, id, createIfNotExist, out bool createdFile); - if (fileHandle.IsInvalid) - { - return null; - } - - bool clearContents = false; - if (!createdFile) - { - // A shared file lock on the shared memory file would be held by any process that has opened the same file. Try to take - // an exclusive lock on the file. Successfully acquiring an exclusive lock indicates that no process has a reference to - // the shared memory file, and this process can reinitialize its contents. - if (SharedMemoryHelpers.TryAcquireFileLock(fileHandle, nonBlocking: true)) - { - // The shared memory file is not being used, flag it as created so that its contents will be reinitialized - Interop.Sys.FLock(fileHandle, Interop.Sys.LockOperations.LOCK_UN); - if (!createIfNotExist) - { - return null; - } - createdFile = true; - clearContents = true; - } - } - - if (createdFile) - { - SetFileSize(fileHandle, sharedMemoryFilePath, (long)sharedDataTotalByteCount); - } - else - { - if (Interop.Sys.FStat(fileHandle, out Interop.Sys.FileStatus fileStatus) != 0) - { - Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); - throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath); - } - - if (fileStatus.Size < (long)sharedDataUsedByteCount) - { - throw new InvalidOperationException("Header mismatch"); - } - - SetFileSize(fileHandle, sharedMemoryFilePath, (long)sharedDataTotalByteCount); - } - - // Acquire and hold a shared file lock on the shared memory file as long as it is open, to indicate that this process is - // using the file. An exclusive file lock is attempted above to detect whether the file contents are valid, for the case - // where a process crashes or is killed after the file is created. Since we already hold the creation/deletion locks, a - // non-blocking file lock should succeed. - - if (!SharedMemoryHelpers.TryAcquireFileLock(fileHandle, nonBlocking: true, exclusive: false)) - { - Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); - throw Interop.GetExceptionForIoErrno(errorInfo, sharedMemoryFilePath); - } - - using AutoReleaseFileLock autoReleaseFileLock = new(fileHandle); - - using MemoryMappedFileHolder memory = SharedMemoryHelpers.MemoryMapFile(fileHandle, sharedDataTotalByteCount); - - SharedMemorySharedDataHeader* sharedDataHeader = (SharedMemorySharedDataHeader*)memory.Pointer; - if (createdFile && clearContents) - { - NativeMemory.Clear(memory.Pointer, sharedDataUsedByteCount); - *sharedDataHeader = requiredSharedDataHeader; - } - else - { - if (sharedDataHeader->Type != requiredSharedDataHeader.Type || - sharedDataHeader->Version != requiredSharedDataHeader.Version) - { - throw new InvalidOperationException("Header mismatch"); - } - } - - if (!createdFile) - { - creationDeletionLockFileHandle.Dispose(); - } - - processDataHeader = new SharedMemoryProcessDataHeader( - id, - fileHandle, - sharedDataHeader, - sharedDataTotalByteCount - ); - - autoReleaseFileLock.SuppressRelease(); - memory.SuppressRelease(); - - if (createdFile) - { - created = true; - } - - return processDataHeader; - - static nuint AlignUp(nuint value, nuint alignment) - { - nuint alignMask = alignment - 1; - return (nuint)((value + alignMask) & ~alignMask); - } - - static void SetFileSize(SafeFileHandle fd, string path, long size) - { - if (Interop.Sys.FTruncate(fd, 0) < 0) - { - Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); - if (errorInfo.Error != Interop.Error.EBADF && errorInfo.Error != Interop.Error.EINVAL) - { - // We know the file descriptor is valid and we know the size argument to FTruncate is correct, - // so if EBADF or EINVAL is returned, it means we're dealing with a special file that can't be - // truncated. Ignore the error in such cases; in all others, throw. - throw Interop.GetExceptionForIoErrno(errorInfo, path); - } - } - } - } - } - [UnsupportedOSPlatform("windows")] internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader header) { private const byte SyncSystemVersion = 1; protected const int PollLoopMaximumSleepMilliseconds = 100; + [FeatureSwitchDefinition("System.Threading.Mutex.NamedMutexUsePThreadMutex")] + private static bool UsePThreadMutexes { get; } = AppContext.TryGetSwitch("System.Threading.Mutex.NamedMutexUsePThreadMutex", out bool value) + ? value + : false; + private SharedMemoryProcessDataHeader _processDataHeader = header; protected nuint _lockCount; private Thread? _lockOwnerThread; @@ -486,13 +132,13 @@ public void Abandon() private static unsafe SharedMemoryProcessDataHeader? CreateOrOpen(string name, bool isUserScope, bool createIfNotExist, bool acquireLockIfCreated, out bool created) { - using Lock.Scope creationDeletionProcessLock = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); + using Lock.Scope creationDeletionProcessLock = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); SharedMemoryProcessDataHeader? processDataHeader = SharedMemoryProcessDataHeader.CreateOrOpen( name, isUserScope, new SharedMemorySharedDataHeader(SharedMemoryType.Mutex, SyncSystemVersion), - SharedMemoryManager.UsePThreadMutexes ? NamedMutexSharedDataWithPThread.Size : (nuint)sizeof(NamedMutexSharedDataNoPThread), + UsePThreadMutexes ? NamedMutexProcessDataWithPThreads.SharedData.Size : (nuint)sizeof(NamedMutexProcessDataNoPThreads.SharedData), createIfNotExist, acquireLockIfCreated, out created, @@ -512,7 +158,7 @@ public void Abandon() if (processDataHeader._processData is null) { - if (SharedMemoryManager.UsePThreadMutexes) + if (UsePThreadMutexes) { processDataHeader._processData = new NamedMutexProcessDataWithPThreads(processDataHeader); } @@ -534,9 +180,9 @@ public void Abandon() private static unsafe void InitializeSharedData(void* v) { - if (SharedMemoryManager.UsePThreadMutexes) + if (UsePThreadMutexes) { - NamedMutexSharedDataWithPThread* sharedData = (NamedMutexSharedDataWithPThread*)v; + NamedMutexProcessDataWithPThreads.SharedData* sharedData = (NamedMutexProcessDataWithPThreads.SharedData*)v; if (Interop.Sys.PThreadMutex_Init(sharedData->LockAddress) != 0) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); @@ -548,7 +194,7 @@ private static unsafe void InitializeSharedData(void* v) } else { - NamedMutexSharedDataNoPThread* sharedData = (NamedMutexSharedDataNoPThread*)v; + NamedMutexProcessDataNoPThreads.SharedData* sharedData = (NamedMutexProcessDataNoPThreads.SharedData*)v; sharedData->LockOwnerProcessId = SharedMemoryHelpers.InvalidProcessId; sharedData->LockOwnerThreadId = SharedMemoryHelpers.InvalidThreadId; sharedData->IsAbandoned = false; @@ -558,9 +204,9 @@ private static unsafe void InitializeSharedData(void* v) } [UnsupportedOSPlatform("windows")] - internal unsafe class NamedMutexProcessDataWithPThreads(SharedMemoryProcessDataHeader processDataHeader) : NamedMutexProcessDataBase(processDataHeader) + internal sealed unsafe class NamedMutexProcessDataWithPThreads(SharedMemoryProcessDataHeader processDataHeader) : NamedMutexProcessDataBase(processDataHeader) { - private readonly NamedMutexSharedDataWithPThread* _sharedData = (NamedMutexSharedDataWithPThread*)SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader); + private readonly SharedData* _sharedData = (SharedData*)SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader); public override bool IsLockOwnedByCurrentThread { @@ -634,23 +280,55 @@ protected override void ReleaseLockCore() Interop.Sys.PThreadMutex_Release(_sharedData->LockAddress); } + + [UnsupportedOSPlatform("windows")] + [StructLayout(LayoutKind.Sequential)] + internal unsafe ref struct SharedData + { + public static nuint Size => (nuint)PThreadMutexSize + IsAbandonedOffset + sizeof(byte); + private static readonly int PThreadMutexSize = Interop.Sys.PThreadMutex_Size(); + + private const uint LockOwnerProcessIdOffset = 0x0; + private const uint LockOwnerThreadIdOffset = 0x4; + private const uint IsAbandonedOffset = 0x8; + + [UnscopedRef] + public ref uint LockOwnerProcessId => ref *(uint*)((byte*)Unsafe.AsPointer(ref this) + PThreadMutexSize + LockOwnerProcessIdOffset); + [UnscopedRef] + public ref uint LockOwnerThreadId => ref *(uint*)((byte*)Unsafe.AsPointer(ref this) + PThreadMutexSize + LockOwnerThreadIdOffset); + + public bool IsAbandoned + { + get + { + return *((byte*)Unsafe.AsPointer(ref this) + PThreadMutexSize + IsAbandonedOffset) != 0; + } + set + { + *((byte*)Unsafe.AsPointer(ref this) + PThreadMutexSize + IsAbandonedOffset) = value ? (byte)1 : (byte)0; + } + } + + public void* LockAddress => Unsafe.AsPointer(ref this); + } } [UnsupportedOSPlatform("windows")] - internal unsafe class NamedMutexProcessDataNoPThreads : NamedMutexProcessDataBase + internal sealed unsafe class NamedMutexProcessDataNoPThreads : NamedMutexProcessDataBase { + private const string SharedMemoryLockFilesDirectoryName = "lockfiles"; private readonly Lock _processLevelLock = new(); private readonly SafeFileHandle _sharedLockFileHandle; - private readonly NamedMutexSharedDataNoPThread* _sharedData; + private readonly SharedData* _sharedData; public NamedMutexProcessDataNoPThreads(SharedMemoryProcessDataHeader processDataHeader, bool created) : base(processDataHeader) { - _sharedData = (NamedMutexSharedDataNoPThread*)SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader); + _sharedData = (SharedData*)SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader); string lockFileDirectory = Path.Combine( - SharedMemoryManager.SharedFilesPath, + SharedMemoryHelpers.SharedFilesPath, Id.GetRuntimeTempDirectoryName(), - SharedMemoryManager.SharedMemoryLockFilesDirectoryName + SharedMemoryLockFilesDirectoryName ); if (created) @@ -713,9 +391,9 @@ protected override bool IsAbandoned ~NamedMutexProcessDataNoPThreads() { string sessionDirectory = Path.Combine( - SharedMemoryManager.SharedFilesPath, + SharedMemoryHelpers.SharedFilesPath, Id.GetRuntimeTempDirectoryName(), - SharedMemoryManager.SharedMemoryLockFilesDirectoryName, + SharedMemoryLockFilesDirectoryName, Id.GetSessionDirectoryName() ); @@ -821,316 +499,50 @@ protected override unsafe MutexTryAcquireLockResult AcquireLockCore(int timeoutM return MutexTryAcquireLockResult.AcquiredLock; } - } - - [UnsupportedOSPlatform("windows")] - internal static class SharedMemoryHelpers - { - public const uint InvalidProcessId = unchecked((uint)-1); - public const uint InvalidThreadId = unchecked((uint)-1); - - private const UnixFileMode PermissionsMask_OwnerUser_ReadWrite = UnixFileMode.UserRead | UnixFileMode.UserWrite; - private const UnixFileMode PermissionsMask_OwnerUser_ReadWriteExecute = PermissionsMask_OwnerUser_ReadWrite | UnixFileMode.UserExecute; - private const UnixFileMode PermissionsMask_NonOwnerUsers_Write = UnixFileMode.GroupWrite | UnixFileMode.OtherWrite; - private const UnixFileMode PermissionsMask_AllUsers_ReadWrite = UnixFileMode.UserRead | UnixFileMode.UserRead | UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.OtherRead | UnixFileMode.OtherWrite; - private const UnixFileMode PermissionsMask_AllUsers_ReadWriteExecute = PermissionsMask_AllUsers_ReadWrite | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; - private const UnixFileMode PermissionsMask_Sticky = UnixFileMode.StickyBit; - - internal static SafeFileHandle CreateOrOpenFile(string sharedMemoryFilePath, SharedMemoryId id, bool createIfNotExist, out bool createdFile) - { - SafeFileHandle fd = Interop.Sys.Open(sharedMemoryFilePath, Interop.Sys.OpenFlags.O_RDWR, 0); - if (!fd.IsInvalid) - { - if (id.IsUserScope) - { - if (Interop.Sys.FStat(fd, out Interop.Sys.FileStatus fileStatus) != 0) - { - Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); - fd.Dispose(); - throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath); - } - - if (fileStatus.Uid != id.Uid) - { - fd.Dispose(); - throw new IOException($"The file '{sharedMemoryFilePath}' is not owned by the current user with UID {id.Uid}."); - } - - if ((fileStatus.Mode & (int)PermissionsMask_AllUsers_ReadWriteExecute) != (int)PermissionsMask_OwnerUser_ReadWrite) - { - fd.Dispose(); - throw new IOException($"The file '{sharedMemoryFilePath}' does not have the expected permissions for user scope: {PermissionsMask_OwnerUser_ReadWrite}."); - } - } - createdFile = false; - return fd; - } - - Debug.Assert(Interop.Sys.GetLastError() == Interop.Error.ENOENT); - if (!createIfNotExist) - { - createdFile = false; - return fd; - } - - fd.Dispose(); - - UnixFileMode permissionsMask = id.IsUserScope - ? PermissionsMask_OwnerUser_ReadWrite - : PermissionsMask_AllUsers_ReadWrite; - - fd = Interop.Sys.Open( - sharedMemoryFilePath, - Interop.Sys.OpenFlags.O_RDWR | Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL, - (int)permissionsMask); - - int result = Interop.Sys.FChMod(fd, (int)permissionsMask); - - if (result != 0) - { - Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); - fd.Dispose(); - Interop.Sys.Unlink(sharedMemoryFilePath); - throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath); - } - - createdFile = true; - return fd; - } - - internal static bool EnsureDirectoryExists(string directoryPath, SharedMemoryId id, bool isGlobalLockAcquired, bool createIfNotExist = true, bool isSystemDirectory = false) + [UnsupportedOSPlatform("windows")] + [StructLayout(LayoutKind.Sequential)] + internal ref struct SharedData { - UnixFileMode permissionsMask = id.IsUserScope - ? PermissionsMask_OwnerUser_ReadWriteExecute - : PermissionsMask_AllUsers_ReadWriteExecute; + private uint _timedWaiterCount; + private uint _lockOwnerProcessId; + private uint _lockOwnerThreadId; + private byte _isAbandoned; - int statResult = Interop.Sys.Stat(directoryPath, out Interop.Sys.FileStatus fileStatus); - - if (statResult != 0 && Interop.Sys.GetLastError() == Interop.Error.ENOENT) + public uint TimedWaiterCount { get => _timedWaiterCount; set => _timedWaiterCount = value; } + public uint LockOwnerProcessId { - if (!createIfNotExist) - { - // The directory does not exist and we are not allowed to create it. - return false; - } - - // The path does not exist, create the directory. The permissions mask passed to mkdir() is filtered by the process' - // permissions umask, so mkdir() may not set all of the requested permissions. We need to use chmod() to set the proper - // permissions. That creates a race when there is no global lock acquired when creating the directory. Another user's - // process may create the directory and this user's process may try to use it before the other process sets the full - // permissions. In that case, create a temporary directory first, set the permissions, and rename it to the actual - // directory name. - - if (isGlobalLockAcquired) - { - Directory.CreateDirectory(directoryPath, permissionsMask); - - try - { - FileSystem.SetUnixFileMode(directoryPath, permissionsMask); - } - catch (Exception) - { - Directory.Delete(directoryPath); - throw; - } - - return true; - } - - string tempPath = Path.Combine(SharedMemoryManager.SharedFilesPath, SharedMemoryManager.SharedMemoryUniqueTempNameTemplate); - - unsafe - { - byte* tempPathPtr = Utf8StringMarshaller.ConvertToUnmanaged(tempPath); - if (Interop.Sys.MkdTemp(tempPathPtr) == null) - { - Utf8StringMarshaller.Free(tempPathPtr); - Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); - throw Interop.GetExceptionForIoErrno(error, tempPath); - } - // Convert the path back to get the substituted path. - tempPath = Utf8StringMarshaller.ConvertToManaged(tempPathPtr)!; - Utf8StringMarshaller.Free(tempPathPtr); - } - - try + get { - FileSystem.SetUnixFileMode(tempPath, permissionsMask); + return _lockOwnerProcessId; } - catch (Exception) - { - Directory.Delete(tempPath); - throw; - } - - if (Interop.Sys.Rename(tempPath, directoryPath) == 0) + set { - return true; + _lockOwnerProcessId = value; } - - // Another process may have beaten us to it. Delete the temp directory and continue to check the requested directory to - // see if it meets our needs. - Directory.Delete(tempPath); - statResult = Interop.Sys.Stat(directoryPath, out fileStatus); } - // If the path exists, check that it's a directory - if (statResult != 0 || (fileStatus.Mode & Interop.Sys.FileTypes.S_IFDIR) == 0) + public uint LockOwnerThreadId { - if (statResult != 0) + get { - Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); - if (error.Error != Interop.Error.ENOENT) - { - throw Interop.GetExceptionForIoErrno(error, directoryPath); - } + return _lockOwnerThreadId; } - else + set { - throw new IOException($"The path '{directoryPath}' exists but is not a directory."); + _lockOwnerThreadId = value; } } - if (isSystemDirectory) + public bool IsAbandoned { - // For system directories (such as TEMP_DIRECTORY_PATH), require sufficient permissions only for the - // owner user. For instance, "docker run --mount ..." to mount /tmp to some directory on the host mounts the - // destination directory with the same permissions as the source directory, which may not include some permissions for - // other users. In the docker container, other user permissions are typically not relevant and relaxing the permissions - // requirement allows for that scenario to work without having to work around it by first giving sufficient permissions - // for all users. - // - // If the directory is being used for user-scoped shared memory data, also ensure that either it has the sticky bit or - // it's owned by the current user and without write access for other users. - - permissionsMask = PermissionsMask_OwnerUser_ReadWriteExecute; - if ((fileStatus.Mode & (int)permissionsMask) == (int)permissionsMask - && ( - !id.IsUserScope || - (fileStatus.Mode & (int)PermissionsMask_Sticky) == (int)PermissionsMask_Sticky || - (fileStatus.Uid == id.Uid && (fileStatus.Mode & (int)PermissionsMask_NonOwnerUsers_Write) == 0) - )) + get { - return true; + return _isAbandoned != 0; + } + set + { + _isAbandoned = value ? (byte)1 : (byte)0; } - - throw new IOException($"The directory '{directoryPath}' does not have the expected owner or permissions for system scope: {fileStatus.Uid}, {Convert.ToString(fileStatus.Mode, 8)}."); - } - - // For non-system directories (such as SharedFilesPath/UserUnscopedRuntimeTempDirectoryName), - // require the sufficient permissions and try to update them if requested to create the directory, so that - // shared memory files may be shared according to its scope. - - // For user-scoped directories, verify the owner UID - if (id.IsUserScope && fileStatus.Uid != id.Uid) - { - throw new IOException($"The directory '{directoryPath}' is not owned by the current user with UID {id.Uid}."); - } - - // Verify the permissions, or try to change them if possible - if ((fileStatus.Mode & (int)PermissionsMask_AllUsers_ReadWriteExecute) == (int)permissionsMask - || (createIfNotExist && Interop.Sys.ChMod(directoryPath, (int)permissionsMask) == 0)) - { - return true; - } - - // We were not able to verify or set the necessary permissions. For user-scoped directories, this is treated as a failure - // since other users aren't sufficiently restricted in permissions. - if (id.IsUserScope) - { - throw new IOException($"The directory '{directoryPath}' does not have the expected permissions for user scope: {Convert.ToString(fileStatus.Mode, 8)}."); - } - - - // For user-unscoped directories, as a last resort, check that at least the owner user has full access. - permissionsMask = PermissionsMask_OwnerUser_ReadWriteExecute; - if ((fileStatus.Mode & (int)permissionsMask) != (int)permissionsMask) - { - throw new IOException($"The directory '{directoryPath}' does not have the expected owner permissions: {Convert.ToString(fileStatus.Mode, 8)}."); - } - - return true; - } - - internal static nuint GetVirtualPageSize() => throw new NotImplementedException(); - internal static MemoryMappedFileHolder MemoryMapFile(SafeFileHandle fileHandle, nuint sharedDataTotalByteCount) - { - nint addr = Interop.Sys.MMap( - 0, - sharedDataTotalByteCount, - Interop.Sys.MemoryMappedProtections.PROT_READ | Interop.Sys.MemoryMappedProtections.PROT_WRITE, - Interop.Sys.MemoryMappedFlags.MAP_SHARED, - fileHandle, - (long)sharedDataTotalByteCount); - - if (addr == -1) - { - Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); - throw Interop.GetExceptionForIoErrno(errorInfo, "Failed to memory map the file"); - } - - return new MemoryMappedFileHolder(addr, sharedDataTotalByteCount); - } - - internal static bool TryAcquireFileLock(SafeFileHandle sharedLockFileHandle, bool nonBlocking, bool exclusive = true) - { - Interop.Sys.LockOperations lockOperation = exclusive ? Interop.Sys.LockOperations.LOCK_EX : Interop.Sys.LockOperations.LOCK_SH; - if (nonBlocking) - { - lockOperation |= Interop.Sys.LockOperations.LOCK_NB; - } - int result = Interop.Sys.FLock(sharedLockFileHandle, lockOperation); - - if (result == 0) - { - return true; - } - - Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); - if (errorInfo.Error == Interop.Error.EWOULDBLOCK) - { - return false; - } - - throw Interop.GetExceptionForIoErrno(errorInfo); - } - } - - internal unsafe ref struct MemoryMappedFileHolder(nint addr, nuint length) - { - private bool _suppressed; - - public void SuppressRelease() - { - _suppressed = true; - } - - public void Dispose() - { - if (!_suppressed) - { - Interop.Sys.MUnmap(addr, length); - } - } - - public void* Pointer => (void*)addr; - } - - internal unsafe ref struct AutoReleaseFileLock(SafeFileHandle fd) - { - private bool _suppressed; - - public void SuppressRelease() - { - _suppressed = true; - } - - public void Dispose() - { - if (!_suppressed && !fd.IsInvalid) - { - Interop.Sys.FLock(fd, Interop.Sys.LockOperations.LOCK_UN); } } } @@ -1142,149 +554,4 @@ internal enum MutexTryAcquireLockResult : byte TimedOut, AcquiredLockRecursively, } - - [UnsupportedOSPlatform("windows")] - internal sealed class SharedMemoryManager - { - [FeatureSwitchDefinition("System.Threading.NamedMutexUsePThreadMutex")] - internal static bool UsePThreadMutexes { get; } = AppContext.TryGetSwitch("System.Threading.NamedMutexUsePThreadMutex", out bool value) - ? value - : false; - - internal static SharedMemoryManager Instance { get; } = new SharedMemoryManager(); - public static string SharedFilesPath { get; } = InitalizeSharedFilesPath(); - public static int SessionId { get; } = Interop.Sys.GetSid(Environment.ProcessId); - - private static string InitalizeSharedFilesPath() - { - if (OperatingSystem.IsApplePlatform()) - { - string? applicationGroupId = Environment.GetEnvironmentVariable("DOTNET_SHARED_MEMORY_APPLICATION_GROUP_ID"); - if (applicationGroupId is not null) - { - string sharedFilesPath = Path.Combine( - PersistedFiles.GetHomeDirectoryFromPasswd(), - ApplicationContainerBasePathSuffix, - applicationGroupId - ); - - if (File.Exists(sharedFilesPath)) - { - // If the path exists and is a file, throw an exception. - // If it's a directory, or does not exist, callers can correctly handle it. - throw new DirectoryNotFoundException(); - } - - return sharedFilesPath; - } - } - - if (OperatingSystem.IsAndroid()) - { - return "/data/local/tmp/"; - } - else - { - return "/tmp/"; - } - } - - private const string ApplicationContainerBasePathSuffix = "/Library/Group Containers/"; - - internal const string SharedMemoryLockFilesDirectoryName = "lockfiles"; - - internal const string SharedMemorySharedMemoryDirectoryName = "shm"; - - internal const string UserUnscopedRuntimeTempDirectoryName = ".dotnet"; - - internal const string UserScopedRuntimeTempDirectoryName = ".dotnet-uid"; - - internal const string SharedMemoryGlobalDirectoryName = "global"; - - internal const string SharedMemorySessionDirectoryName = "session"; - - internal const string SharedMemoryUniqueTempNameTemplate = ".dotnet.XXXXXX"; - - private Lock _creationDeletionProcessLock = new Lock(); - private SafeFileHandle? _creationDeletionLockFileHandle; - private Dictionary _uidToFileHandleMap = []; - - public Lock.Scope AcquireCreationDeletionProcessLock() - { - return _creationDeletionProcessLock.EnterScope(); - } - - public AutoReleaseFileLock AcquireCreationDeletionLockForId(SharedMemoryId id) - { - Debug.Assert(_creationDeletionProcessLock.IsHeldByCurrentThread); - SafeFileHandle? fd = id.IsUserScope ? GetUserScopeCreationDeletionLockFileHandle(id.Uid) : _creationDeletionLockFileHandle; - if (fd is null) - { - if (!SharedMemoryHelpers.EnsureDirectoryExists(SharedFilesPath, id, isGlobalLockAcquired: false, createIfNotExist: false, isSystemDirectory: true)) - { - Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); - throw Interop.GetExceptionForIoErrno(error, SharedFilesPath); - } - string runtimeTempDirectory = Path.Combine( - SharedFilesPath, - id.GetRuntimeTempDirectoryName()); - - SharedMemoryHelpers.EnsureDirectoryExists(runtimeTempDirectory, id, isGlobalLockAcquired: false); - - string sharedMemoryDirectory = Path.Combine( - runtimeTempDirectory, - SharedMemorySharedMemoryDirectoryName); - - SharedMemoryHelpers.EnsureDirectoryExists(sharedMemoryDirectory, id, isGlobalLockAcquired: false); - - fd = Interop.Sys.Open(sharedMemoryDirectory, Interop.Sys.OpenFlags.O_RDONLY, 0); - if (fd.IsInvalid) - { - Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); - fd.Dispose(); - throw Interop.GetExceptionForIoErrno(error, sharedMemoryDirectory); - } - } - - if (id.IsUserScope) - { - _uidToFileHandleMap.Add(id.Uid, fd); - } - - _creationDeletionLockFileHandle = fd; - - bool acquired = SharedMemoryHelpers.TryAcquireFileLock(fd, nonBlocking: true, exclusive: true); - Debug.Assert(acquired); - return new AutoReleaseFileLock(fd); - } - - public void AddUserScopeCreationDeletionLockFileHandle(uint uid, SafeFileHandle fileHandle) - { - _uidToFileHandleMap[uid] = fileHandle; - } - - public SafeFileHandle? GetUserScopeCreationDeletionLockFileHandle(uint uid) - { - _uidToFileHandleMap.TryGetValue(uid, out SafeFileHandle? fileHandle); - return fileHandle; - } - - private Dictionary> _processDataHeaders = []; - - public void AddProcessDataHeader(SharedMemoryProcessDataHeader processDataHeader) - { - _processDataHeaders[processDataHeader._id] = processDataHeader; - } - - public void RemoveProcessDataHeader(SharedMemoryProcessDataHeader processDataHeader) - { - _processDataHeaders.Remove(processDataHeader._id); - } - - public SharedMemoryProcessDataHeader? FindProcessDataHeader(SharedMemoryId id) - { - _processDataHeaders.TryGetValue(id, out SharedMemoryProcessDataHeader? header); - return header; - } - } } diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetPageSize.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetPageSize.cs new file mode 100644 index 00000000000000..9f4b437da883cc --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetPageSize.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Sys + { + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetPageSize")] + internal static partial int GetPageSize(); + } +} diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PThreadMutex.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PThreadMutex.cs index d9ecc83bb8407e..093d237b457f47 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PThreadMutex.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PThreadMutex.cs @@ -15,5 +15,9 @@ internal static unsafe partial class Sys [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PThreadMutex_Release", SetLastError = true)] internal static partial int PThreadMutex_Release(void* mutex); + + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PThreadMutex_Size")] + [SuppressGCTransition] + internal static partial int PThreadMutex_Size(); } } diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 1c58c4bdf9f24e..33693d567ab961 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2405,6 +2405,9 @@ Common\Interop\Unix\System.Native\Interop.GetOSArchitecture.cs + + Common\Interop\Unix\System.Native\Interop.GetPageSize.cs + Common\Interop\Unix\System.Native\Interop.GetProcessPath.cs diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index 02d1347f2cb943..3df739fa83d5ba 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -289,6 +289,8 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_PThreadMutex_Init) DllImportEntry(SystemNative_PThreadMutex_Acquire) DllImportEntry(SystemNative_PThreadMutex_Release) + DllImportEntry(SystemNative_PThreadMutex_Size) + DllImportEntry(SystemNative_GetPageSize) }; EXTERN_C const void* SystemResolveDllImport(const char* name); diff --git a/src/native/libs/System.Native/pal_process.c b/src/native/libs/System.Native/pal_process.c index fa5e522c36205e..8339515c30afff 100644 --- a/src/native/libs/System.Native/pal_process.c +++ b/src/native/libs/System.Native/pal_process.c @@ -895,3 +895,10 @@ char* SystemNative_GetProcessPath(void) { return minipal_getexepath(); } + +int32_t SystemNative_GetPageSize(void) +{ + // The page size is the size of a memory page, which is the smallest unit of memory that can be + // allocated or deallocated. It is typically 4096 bytes on most systems. + return getpagesize(); +} diff --git a/src/native/libs/System.Native/pal_process.h b/src/native/libs/System.Native/pal_process.h index 6ef66da7acf84f..2f283e624a781f 100644 --- a/src/native/libs/System.Native/pal_process.h +++ b/src/native/libs/System.Native/pal_process.h @@ -244,3 +244,5 @@ PALEXPORT int32_t SystemNative_SchedGetAffinity(int32_t pid, intptr_t* mask); * resolving symbolic links. The caller is responsible for releasing the buffer. */ PALEXPORT char* SystemNative_GetProcessPath(void); + +PALEXPORT int32_t SystemNative_GetPageSize(void); diff --git a/src/native/libs/System.Native/pal_threading.c b/src/native/libs/System.Native/pal_threading.c index 60e90fc1398b80..863a337cbeccf2 100644 --- a/src/native/libs/System.Native/pal_threading.c +++ b/src/native/libs/System.Native/pal_threading.c @@ -367,3 +367,8 @@ int32_t SystemNative_PThreadMutex_Release(void* mutex) assert(mutex != NULL); return pthread_mutex_unlock((pthread_mutex_t*)mutex); } + +int32_t SystemNative_PThreadMutex_Size(void) +{ + return (int32_t)sizeof(pthread_mutex_t); +} diff --git a/src/native/libs/System.Native/pal_threading.h b/src/native/libs/System.Native/pal_threading.h index c7890507e76777..906ab4fd08fc53 100644 --- a/src/native/libs/System.Native/pal_threading.h +++ b/src/native/libs/System.Native/pal_threading.h @@ -38,3 +38,5 @@ PALEXPORT int32_t SystemNative_PThreadMutex_Init(void* mutex); PALEXPORT int32_t SystemNative_PThreadMutex_Acquire(void* mutex, int32_t timeoutMilliseconds); PALEXPORT int32_t SystemNative_PThreadMutex_Release(void* mutex); + +PALEXPORT int32_t SystemNative_PThreadMutex_Size(void); From 5c59aada33b507fc03632426b295c1d00ba19d1b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 10 Jul 2025 22:16:23 +0000 Subject: [PATCH 06/47] Move the named mutex impl to a runtime that has the wait subsystem. Create a NamedMutex class that wraps the underlying data structures --- .../System.Private.CoreLib.csproj | 2 - .../src/System/Threading/Thread.CoreCLR.cs | 2 - .../System/Threading/Thread.NativeAot.Unix.cs | 2 + .../System.Private.CoreLib.Shared.projitems | 2 + .../System/IO/SharedMemoryManager.Unix.cs} | 0 .../src/System/Threading/NamedMutex.Unix.cs} | 60 ++++++++++++++++++- .../WaitSubsystem.HandleManager.Unix.cs | 14 +++-- .../System/Threading/WaitSubsystem.Unix.cs | 6 +- .../WaitSubsystem.WaitableObject.Unix.cs | 2 +- .../src/System/Threading/Thread.Mono.cs | 2 + 10 files changed, 79 insertions(+), 13 deletions(-) rename src/{coreclr/System.Private.CoreLib/src/System/IO/SharedMemoryManager.cs => libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs} (100%) rename src/{coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs => libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs} (89%) diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index 9238ce848040f0..c12d6c854ac0c0 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -290,10 +290,8 @@ - - diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index de831cd26ebb71..99569377896a14 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -30,8 +30,6 @@ public sealed partial class Thread internal ExecutionContext? _executionContext; // this call context follows the logical thread internal SynchronizationContext? _synchronizationContext; // maintained separately from ExecutionContext - internal NamedMutexProcessDataBase? _ownedSharedNamedMutexes; // shared named mutexes owned by this thread - private string? _name; private StartHelper? _startHelper; diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Unix.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Unix.cs index de77bc91b088e3..1f2b9a3003c7ef 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Unix.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Unix.cs @@ -18,6 +18,8 @@ public sealed partial class Thread internal WaitSubsystem.ThreadWaitInfo WaitInfo => _waitInfo; + internal NamedMutexProcessDataBase? _ownedSharedNamedMutexes; // shared named mutexes owned by this thread + private void PlatformSpecificInitialize() { _waitInfo = new WaitSubsystem.ThreadWaitInfo(this); diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 33693d567ab961..9143b6f8c1e572 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2838,9 +2838,11 @@ + + diff --git a/src/coreclr/System.Private.CoreLib/src/System/IO/SharedMemoryManager.cs b/src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs similarity index 100% rename from src/coreclr/System.Private.CoreLib/src/System/IO/SharedMemoryManager.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/SharedMemoryManager.Unix.cs diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs similarity index 89% rename from src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs index 26681e097d7d68..dd6e092116a268 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/MutexSharedMemory.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/NamedMutex.Unix.cs @@ -35,6 +35,7 @@ internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader< protected SharedMemoryId Id => _processDataHeader._id; public abstract bool IsLockOwnedByCurrentThread { get; } + public bool IsLockOwnedByAnyThreadInThisProcess => _lockOwnerThread is not null; protected abstract void SetLockOwnerToCurrentThread(); @@ -130,7 +131,7 @@ public void Abandon() } } - private static unsafe SharedMemoryProcessDataHeader? CreateOrOpen(string name, bool isUserScope, bool createIfNotExist, bool acquireLockIfCreated, out bool created) + internal static unsafe SharedMemoryProcessDataHeader? CreateOrOpen(string name, bool isUserScope, bool createIfNotExist, bool acquireLockIfCreated, out bool created) { using Lock.Scope creationDeletionProcessLock = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); @@ -554,4 +555,61 @@ internal enum MutexTryAcquireLockResult : byte TimedOut, AcquiredLockRecursively, } + + internal static partial class WaitSubsystem + { + [UnsupportedOSPlatform("windows")] + private sealed class NamedMutex(SharedMemoryProcessDataHeader processDataHeader) : IWaitableObject + { + private int _referenceCount = 1; + + public void IncrementRefCount() + { + Debug.Assert(_referenceCount > 0, "Ref count should not be negative."); + _referenceCount++; + } + + public int Acquire(int timeoutMilliseconds) + { + MutexTryAcquireLockResult result = processDataHeader._processData!.TryAcquireLock(timeoutMilliseconds); + return result switch + { + MutexTryAcquireLockResult.AcquiredLock => WaitHandle.WaitSuccess, + MutexTryAcquireLockResult.AcquiredLockButMutexWasAbandoned => WaitHandle.WaitAbandoned, + MutexTryAcquireLockResult.TimedOut => WaitHandle.WaitTimeout, + _ => throw new InvalidOperationException("Unexpected result from TryAcquireLock") + }; + } + + public void Release() + { + processDataHeader._processData!.ReleaseLock(); + } + + public void OnDeleteHandle() + { + s_lock.Acquire(); + try + { + // Multiple handles may refer to the same named object. Make sure the object + // is only abandoned once the last handle to it is deleted. Also, remove the + // object from the named objects dictionary at this point. + _referenceCount--; + if (_referenceCount > 0) + { + return; + } + + if (processDataHeader._processData!.IsLockOwnedByAnyThreadInThisProcess) + { + processDataHeader._processData.Abandon(); + } + } + finally + { + s_lock.Release(); + } + } + } + } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.HandleManager.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.HandleManager.Unix.cs index 1ef3fabfde518b..835b3b879718af 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.HandleManager.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.HandleManager.Unix.cs @@ -10,9 +10,14 @@ namespace System.Threading { internal static partial class WaitSubsystem { + private interface IWaitableObject + { + void OnDeleteHandle(); + } + private static class HandleManager { - public static IntPtr NewHandle(WaitableObject waitableObject) + public static IntPtr NewHandle(IWaitableObject waitableObject) { Debug.Assert(waitableObject != null); @@ -24,7 +29,8 @@ public static IntPtr NewHandle(WaitableObject waitableObject) return handle; } - public static WaitableObject FromHandle(IntPtr handle) + public static TWaitableObject FromHandle(IntPtr handle) + where TWaitableObject : IWaitableObject { if (handle == IntPtr.Zero || handle == new IntPtr(-1)) { @@ -33,7 +39,7 @@ public static WaitableObject FromHandle(IntPtr handle) // We don't know if any other handles are invalid, and this may crash or otherwise do bad things, that is by // design, IntPtr is unsafe by nature. - return (WaitableObject)GCHandle.FromIntPtr(handle).Target!; + return (TWaitableObject)GCHandle.FromIntPtr(handle).Target!; } /// @@ -48,7 +54,7 @@ public static void DeleteHandle(IntPtr handle) // We don't know if any other handles are invalid, and this may crash or otherwise do bad things, that is by // design, IntPtr is unsafe by nature. - FromHandle(handle).OnDeleteHandle(); + FromHandle(handle).OnDeleteHandle(); GCHandle.FromIntPtr(handle).Free(); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs index c8635142aab886..c91b49db6df9b4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs @@ -241,7 +241,7 @@ public static void DeleteHandle(IntPtr handle) public static void SetEvent(IntPtr handle) { - SetEvent(HandleManager.FromHandle(handle)); + SetEvent(HandleManager.FromHandle(handle)); } public static void SetEvent(WaitableObject waitableObject) @@ -261,7 +261,7 @@ public static void SetEvent(WaitableObject waitableObject) public static void ResetEvent(IntPtr handle) { - ResetEvent(HandleManager.FromHandle(handle)); + ResetEvent(HandleManager.FromHandle(handle)); } public static void ResetEvent(WaitableObject waitableObject) @@ -282,7 +282,7 @@ public static void ResetEvent(WaitableObject waitableObject) public static int ReleaseSemaphore(IntPtr handle, int count) { Debug.Assert(count > 0); - return ReleaseSemaphore(HandleManager.FromHandle(handle), count); + return ReleaseSemaphore(HandleManager.FromHandle(handle), count); } public static int ReleaseSemaphore(WaitableObject waitableObject, int count) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs index fdf3ca54df23d5..cf1559cc074518 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs @@ -13,7 +13,7 @@ internal static partial class WaitSubsystem /// /// Used by the wait subsystem on Unix, so this class cannot have any dependencies on the wait subsystem. /// - public sealed class WaitableObject + public sealed class WaitableObject : IWaitableObject { /// /// Dictionary to look up named waitable objects. This implementation only supports in-process diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs index 5715dd23923f6b..09c7112c57d934 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs @@ -67,6 +67,8 @@ public partial class Thread internal SynchronizationContext? _synchronizationContext; #if TARGET_UNIX || TARGET_BROWSER || TARGET_WASI internal WaitSubsystem.ThreadWaitInfo? _waitInfo; + + internal NamedMutexProcessDataBase? _ownedSharedNamedMutexes; // shared named mutexes owned by this thread #endif // This is used for a quick check on thread pool threads after running a work item to determine if the name, background From 40ec2d79dfa1bfb64fbc3c02c35b3ddced2dd46f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 10 Jul 2025 23:35:00 +0000 Subject: [PATCH 07/47] Finish integrating with the wait subsystem entry points and bring back the "close" logic --- .../System.Native/Interop.PThreadMutex.cs | 3 + .../src/System/IO/SharedMemoryManager.Unix.cs | 66 +++++++- .../src/System/Threading/Mutex.Unix.cs | 4 +- .../src/System/Threading/NamedMutex.Unix.cs | 149 ++++++++++++------ .../WaitSubsystem.HandleManager.Unix.cs | 35 +++- .../WaitSubsystem.ThreadWaitInfo.Unix.cs | 33 ++++ .../System/Threading/WaitSubsystem.Unix.cs | 58 +++---- .../WaitSubsystem.WaitableObject.Unix.cs | 24 --- .../src/System/Threading/Thread.Mono.cs | 2 - src/native/libs/System.Native/entrypoints.c | 1 + src/native/libs/System.Native/pal_threading.c | 6 + src/native/libs/System.Native/pal_threading.h | 2 + 12 files changed, 265 insertions(+), 118 deletions(-) diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PThreadMutex.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PThreadMutex.cs index 093d237b457f47..fd49ebb36fdcec 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PThreadMutex.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PThreadMutex.cs @@ -16,6 +16,9 @@ internal static unsafe partial class Sys [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PThreadMutex_Release", SetLastError = true)] internal static partial int PThreadMutex_Release(void* mutex); + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PThreadMutex_Destroy", SetLastError = true)] + internal static partial int PThreadMutex_Destroy(void* mutex); + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PThreadMutex_Size")] [SuppressGCTransition] internal static partial int PThreadMutex_Size(); 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 decd4facf26b6f..bb104321a84616 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 @@ -17,7 +17,6 @@ namespace System.IO { - [UnsupportedOSPlatform("windows")] internal readonly unsafe struct SharedMemoryId { private const string UserUnscopedRuntimeTempDirectoryName = ".dotnet"; @@ -119,9 +118,13 @@ public SharedMemorySharedDataHeader(SharedMemoryType type, uint version) } } - [UnsupportedOSPlatform("windows")] + internal interface ISharedMemoryProcessData + { + void Close(bool releaseSharedData); + } + internal sealed unsafe class SharedMemoryProcessDataHeader - where TSharedMemoryProcessData : class + where TSharedMemoryProcessData : class, ISharedMemoryProcessData { internal SharedMemoryId _id; internal TSharedMemoryProcessData? _processData; @@ -305,9 +308,55 @@ static void SetFileSize(SafeFileHandle fd, string path, long size) } } } + + internal void Close() + { + SharedMemoryManager.Instance.RemoveProcessDataHeader(this); + + using AutoReleaseFileLock autoReleaseFileLock = SharedMemoryManager.Instance.AcquireCreationDeletionLockForId(_id); + + bool releaseSharedData = false; + + try + { + Interop.Sys.FLock(_fileHandle, Interop.Sys.LockOperations.LOCK_UN); + if (SharedMemoryHelpers.TryAcquireFileLock(_fileHandle, nonBlocking: true, exclusive: true)) + { + // There's no one else using this mutex. + // We can delete our shared data. + releaseSharedData = true; + } + } + catch (Exception) + { + // Ignore the error, just don't release shared data. + } + + _processData?.Close(releaseSharedData); + _processData = null; + Interop.Sys.MUnmap((nint)_sharedDataHeader, _sharedDataTotalByteCount); + _fileHandle.Dispose(); + + if (releaseSharedData) + { + string sessionDirectoryPath = Path.Combine( + SharedMemoryHelpers.SharedFilesPath, + _id.GetRuntimeTempDirectoryName(), + SharedMemoryManager.SharedMemorySharedMemoryDirectoryName, + _id.GetSessionDirectoryName() + ); + + string sharedMemoryFilePath = Path.Combine(sessionDirectoryPath, _id.Name); + + // Directly call the underlying functions here as this is best-effort. + // If we fail to delete, we don't want an exception. + Interop.Sys.Unlink(sharedMemoryFilePath); + + Interop.Sys.RmDir(sessionDirectoryPath); + } + } } - [UnsupportedOSPlatform("windows")] internal static class SharedMemoryHelpers { public const uint InvalidProcessId = unchecked((uint)-1); @@ -445,7 +494,9 @@ internal static bool EnsureDirectoryExists(string directoryPath, SharedMemoryId if (isGlobalLockAcquired) { +#pragma warning disable CA1416 // Validate platform compatibility. This file is only included on Unix platforms. Directory.CreateDirectory(directoryPath, permissionsMask); +#pragma warning restore CA1416 // Validate platform compatibility try { @@ -656,17 +707,16 @@ public void Dispose() } } - [UnsupportedOSPlatform("windows")] internal sealed class SharedMemoryManager - where TSharedMemoryProcessData : class + where TSharedMemoryProcessData : class, ISharedMemoryProcessData { internal static SharedMemoryManager Instance { get; } = new SharedMemoryManager(); internal const string SharedMemorySharedMemoryDirectoryName = "shm"; - private Lock _creationDeletionProcessLock = new Lock(); + private readonly Lock _creationDeletionProcessLock = new Lock(); private SafeFileHandle? _creationDeletionLockFileHandle; - private Dictionary _uidToFileHandleMap = []; + private readonly Dictionary _uidToFileHandleMap = []; public Lock.Scope AcquireCreationDeletionProcessLock() { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Mutex.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Mutex.Unix.cs index 4ee0c6c7000246..1f48fcccdd2a52 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Mutex.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Mutex.Unix.cs @@ -22,7 +22,7 @@ private void CreateMutexCore( { name = BuildNameForOptions(name, options); - SafeWaitHandle? safeWaitHandle = WaitSubsystem.CreateNamedMutex(initiallyOwned, name, out createdNew); + SafeWaitHandle? safeWaitHandle = WaitSubsystem.CreateNamedMutex(initiallyOwned, name, isUserScope: options.WasSpecified && options.CurrentUserOnly, out createdNew); if (safeWaitHandle == null) { throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name)); @@ -44,7 +44,7 @@ private static OpenExistingResult OpenExistingWorker( name = BuildNameForOptions(name, options); - OpenExistingResult status = WaitSubsystem.OpenNamedMutex(name, out SafeWaitHandle? safeWaitHandle); + OpenExistingResult status = WaitSubsystem.OpenNamedMutex(name, isUserScope: options.WasSpecified && options.CurrentUserOnly, out SafeWaitHandle? safeWaitHandle); result = status == OpenExistingResult.Success ? new Mutex(safeWaitHandle!) : null; return status; } 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 dd6e092116a268..cfade222465223 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 @@ -16,8 +16,7 @@ namespace System.Threading { - [UnsupportedOSPlatform("windows")] - internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader header) + internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader header) : ISharedMemoryProcessData { private const byte SyncSystemVersion = 1; protected const int PollLoopMaximumSleepMilliseconds = 100; @@ -39,7 +38,7 @@ internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader< protected abstract void SetLockOwnerToCurrentThread(); - public MutexTryAcquireLockResult TryAcquireLock(int timeoutMilliseconds) + public MutexTryAcquireLockResult TryAcquireLock(WaitSubsystem.ThreadWaitInfo waitInfo, int timeoutMilliseconds) { MutexTryAcquireLockResult result = AcquireLockCore(timeoutMilliseconds); @@ -50,8 +49,8 @@ public MutexTryAcquireLockResult TryAcquireLock(int timeoutMilliseconds) SetLockOwnerToCurrentThread(); _lockCount = 1; - _lockOwnerThread = Thread.CurrentThread; - AddOwnedNamedMutex(Thread.CurrentThread, this); + _lockOwnerThread = waitInfo.Thread; + AddOwnedNamedMutex(waitInfo.Thread, this); if (IsAbandoned) { @@ -62,10 +61,10 @@ public MutexTryAcquireLockResult TryAcquireLock(int timeoutMilliseconds) return result; } - private static void AddOwnedNamedMutex(Thread currentThread, NamedMutexProcessDataBase namedMutexProcessDataBase) + private static void AddOwnedNamedMutex(Thread thread, NamedMutexProcessDataBase namedMutexProcessDataBase) { - namedMutexProcessDataBase._nextInThreadOwnedNamedMutexList = currentThread._ownedSharedNamedMutexes; - currentThread._ownedSharedNamedMutexes = namedMutexProcessDataBase; + namedMutexProcessDataBase._nextInThreadOwnedNamedMutexList = thread.WaitInfo.LockedNamedMutexesHead; + thread.WaitInfo.LockedNamedMutexesHead = namedMutexProcessDataBase; } public void ReleaseLock() @@ -88,13 +87,13 @@ public void ReleaseLock() private static void RemoveOwnedNamedMutex(Thread currentThread, NamedMutexProcessDataBase namedMutexProcessDataBase) { - if (currentThread._ownedSharedNamedMutexes == namedMutexProcessDataBase) + if (currentThread.WaitInfo.LockedNamedMutexesHead == namedMutexProcessDataBase) { - currentThread._ownedSharedNamedMutexes = namedMutexProcessDataBase._nextInThreadOwnedNamedMutexList; + currentThread.WaitInfo.LockedNamedMutexesHead = namedMutexProcessDataBase._nextInThreadOwnedNamedMutexList; } else { - NamedMutexProcessDataBase? previous = currentThread._ownedSharedNamedMutexes; + NamedMutexProcessDataBase? previous = currentThread.WaitInfo.LockedNamedMutexesHead; while (previous?._nextInThreadOwnedNamedMutexList != namedMutexProcessDataBase) { previous = previous?._nextInThreadOwnedNamedMutexList; @@ -123,14 +122,6 @@ public void Abandon() protected abstract void ReleaseLockCore(); - ~NamedMutexProcessDataBase() - { - if (_lockOwnerThread is not null) - { - Abandon(); - } - } - internal static unsafe SharedMemoryProcessDataHeader? CreateOrOpen(string name, bool isUserScope, bool createIfNotExist, bool acquireLockIfCreated, out bool created) { using Lock.Scope creationDeletionProcessLock = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); @@ -170,7 +161,7 @@ public void Abandon() if (created && acquireLockIfCreated) { - MutexTryAcquireLockResult acquireResult = processDataHeader._processData.TryAcquireLock(timeoutMilliseconds: 0); + MutexTryAcquireLockResult acquireResult = processDataHeader._processData.TryAcquireLock(Thread.CurrentThread.WaitInfo, timeoutMilliseconds: 0); Debug.Assert(acquireResult != MutexTryAcquireLockResult.AcquiredLock); } } @@ -202,9 +193,16 @@ private static unsafe void InitializeSharedData(void* v) sharedData->TimedWaiterCount = 0; } } + + public virtual void Close(bool releaseSharedData) + { + if (IsLockOwnedByCurrentThread) + { + RemoveOwnedNamedMutex(Thread.CurrentThreadAssumedInitialized, this); + } + } } - [UnsupportedOSPlatform("windows")] internal sealed unsafe class NamedMutexProcessDataWithPThreads(SharedMemoryProcessDataHeader processDataHeader) : NamedMutexProcessDataBase(processDataHeader) { private readonly SharedData* _sharedData = (SharedData*)SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader); @@ -282,7 +280,17 @@ protected override void ReleaseLockCore() Interop.Sys.PThreadMutex_Release(_sharedData->LockAddress); } - [UnsupportedOSPlatform("windows")] + public override void Close(bool releaseSharedData) + { + base.Close(releaseSharedData); + + if (releaseSharedData) + { + // Release the pthread mutex. + Interop.Sys.PThreadMutex_Destroy(_sharedData->LockAddress); + } + } + [StructLayout(LayoutKind.Sequential)] internal unsafe ref struct SharedData { @@ -314,7 +322,6 @@ public bool IsAbandoned } } - [UnsupportedOSPlatform("windows")] internal sealed unsafe class NamedMutexProcessDataNoPThreads : NamedMutexProcessDataBase { private const string SharedMemoryLockFilesDirectoryName = "lockfiles"; @@ -389,26 +396,11 @@ protected override bool IsAbandoned set => _sharedData->IsAbandoned = value; } - ~NamedMutexProcessDataNoPThreads() + public override void Close(bool releaseSharedData) { - string sessionDirectory = Path.Combine( - SharedMemoryHelpers.SharedFilesPath, - Id.GetRuntimeTempDirectoryName(), - SharedMemoryLockFilesDirectoryName, - Id.GetSessionDirectoryName() - ); + base.Close(releaseSharedData); - try - { - // Delete the lock file. - File.Delete(Path.Combine(sessionDirectory, Id.Name)); - // Delete the session directory if it's empty. - Directory.Delete(sessionDirectory); - } - catch (Exception) - { - // Ignore the error, just don't release the shared data. - } + _sharedLockFileHandle.Dispose(); } protected override unsafe MutexTryAcquireLockResult AcquireLockCore(int timeoutMilliseconds) @@ -500,7 +492,7 @@ protected override unsafe MutexTryAcquireLockResult AcquireLockCore(int timeoutM return MutexTryAcquireLockResult.AcquiredLock; } - [UnsupportedOSPlatform("windows")] + [StructLayout(LayoutKind.Sequential)] internal ref struct SharedData { @@ -558,9 +550,12 @@ internal enum MutexTryAcquireLockResult : byte internal static partial class WaitSubsystem { - [UnsupportedOSPlatform("windows")] private sealed class NamedMutex(SharedMemoryProcessDataHeader processDataHeader) : IWaitableObject { + /// + /// Dictionary to look up named mutexes. + /// + private static Dictionary? s_namedObjects; private int _referenceCount = 1; public void IncrementRefCount() @@ -569,9 +564,11 @@ public void IncrementRefCount() _referenceCount++; } - public int Acquire(int timeoutMilliseconds) + public int Wait_Locked(ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool interruptible, bool prioritize, ref LockHolder lockHolder) { - MutexTryAcquireLockResult result = processDataHeader._processData!.TryAcquireLock(timeoutMilliseconds); + // We're done touching the wait subsystem data structures, so we can release the lock on them. + lockHolder.Dispose(); + MutexTryAcquireLockResult result = processDataHeader._processData!.TryAcquireLock(waitInfo, timeoutMilliseconds); return result switch { MutexTryAcquireLockResult.AcquiredLock => WaitHandle.WaitSuccess, @@ -581,8 +578,9 @@ public int Acquire(int timeoutMilliseconds) }; } - public void Release() + public void Signal(int count, ref LockHolder lockHolder) { + lockHolder.Dispose(); processDataHeader._processData!.ReleaseLock(); } @@ -600,16 +598,67 @@ public void OnDeleteHandle() return; } - if (processDataHeader._processData!.IsLockOwnedByAnyThreadInThisProcess) - { - processDataHeader._processData.Abandon(); - } + processDataHeader.Close(); } finally { s_lock.Release(); } } + + public static NamedMutex? CreateNamedMutex_Locked(string name, bool isUserScope, bool initiallyOwned, out bool createdNew) + { + s_lock.VerifyIsLocked(); + + s_namedObjects ??= []; + + if (s_namedObjects.TryGetValue(name, out NamedMutex? result)) + { + createdNew = false; + result._referenceCount++; + } + else + { + var namedMutexProcessData = NamedMutexProcessDataBase.CreateOrOpen(name, isUserScope, createIfNotExist: true, acquireLockIfCreated: initiallyOwned, out createdNew); + if (namedMutexProcessData is null) + { + createdNew = false; + return null; + } + result = new NamedMutex(namedMutexProcessData); + s_namedObjects.Add(name, result); + return result; + } + + return result; + } + + public static OpenExistingResult OpenNamedMutex(string name, bool isUserScope, out NamedMutex? result) + { + s_lock.VerifyIsLocked(); + + if (s_namedObjects is null || !s_namedObjects.TryGetValue(name, out NamedMutex? namedMutex)) + { + // We haven't seen this named mutex before in this process. + // Try seeing if it exists on the system. + var namedMutexProcessData = NamedMutexProcessDataBase.CreateOrOpen(name, isUserScope, createIfNotExist: false, acquireLockIfCreated: false, out _); + if (namedMutexProcessData is null) + { + // This mutex does not exist. + result = null; + return OpenExistingResult.NameNotFound; + } + + // We found the mutex on the system, so we can open it. + s_namedObjects ??= []; + namedMutex = new NamedMutex(namedMutexProcessData); + s_namedObjects.Add(name, namedMutex); + } + + namedMutex.IncrementRefCount(); + result = namedMutex; + return OpenExistingResult.Success; + } } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.HandleManager.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.HandleManager.Unix.cs index 835b3b879718af..6ac4e76fcd39f6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.HandleManager.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.HandleManager.Unix.cs @@ -10,9 +10,35 @@ namespace System.Threading { internal static partial class WaitSubsystem { - private interface IWaitableObject + public interface IWaitableObject { void OnDeleteHandle(); + int Wait_Locked(ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool interruptible, bool prioritize, ref LockHolder lockHolder); + void Signal(int count, ref LockHolder lockHolder); + } + + public static int Wait(this IWaitableObject waitable, ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool interruptible, bool prioritize) + { + Debug.Assert(waitInfo != null); + Debug.Assert(waitInfo.Thread == Thread.CurrentThread); + + Debug.Assert(timeoutMilliseconds >= -1); + + var lockHolder = new LockHolder(s_lock); + try + { + if (interruptible && waitInfo.CheckAndResetPendingInterrupt) + { + lockHolder.Dispose(); + throw new ThreadInterruptedException(); + } + + return waitable.Wait_Locked(waitInfo, timeoutMilliseconds, interruptible, prioritize, ref lockHolder); + } + finally + { + lockHolder.Dispose(); + } } private static class HandleManager @@ -29,8 +55,7 @@ public static IntPtr NewHandle(IWaitableObject waitableObject) return handle; } - public static TWaitableObject FromHandle(IntPtr handle) - where TWaitableObject : IWaitableObject + public static IWaitableObject FromHandle(IntPtr handle) { if (handle == IntPtr.Zero || handle == new IntPtr(-1)) { @@ -39,7 +64,7 @@ public static TWaitableObject FromHandle(IntPtr handle) // We don't know if any other handles are invalid, and this may crash or otherwise do bad things, that is by // design, IntPtr is unsafe by nature. - return (TWaitableObject)GCHandle.FromIntPtr(handle).Target!; + return (IWaitableObject)GCHandle.FromIntPtr(handle).Target!; } /// @@ -54,7 +79,7 @@ public static void DeleteHandle(IntPtr handle) // We don't know if any other handles are invalid, and this may crash or otherwise do bad things, that is by // design, IntPtr is unsafe by nature. - FromHandle(handle).OnDeleteHandle(); + FromHandle(handle).OnDeleteHandle(); GCHandle.FromIntPtr(handle).Free(); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs index a26587e5412a5f..32e03af90108e5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs @@ -86,6 +86,13 @@ public sealed class ThreadWaitInfo /// private WaitableObject? _lockedMutexesHead; + /// + /// Linked list of named mutexes that are locked by the thread and need to be abandoned before the thread exits. + /// The linked list has only a head and no tail, which means acquired mutexes are prepended and + /// mutexes are abandoned in reverse order. + /// + private NamedMutexProcessDataBase? _lockedNamedMutexesHead; + public ThreadWaitInfo(Thread thread) { Debug.Assert(thread != null); @@ -553,6 +560,20 @@ public WaitableObject? LockedMutexesHead } } + public NamedMutexProcessDataBase? LockedNamedMutexesHead + { + get + { + s_lock.VerifyIsLocked(); + return _lockedNamedMutexesHead; + } + set + { + s_lock.VerifyIsLocked(); + _lockedNamedMutexesHead = value; + } + } + public void OnThreadExiting() { // Abandon locked mutexes. Acquired mutexes are prepended to the linked list, so the mutexes are abandoned in @@ -571,6 +592,18 @@ public void OnThreadExiting() waitableObject.AbandonMutex(); Debug.Assert(LockedMutexesHead != waitableObject); } + + while (true) + { + NamedMutexProcessDataBase? namedMutex = LockedNamedMutexesHead; + if (namedMutex == null) + { + break; + } + + namedMutex.Abandon(); + Debug.Assert(LockedNamedMutexesHead != namedMutex); + } } finally { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs index c91b49db6df9b4..8a5b304cd4a57a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; @@ -144,7 +145,7 @@ public void Dispose() } } - private static SafeWaitHandle NewHandle(WaitableObject waitableObject) + private static SafeWaitHandle NewHandle(IWaitableObject waitableObject) { var safeWaitHandle = new SafeWaitHandle(); @@ -193,7 +194,7 @@ public static SafeWaitHandle NewMutex(bool initiallyOwned) return safeWaitHandle; } - public static SafeWaitHandle? CreateNamedMutex(bool initiallyOwned, string name, out bool createdNew) + public static SafeWaitHandle? CreateNamedMutex(bool initiallyOwned, string name, bool isUserScope, out bool createdNew) { // For initially owned, newly created named mutexes, there is a potential race // between adding the mutex to the named object table and initially acquiring it. @@ -202,23 +203,12 @@ public static SafeWaitHandle NewMutex(bool initiallyOwned) LockHolder lockHolder = new LockHolder(s_lock); try { - WaitableObject? waitableObject = WaitableObject.CreateNamedMutex_Locked(name, out createdNew); - if (waitableObject == null) + NamedMutex? namedMutex = NamedMutex.CreateNamedMutex_Locked(name, isUserScope, initiallyOwned: initiallyOwned, out createdNew); + if (namedMutex == null) { return null; } - SafeWaitHandle safeWaitHandle = NewHandle(waitableObject); - if (!initiallyOwned || !createdNew) - { - return safeWaitHandle; - } - - // Acquire the mutex. A thread's has a reference to all es locked - // by the thread. See . So, acquire the lock only after all - // possibilities for exceptions have been exhausted. - ThreadWaitInfo waitInfo = Thread.CurrentThread.WaitInfo; - int status = waitableObject.Wait_Locked(waitInfo, timeoutMilliseconds: 0, interruptible: false, prioritize: false, ref lockHolder); - Debug.Assert(status == 0); + SafeWaitHandle safeWaitHandle = NewHandle(namedMutex); return safeWaitHandle; } finally @@ -227,9 +217,9 @@ public static SafeWaitHandle NewMutex(bool initiallyOwned) } } - public static OpenExistingResult OpenNamedMutex(string name, out SafeWaitHandle? result) + public static OpenExistingResult OpenNamedMutex(string name, bool isUserScope, out SafeWaitHandle? result) { - OpenExistingResult status = WaitableObject.OpenNamedMutex(name, out WaitableObject? mutex); + OpenExistingResult status = NamedMutex.OpenNamedMutex(name, isUserScope, out NamedMutex? mutex); result = status == OpenExistingResult.Success ? NewHandle(mutex!) : null; return status; } @@ -241,7 +231,7 @@ public static void DeleteHandle(IntPtr handle) public static void SetEvent(IntPtr handle) { - SetEvent(HandleManager.FromHandle(handle)); + SetEvent((WaitableObject)HandleManager.FromHandle(handle)); } public static void SetEvent(WaitableObject waitableObject) @@ -261,7 +251,7 @@ public static void SetEvent(WaitableObject waitableObject) public static void ResetEvent(IntPtr handle) { - ResetEvent(HandleManager.FromHandle(handle)); + ResetEvent((WaitableObject)HandleManager.FromHandle(handle)); } public static void ResetEvent(WaitableObject waitableObject) @@ -282,7 +272,7 @@ public static void ResetEvent(WaitableObject waitableObject) public static int ReleaseSemaphore(IntPtr handle, int count) { Debug.Assert(count > 0); - return ReleaseSemaphore(HandleManager.FromHandle(handle), count); + return ReleaseSemaphore((WaitableObject)HandleManager.FromHandle(handle), count); } public static int ReleaseSemaphore(WaitableObject waitableObject, int count) @@ -306,14 +296,14 @@ public static void ReleaseMutex(IntPtr handle) ReleaseMutex(HandleManager.FromHandle(handle)); } - public static void ReleaseMutex(WaitableObject waitableObject) + public static void ReleaseMutex(IWaitableObject waitableObject) { Debug.Assert(waitableObject != null); LockHolder lockHolder = new LockHolder(s_lock); try { - waitableObject.SignalMutex(ref lockHolder); + waitableObject.Signal(1, ref lockHolder); } finally { @@ -328,7 +318,7 @@ public static int Wait(IntPtr handle, int timeoutMilliseconds, bool interruptibl } public static int Wait( - WaitableObject waitableObject, + IWaitableObject waitableObject, int timeoutMilliseconds, bool interruptible = true, bool prioritize = false) @@ -349,14 +339,28 @@ public static int Wait( Debug.Assert(timeoutMilliseconds >= -1); ThreadWaitInfo waitInfo = Thread.CurrentThread.WaitInfo; + + if (waitHandles.Length == 1 && HandleManager.FromHandle(waitHandles[0]) is NamedMutex namedMutex) + { + // Named mutexes don't participate in the wait subsystem fully. + return namedMutex.Wait(waitInfo, timeoutMilliseconds, interruptible: true, prioritize: false); + } + WaitableObject?[] waitableObjects = waitInfo.GetWaitedObjectArray(waitHandles.Length); bool success = false; + try { for (int i = 0; i < waitHandles.Length; ++i) { Debug.Assert(waitHandles[i] != IntPtr.Zero); - WaitableObject waitableObject = HandleManager.FromHandle(waitHandles[i]); + IWaitableObject waitableObjectMaybe = HandleManager.FromHandle(waitHandles[i]); + + if (waitableObjectMaybe is not WaitableObject waitableObject) + { + throw new ArgumentException("Only unnamed waitable objects are supported in multi-wait operations.", nameof(waitHandles)); + } + if (waitForAll) { // Check if this is a duplicate, as wait-for-all does not support duplicates. Including the parent @@ -421,8 +425,8 @@ public static int SignalAndWait( } public static int SignalAndWait( - WaitableObject waitableObjectToSignal, - WaitableObject waitableObjectToWaitOn, + IWaitableObject waitableObjectToSignal, + IWaitableObject waitableObjectToWaitOn, int timeoutMilliseconds, bool interruptible = true, bool prioritize = false) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs index cf1559cc074518..7f892a32a7e646 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs @@ -301,30 +301,6 @@ private void AcceptSignal(ThreadWaitInfo waitInfo) } } - public int Wait(ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool interruptible, bool prioritize) - { - Debug.Assert(waitInfo != null); - Debug.Assert(waitInfo.Thread == Thread.CurrentThread); - - Debug.Assert(timeoutMilliseconds >= -1); - - var lockHolder = new LockHolder(s_lock); - try - { - if (interruptible && waitInfo.CheckAndResetPendingInterrupt) - { - lockHolder.Dispose(); - throw new ThreadInterruptedException(); - } - - return Wait_Locked(waitInfo, timeoutMilliseconds, interruptible, prioritize, ref lockHolder); - } - finally - { - lockHolder.Dispose(); - } - } - /// /// This function does not check for a pending thread interrupt. Callers are expected to do that soon after /// acquiring . diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs index 09c7112c57d934..5715dd23923f6b 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs @@ -67,8 +67,6 @@ public partial class Thread internal SynchronizationContext? _synchronizationContext; #if TARGET_UNIX || TARGET_BROWSER || TARGET_WASI internal WaitSubsystem.ThreadWaitInfo? _waitInfo; - - internal NamedMutexProcessDataBase? _ownedSharedNamedMutexes; // shared named mutexes owned by this thread #endif // This is used for a quick check on thread pool threads after running a work item to determine if the name, background diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index 3df739fa83d5ba..4e03ff98bcaee0 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -289,6 +289,7 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_PThreadMutex_Init) DllImportEntry(SystemNative_PThreadMutex_Acquire) DllImportEntry(SystemNative_PThreadMutex_Release) + DllImportEntry(SystemNative_PThreadMutex_Destroy) DllImportEntry(SystemNative_PThreadMutex_Size) DllImportEntry(SystemNative_GetPageSize) }; diff --git a/src/native/libs/System.Native/pal_threading.c b/src/native/libs/System.Native/pal_threading.c index 863a337cbeccf2..4583779fc72e13 100644 --- a/src/native/libs/System.Native/pal_threading.c +++ b/src/native/libs/System.Native/pal_threading.c @@ -368,6 +368,12 @@ int32_t SystemNative_PThreadMutex_Release(void* mutex) return pthread_mutex_unlock((pthread_mutex_t*)mutex); } +int32_t SystemNative_PThreadMutex_Destroy(void* mutex) +{ + assert(mutex != NULL); + return pthread_mutex_destroy((pthread_mutex_t*)mutex); +} + int32_t SystemNative_PThreadMutex_Size(void) { return (int32_t)sizeof(pthread_mutex_t); diff --git a/src/native/libs/System.Native/pal_threading.h b/src/native/libs/System.Native/pal_threading.h index 906ab4fd08fc53..c3bf93d250c8b0 100644 --- a/src/native/libs/System.Native/pal_threading.h +++ b/src/native/libs/System.Native/pal_threading.h @@ -39,4 +39,6 @@ PALEXPORT int32_t SystemNative_PThreadMutex_Acquire(void* mutex, int32_t timeout PALEXPORT int32_t SystemNative_PThreadMutex_Release(void* mutex); +PALEXPORT int32_t SystemNative_PThreadMutex_Destroy(void* mutex); + PALEXPORT int32_t SystemNative_PThreadMutex_Size(void); From 4904eb72655e45529cfb0ccf622ec84eb6fbbea8 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 10 Jul 2025 23:39:25 +0000 Subject: [PATCH 08/47] Enable tests --- src/libraries/System.Threading/tests/MutexTests.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/libraries/System.Threading/tests/MutexTests.cs b/src/libraries/System.Threading/tests/MutexTests.cs index 345831191e5066..7eb2364b69e0c2 100644 --- a/src/libraries/System.Threading/tests/MutexTests.cs +++ b/src/libraries/System.Threading/tests/MutexTests.cs @@ -24,11 +24,6 @@ private static bool IsCrossProcessNamedMutexSupported if (PlatformDetection.IsMobile) return false; - // Cross-process named mutex support is not implemented on NativeAOT and Mono - // [ActiveIssue("https://github.com/dotnet/runtime/issues/48720")] - if (PlatformDetection.IsMonoRuntime || PlatformDetection.IsNativeAot) - return false; - return true; } } From 194ea40369bea6d0e896c478c4c365e133dbbb35 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 12 Jul 2025 01:15:33 +0000 Subject: [PATCH 09/47] Fix various problems found by tests and adjust locking to actually work and not deadlock --- .../src/System.Private.CoreLib.csproj | 6 - .../src/System/IO/SharedMemoryManager.Unix.cs | 108 +++-- .../src/System/Threading/Mutex.Unix.cs | 5 - .../src/System/Threading/NamedMutex.Unix.cs | 380 ++++++++++-------- .../WaitSubsystem.ThreadWaitInfo.Unix.cs | 15 +- .../System/Threading/WaitSubsystem.Unix.cs | 22 +- .../WaitSubsystem.WaitableObject.Unix.cs | 2 +- 7 files changed, 293 insertions(+), 245 deletions(-) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 2c96332adf7d00..bf88594f9cdb5f 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -302,15 +302,9 @@ Interop\Unix\System.Native\Interop.Exit.cs - - Interop\Unix\System.Native\Interop.MMap.cs - Interop\Unix\System.Native\Interop.MProtect.cs - - Interop\Unix\System.Native\Interop.MUnmap.cs - 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 bb104321a84616..6d4b0d10302fd8 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 @@ -48,7 +48,7 @@ public SharedMemoryId(string name, bool isUserScope) if (name.ContainsAny(['\\', '/'])) { - throw new ArgumentException("Name cannot contain path separators after prefixes.", nameof(name)); + throw new ArgumentException($"Name '{name}' cannot contain path separators after prefixes.", nameof(name)); } IsUserScope = isUserScope; @@ -126,11 +126,12 @@ internal interface ISharedMemoryProcessData internal sealed unsafe class SharedMemoryProcessDataHeader where TSharedMemoryProcessData : class, ISharedMemoryProcessData { - internal SharedMemoryId _id; + internal readonly SharedMemoryId _id; internal TSharedMemoryProcessData? _processData; private readonly SafeFileHandle _fileHandle; private readonly SharedMemorySharedDataHeader* _sharedDataHeader; private readonly nuint _sharedDataTotalByteCount; + private int _referenceCount = 1; public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandle, SharedMemorySharedDataHeader* sharedDataHeader, nuint sharedDataTotalByteCount) { @@ -139,6 +140,7 @@ public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandl _sharedDataHeader = sharedDataHeader; _sharedDataTotalByteCount = sharedDataTotalByteCount; _processData = null; // Will be initialized later + SharedMemoryManager.Instance.AddProcessDataHeader(this); } public static void* GetDataPointer(SharedMemoryProcessDataHeader processDataHeader) @@ -170,6 +172,7 @@ public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandl if (processDataHeader is not null) { Debug.Assert(processDataHeader._sharedDataTotalByteCount == sharedDataTotalByteCount); + processDataHeader.IncrementRefCount(); return processDataHeader; } @@ -251,9 +254,12 @@ public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandl using MemoryMappedFileHolder memory = SharedMemoryHelpers.MemoryMapFile(fileHandle, sharedDataTotalByteCount); SharedMemorySharedDataHeader* sharedDataHeader = (SharedMemorySharedDataHeader*)memory.Pointer; - if (createdFile && clearContents) + if (createdFile) { - NativeMemory.Clear(memory.Pointer, sharedDataUsedByteCount); + if (clearContents) + { + NativeMemory.Clear(memory.Pointer, sharedDataTotalByteCount); + } *sharedDataHeader = requiredSharedDataHeader; } else @@ -295,7 +301,7 @@ static nuint AlignUp(nuint value, nuint alignment) static void SetFileSize(SafeFileHandle fd, string path, long size) { - if (Interop.Sys.FTruncate(fd, 0) < 0) + if (Interop.Sys.FTruncate(fd, size) < 0) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); if (errorInfo.Error != Interop.Error.EBADF && errorInfo.Error != Interop.Error.EINVAL) @@ -309,8 +315,25 @@ static void SetFileSize(SafeFileHandle fd, string path, long size) } } - internal void Close() + public void IncrementRefCount() + { + Debug.Assert(_referenceCount > 0, "Ref count should not be negative."); + _referenceCount++; + } + + public void DecrementRefCount() + { + Debug.Assert(_referenceCount > 0, "Ref count should not be negative."); + _referenceCount--; + if (_referenceCount == 0) + { + Close(); + } + } + + private void Close() { + SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); SharedMemoryManager.Instance.RemoveProcessDataHeader(this); using AutoReleaseFileLock autoReleaseFileLock = SharedMemoryManager.Instance.AcquireCreationDeletionLockForId(_id); @@ -359,13 +382,10 @@ internal void Close() internal static class SharedMemoryHelpers { - public const uint InvalidProcessId = unchecked((uint)-1); - public const uint InvalidThreadId = unchecked((uint)-1); - private const UnixFileMode PermissionsMask_OwnerUser_ReadWrite = UnixFileMode.UserRead | UnixFileMode.UserWrite; private const UnixFileMode PermissionsMask_OwnerUser_ReadWriteExecute = PermissionsMask_OwnerUser_ReadWrite | UnixFileMode.UserExecute; private const UnixFileMode PermissionsMask_NonOwnerUsers_Write = UnixFileMode.GroupWrite | UnixFileMode.OtherWrite; - private const UnixFileMode PermissionsMask_AllUsers_ReadWrite = UnixFileMode.UserRead | UnixFileMode.UserRead | UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.OtherRead | UnixFileMode.OtherWrite; + private const UnixFileMode PermissionsMask_AllUsers_ReadWrite = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.OtherRead | UnixFileMode.OtherWrite; private const UnixFileMode PermissionsMask_AllUsers_ReadWriteExecute = PermissionsMask_AllUsers_ReadWrite | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; private const UnixFileMode PermissionsMask_Sticky = UnixFileMode.StickyBit; @@ -409,14 +429,15 @@ private static string InitalizeSharedFilesPath() internal static SafeFileHandle CreateOrOpenFile(string sharedMemoryFilePath, SharedMemoryId id, bool createIfNotExist, out bool createdFile) { - SafeFileHandle fd = Interop.Sys.Open(sharedMemoryFilePath, Interop.Sys.OpenFlags.O_RDWR, 0); + SafeFileHandle fd = Interop.Sys.Open(sharedMemoryFilePath, Interop.Sys.OpenFlags.O_RDWR | Interop.Sys.OpenFlags.O_CLOEXEC, 0); + Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); if (!fd.IsInvalid) { if (id.IsUserScope) { if (Interop.Sys.FStat(fd, out Interop.Sys.FileStatus fileStatus) != 0) { - Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + error = Interop.Sys.GetLastErrorInfo(); fd.Dispose(); throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath); } @@ -437,7 +458,12 @@ internal static SafeFileHandle CreateOrOpenFile(string sharedMemoryFilePath, Sha return fd; } - Debug.Assert(Interop.Sys.GetLastError() == Interop.Error.ENOENT); + if (error.Error == Interop.Error.ENAMETOOLONG) + { + throw new ArgumentException(SR.Arg_ArgumentException, "name"); + } + + Debug.Assert(error.Error == Interop.Error.ENOENT); if (!createIfNotExist) { createdFile = false; @@ -452,14 +478,20 @@ internal static SafeFileHandle CreateOrOpenFile(string sharedMemoryFilePath, Sha fd = Interop.Sys.Open( sharedMemoryFilePath, - Interop.Sys.OpenFlags.O_RDWR | Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL, + Interop.Sys.OpenFlags.O_RDWR | Interop.Sys.OpenFlags.O_CLOEXEC | Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL, (int)permissionsMask); + if (fd.IsInvalid) + { + error = Interop.Sys.GetLastErrorInfo(); + throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath); + } + int result = Interop.Sys.FChMod(fd, (int)permissionsMask); if (result != 0) { - Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo(); + error = Interop.Sys.GetLastErrorInfo(); fd.Dispose(); Interop.Sys.Unlink(sharedMemoryFilePath); throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath); @@ -634,7 +666,7 @@ internal static MemoryMappedFileHolder MemoryMapFile(SafeFileHandle fileHandle, Interop.Sys.MemoryMappedProtections.PROT_READ | Interop.Sys.MemoryMappedProtections.PROT_WRITE, Interop.Sys.MemoryMappedFlags.MAP_SHARED, fileHandle, - (long)sharedDataTotalByteCount); + 0); if (addr == -1) { @@ -714,18 +746,23 @@ internal sealed class SharedMemoryManager internal const string SharedMemorySharedMemoryDirectoryName = "shm"; - private readonly Lock _creationDeletionProcessLock = new Lock(); + private readonly LowLevelLock _creationDeletionProcessLock = new(); private SafeFileHandle? _creationDeletionLockFileHandle; private readonly Dictionary _uidToFileHandleMap = []; - public Lock.Scope AcquireCreationDeletionProcessLock() + public WaitSubsystem.LockHolder AcquireCreationDeletionProcessLock() { - return _creationDeletionProcessLock.EnterScope(); + return new WaitSubsystem.LockHolder(_creationDeletionProcessLock); + } + + public void VerifyCreationDeletionProcessLockIsLocked() + { + _creationDeletionProcessLock.VerifyIsLocked(); } public AutoReleaseFileLock AcquireCreationDeletionLockForId(SharedMemoryId id) { - Debug.Assert(_creationDeletionProcessLock.IsHeldByCurrentThread); + _creationDeletionProcessLock.VerifyIsLocked(); SafeFileHandle? fd = id.IsUserScope ? GetUserScopeCreationDeletionLockFileHandle(id.Uid) : _creationDeletionLockFileHandle; if (fd is null) { @@ -753,40 +790,39 @@ public AutoReleaseFileLock AcquireCreationDeletionLockForId(SharedMemoryId id) fd.Dispose(); throw Interop.GetExceptionForIoErrno(error, sharedMemoryDirectory); } - } - if (id.IsUserScope) - { - _uidToFileHandleMap.Add(id.Uid, fd); + if (id.IsUserScope) + { + _uidToFileHandleMap.Add(id.Uid, fd); + } + else + { + _creationDeletionLockFileHandle = fd; + } } - _creationDeletionLockFileHandle = fd; - bool acquired = SharedMemoryHelpers.TryAcquireFileLock(fd, nonBlocking: true, exclusive: true); Debug.Assert(acquired); return new AutoReleaseFileLock(fd); - } - - public void AddUserScopeCreationDeletionLockFileHandle(uint uid, SafeFileHandle fileHandle) - { - _uidToFileHandleMap[uid] = fileHandle; - } - public SafeFileHandle? GetUserScopeCreationDeletionLockFileHandle(uint uid) - { - _uidToFileHandleMap.TryGetValue(uid, out SafeFileHandle? fileHandle); - return fileHandle; + SafeFileHandle? GetUserScopeCreationDeletionLockFileHandle(uint uid) + { + _uidToFileHandleMap.TryGetValue(uid, out SafeFileHandle? fileHandle); + return fileHandle; + } } private Dictionary> _processDataHeaders = []; public void AddProcessDataHeader(SharedMemoryProcessDataHeader processDataHeader) { + VerifyCreationDeletionProcessLockIsLocked(); _processDataHeaders[processDataHeader._id] = processDataHeader; } public void RemoveProcessDataHeader(SharedMemoryProcessDataHeader processDataHeader) { + VerifyCreationDeletionProcessLockIsLocked(); _processDataHeaders.Remove(processDataHeader._id); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Mutex.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Mutex.Unix.cs index 1f48fcccdd2a52..9ad25fa61ecf0c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Mutex.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Mutex.Unix.cs @@ -62,11 +62,6 @@ private static string BuildNameForOptions(string name, NamedWaitHandleOptionsInt name = name.Substring(NamedWaitHandleOptionsInternal.CurrentSessionPrefix.Length); } - if (options.WasSpecified && options.CurrentUserOnly) - { - name = @"User\" + name; - } - return name; } 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 cfade222465223..cbfd3c216f5320 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 @@ -20,6 +20,8 @@ internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader< { private const byte SyncSystemVersion = 1; protected const int PollLoopMaximumSleepMilliseconds = 100; + protected const uint InvalidProcessId = unchecked((uint)-1); + protected const uint InvalidThreadId = unchecked((uint)-1); [FeatureSwitchDefinition("System.Threading.Mutex.NamedMutexUsePThreadMutex")] private static bool UsePThreadMutexes { get; } = AppContext.TryGetSwitch("System.Threading.Mutex.NamedMutexUsePThreadMutex", out bool value) @@ -31,15 +33,27 @@ internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader< private Thread? _lockOwnerThread; private NamedMutexProcessDataBase? _nextInThreadOwnedNamedMutexList; - protected SharedMemoryId Id => _processDataHeader._id; + public SharedMemoryId Id => _processDataHeader._id; public abstract bool IsLockOwnedByCurrentThread { get; } public bool IsLockOwnedByAnyThreadInThisProcess => _lockOwnerThread is not null; protected abstract void SetLockOwnerToCurrentThread(); - public MutexTryAcquireLockResult TryAcquireLock(WaitSubsystem.ThreadWaitInfo waitInfo, int timeoutMilliseconds) + public void IncrementRefCount() { + _processDataHeader.IncrementRefCount(); + } + + public void DecrementRefCount() + { + _processDataHeader.DecrementRefCount(); + } + + public MutexTryAcquireLockResult TryAcquireLock(WaitSubsystem.ThreadWaitInfo waitInfo, int timeoutMilliseconds, ref WaitSubsystem.LockHolder holder) + { + SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); + holder.Dispose(); MutexTryAcquireLockResult result = AcquireLockCore(timeoutMilliseconds); if (result == MutexTryAcquireLockResult.AcquiredLockRecursively) @@ -47,6 +61,13 @@ public MutexTryAcquireLockResult TryAcquireLock(WaitSubsystem.ThreadWaitInfo wai 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.Instance.AcquireCreationDeletionProcessLock(); SetLockOwnerToCurrentThread(); _lockCount = 1; _lockOwnerThread = waitInfo.Thread; @@ -63,6 +84,9 @@ public MutexTryAcquireLockResult TryAcquireLock(WaitSubsystem.ThreadWaitInfo wai private static void AddOwnedNamedMutex(Thread thread, NamedMutexProcessDataBase namedMutexProcessDataBase) { + SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); + // This adds a reference to the named mutex in the thread's wait info. + namedMutexProcessDataBase.IncrementRefCount(); namedMutexProcessDataBase._nextInThreadOwnedNamedMutexList = thread.WaitInfo.LockedNamedMutexesHead; thread.WaitInfo.LockedNamedMutexesHead = namedMutexProcessDataBase; } @@ -80,13 +104,24 @@ public void ReleaseLock() return; } - RemoveOwnedNamedMutex(Thread.CurrentThread, this); - _lockOwnerThread = null; - ReleaseLockCore(); + WaitSubsystem.LockHolder scope = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); + try + { + RemoveOwnedNamedMutex(Thread.CurrentThread, this); + _lockOwnerThread = null; + ReleaseLockCore(); + // This removes the reference to the named mutex in the thread's wait info. + DecrementRefCount(); + } + finally + { + scope.Dispose(); + } } private static void RemoveOwnedNamedMutex(Thread currentThread, NamedMutexProcessDataBase namedMutexProcessDataBase) { + SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); if (currentThread.WaitInfo.LockedNamedMutexesHead == namedMutexProcessDataBase) { currentThread.WaitInfo.LockedNamedMutexesHead = namedMutexProcessDataBase._nextInThreadOwnedNamedMutexList; @@ -111,9 +146,16 @@ private static void RemoveOwnedNamedMutex(Thread currentThread, NamedMutexProces public void Abandon() { IsAbandoned = true; + _lockCount = 0; - _lockOwnerThread = null; + + SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); ReleaseLockCore(); + + RemoveOwnedNamedMutex(Thread.CurrentThread, this); + _lockOwnerThread = null; + // This removes the reference to the named mutex in the thread's wait info. + DecrementRefCount(); } protected abstract MutexTryAcquireLockResult AcquireLockCore(int timeoutMilliseconds); @@ -124,49 +166,56 @@ public void Abandon() internal static unsafe SharedMemoryProcessDataHeader? CreateOrOpen(string name, bool isUserScope, bool createIfNotExist, bool acquireLockIfCreated, out bool created) { - using Lock.Scope creationDeletionProcessLock = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); - - SharedMemoryProcessDataHeader? processDataHeader = SharedMemoryProcessDataHeader.CreateOrOpen( - name, - isUserScope, - new SharedMemorySharedDataHeader(SharedMemoryType.Mutex, SyncSystemVersion), - UsePThreadMutexes ? NamedMutexProcessDataWithPThreads.SharedData.Size : (nuint)sizeof(NamedMutexProcessDataNoPThreads.SharedData), - createIfNotExist, - acquireLockIfCreated, - out created, - out AutoReleaseFileLock creationDeletionLockFileScope); - - if (processDataHeader is null) + WaitSubsystem.LockHolder creationDeletionProcessLock = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); + try { - return null; - } - using (creationDeletionLockFileScope) - { - if (created) + SharedMemoryProcessDataHeader? processDataHeader = SharedMemoryProcessDataHeader.CreateOrOpen( + name, + isUserScope, + new SharedMemorySharedDataHeader(SharedMemoryType.Mutex, SyncSystemVersion), + UsePThreadMutexes ? NamedMutexProcessDataWithPThreads.SharedData.Size : (nuint)sizeof(NamedMutexProcessDataNoPThreads.SharedData), + createIfNotExist, + acquireLockIfCreated, + out created, + out AutoReleaseFileLock creationDeletionLockFileScope); + + if (processDataHeader is null) { - InitializeSharedData(SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader)); + return null; } - if (processDataHeader._processData is null) + using (creationDeletionLockFileScope) { - if (UsePThreadMutexes) - { - processDataHeader._processData = new NamedMutexProcessDataWithPThreads(processDataHeader); - } - else + if (created) { - processDataHeader._processData = new NamedMutexProcessDataNoPThreads(processDataHeader, created); + InitializeSharedData(SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader)); } - if (created && acquireLockIfCreated) + if (processDataHeader._processData is null) { - MutexTryAcquireLockResult acquireResult = processDataHeader._processData.TryAcquireLock(Thread.CurrentThread.WaitInfo, timeoutMilliseconds: 0); - Debug.Assert(acquireResult != MutexTryAcquireLockResult.AcquiredLock); + 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; + return processDataHeader; + } + } + finally + { + creationDeletionProcessLock.Dispose(); } } @@ -180,15 +229,15 @@ private static unsafe void InitializeSharedData(void* v) Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); throw Interop.GetExceptionForIoErrno(errorInfo, "Failed to initialize pthread mutex"); } - sharedData->LockOwnerProcessId = SharedMemoryHelpers.InvalidProcessId; - sharedData->LockOwnerThreadId = SharedMemoryHelpers.InvalidThreadId; + sharedData->LockOwnerProcessId = InvalidProcessId; + sharedData->LockOwnerThreadId = InvalidThreadId; sharedData->IsAbandoned = false; } else { NamedMutexProcessDataNoPThreads.SharedData* sharedData = (NamedMutexProcessDataNoPThreads.SharedData*)v; - sharedData->LockOwnerProcessId = SharedMemoryHelpers.InvalidProcessId; - sharedData->LockOwnerThreadId = SharedMemoryHelpers.InvalidThreadId; + sharedData->LockOwnerProcessId = InvalidProcessId; + sharedData->LockOwnerThreadId = InvalidThreadId; sharedData->IsAbandoned = false; sharedData->TimedWaiterCount = 0; } @@ -274,8 +323,8 @@ protected override void ReleaseLockCore() { Debug.Assert(IsLockOwnedByCurrentThread); Debug.Assert(_lockCount == 0); - _sharedData->LockOwnerProcessId = 0; - _sharedData->LockOwnerThreadId = 0; + _sharedData->LockOwnerProcessId = InvalidProcessId; + _sharedData->LockOwnerThreadId = InvalidThreadId; Interop.Sys.PThreadMutex_Release(_sharedData->LockAddress); } @@ -374,8 +423,8 @@ private bool IsLockOwnedByAnyThread { get { - return _sharedData->LockOwnerProcessId != SharedMemoryHelpers.InvalidProcessId && - _sharedData->LockOwnerThreadId != SharedMemoryHelpers.InvalidThreadId; + return _sharedData->LockOwnerProcessId != InvalidProcessId && + _sharedData->LockOwnerThreadId != InvalidThreadId; } } @@ -383,8 +432,8 @@ protected override void ReleaseLockCore() { Debug.Assert(IsLockOwnedByCurrentThread); Debug.Assert(_lockCount == 0); - _sharedData->LockOwnerProcessId = 0; - _sharedData->LockOwnerThreadId = 0; + _sharedData->LockOwnerProcessId = InvalidProcessId; + _sharedData->LockOwnerThreadId = InvalidThreadId; Interop.Sys.FLock(_sharedLockFileHandle, Interop.Sys.LockOperations.LOCK_UN); _processLevelLock.Exit(); @@ -401,6 +450,7 @@ public override void Close(bool releaseSharedData) base.Close(releaseSharedData); _sharedLockFileHandle.Dispose(); + _processLevelLock.Dispose(); } protected override unsafe MutexTryAcquireLockResult AcquireLockCore(int timeoutMilliseconds) @@ -413,84 +463,94 @@ protected override unsafe MutexTryAcquireLockResult AcquireLockCore(int timeoutM // 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. - using Lock.Scope scope = _processLevelLock.EnterScope(); - - if (_lockCount > 0) + bool releaseProcessLock = true; + if (!_processLevelLock.TryEnter(timeoutMilliseconds)) { - Debug.Assert(IsLockOwnedByCurrentThread); - // The lock is already owned by the current thread. - checked - { - _lockCount++; - } - return MutexTryAcquireLockResult.AcquiredLockRecursively; + return MutexTryAcquireLockResult.TimedOut; } - switch (timeoutMilliseconds) + try { - case -1: - bool acquiredLock = false; - while (_sharedData->TimedWaiterCount > 0) + if (_lockCount > 0) + { + Debug.Assert(IsLockOwnedByCurrentThread); + // The lock is already owned by the current thread. + checked { - if (SharedMemoryHelpers.TryAcquireFileLock(_sharedLockFileHandle, nonBlocking: true)) + _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) { - acquiredLock = true; break; } - Thread.Sleep(PollLoopMaximumSleepMilliseconds); - } - if (acquiredLock) - { + acquiredLock = SharedMemoryHelpers.TryAcquireFileLock(_sharedLockFileHandle, nonBlocking: false); + Debug.Assert(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)) - { + case 0: + if (!SharedMemoryHelpers.TryAcquireFileLock(_sharedLockFileHandle, nonBlocking: true)) + { + return MutexTryAcquireLockResult.TimedOut; + } break; - } - - _sharedData->TimedWaiterCount++; - - do + default: { - int elapsedMilliseconds = Environment.TickCount - startTime; - if (elapsedMilliseconds >= timeoutMilliseconds) + if (SharedMemoryHelpers.TryAcquireFileLock(_sharedLockFileHandle, nonBlocking: true)) { - _sharedData->TimedWaiterCount--; - return MutexTryAcquireLockResult.TimedOut; + break; } - int remainingTimeoutMilliseconds = timeoutMilliseconds - elapsedMilliseconds; - int sleepMilliseconds = Math.Min(PollLoopMaximumSleepMilliseconds, remainingTimeoutMilliseconds); - Thread.Sleep(sleepMilliseconds); - } while (!SharedMemoryHelpers.TryAcquireFileLock(_sharedLockFileHandle, nonBlocking: true)); - _sharedData->TimedWaiterCount--; - break; - } - } + _sharedData->TimedWaiterCount++; - // We've now acquired the lock. - // We're going to release the scoped process lock we took above, - // so recursively acquire it to maintain the lock ownership. - _processLevelLock.Enter(); + 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; + // Detect abandoned lock that isn't marked as abandoned. + if (IsLockOwnedByAnyThread) + return MutexTryAcquireLockResult.AcquiredLockButMutexWasAbandoned; - return MutexTryAcquireLockResult.AcquiredLock; + return MutexTryAcquireLockResult.AcquiredLock; + } + finally + { + if (releaseProcessLock) + { + _processLevelLock.Exit(); + } + } } [StructLayout(LayoutKind.Sequential)] @@ -550,113 +610,79 @@ internal enum MutexTryAcquireLockResult : byte internal static partial class WaitSubsystem { - private sealed class NamedMutex(SharedMemoryProcessDataHeader processDataHeader) : IWaitableObject + private sealed class NamedMutex : IWaitableObject { - /// - /// Dictionary to look up named mutexes. - /// - private static Dictionary? s_namedObjects; - private int _referenceCount = 1; + private readonly SharedMemoryProcessDataHeader _processDataHeader; - public void IncrementRefCount() + public NamedMutex(SharedMemoryProcessDataHeader processDataHeader) { - Debug.Assert(_referenceCount > 0, "Ref count should not be negative."); - _referenceCount++; + _processDataHeader = processDataHeader; } public int Wait_Locked(ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool interruptible, bool prioritize, ref LockHolder lockHolder) { - // We're done touching the wait subsystem data structures, so we can release the lock on them. - lockHolder.Dispose(); - MutexTryAcquireLockResult result = processDataHeader._processData!.TryAcquireLock(waitInfo, timeoutMilliseconds); - return result switch + LockHolder scope = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); + try { - MutexTryAcquireLockResult.AcquiredLock => WaitHandle.WaitSuccess, - MutexTryAcquireLockResult.AcquiredLockButMutexWasAbandoned => WaitHandle.WaitAbandoned, - MutexTryAcquireLockResult.TimedOut => WaitHandle.WaitTimeout, - _ => throw new InvalidOperationException("Unexpected result from TryAcquireLock") - }; + lockHolder.Dispose(); + 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 InvalidOperationException("Unexpected result from TryAcquireLock") + }; + } + finally + { + scope.Dispose(); + } } public void Signal(int count, ref LockHolder lockHolder) { lockHolder.Dispose(); - processDataHeader._processData!.ReleaseLock(); + _processDataHeader._processData!.ReleaseLock(); } public void OnDeleteHandle() { - s_lock.Acquire(); + LockHolder scope = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); try { - // Multiple handles may refer to the same named object. Make sure the object - // is only abandoned once the last handle to it is deleted. Also, remove the - // object from the named objects dictionary at this point. - _referenceCount--; - if (_referenceCount > 0) - { - return; - } - - processDataHeader.Close(); + _processDataHeader.DecrementRefCount(); } finally { - s_lock.Release(); + scope.Dispose(); } } - public static NamedMutex? CreateNamedMutex_Locked(string name, bool isUserScope, bool initiallyOwned, out bool createdNew) + public static NamedMutex? CreateNamedMutex(string name, bool isUserScope, bool initiallyOwned, out bool createdNew) { - s_lock.VerifyIsLocked(); - - s_namedObjects ??= []; - - if (s_namedObjects.TryGetValue(name, out NamedMutex? result)) + var namedMutexProcessData = NamedMutexProcessDataBase.CreateOrOpen(name, isUserScope, createIfNotExist: true, acquireLockIfCreated: initiallyOwned, out createdNew); + if (namedMutexProcessData is null) { createdNew = false; - result._referenceCount++; - } - else - { - var namedMutexProcessData = NamedMutexProcessDataBase.CreateOrOpen(name, isUserScope, createIfNotExist: true, acquireLockIfCreated: initiallyOwned, out createdNew); - if (namedMutexProcessData is null) - { - createdNew = false; - return null; - } - result = new NamedMutex(namedMutexProcessData); - s_namedObjects.Add(name, result); - return result; + return null; } - return result; + Debug.Assert(namedMutexProcessData._processData is not null); + return new NamedMutex(namedMutexProcessData); } public static OpenExistingResult OpenNamedMutex(string name, bool isUserScope, out NamedMutex? result) { - s_lock.VerifyIsLocked(); - - if (s_namedObjects is null || !s_namedObjects.TryGetValue(name, out NamedMutex? namedMutex)) + var namedMutexProcessData = NamedMutexProcessDataBase.CreateOrOpen(name, isUserScope, createIfNotExist: false, acquireLockIfCreated: false, out _); + if (namedMutexProcessData is null) { - // We haven't seen this named mutex before in this process. - // Try seeing if it exists on the system. - var namedMutexProcessData = NamedMutexProcessDataBase.CreateOrOpen(name, isUserScope, createIfNotExist: false, acquireLockIfCreated: false, out _); - if (namedMutexProcessData is null) - { - // This mutex does not exist. - result = null; - return OpenExistingResult.NameNotFound; - } - - // We found the mutex on the system, so we can open it. - s_namedObjects ??= []; - namedMutex = new NamedMutex(namedMutexProcessData); - s_namedObjects.Add(name, namedMutex); + result = null; + return OpenExistingResult.NameNotFound; } - namedMutex.IncrementRefCount(); - result = namedMutex; + Debug.Assert(namedMutexProcessData._processData is not null); + result = new NamedMutex(namedMutexProcessData); return OpenExistingResult.Success; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs index 32e03af90108e5..0aa5d720c0b1be 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.IO; namespace System.Threading { @@ -564,12 +565,12 @@ public NamedMutexProcessDataBase? LockedNamedMutexesHead { get { - s_lock.VerifyIsLocked(); + SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); return _lockedNamedMutexesHead; } set { - s_lock.VerifyIsLocked(); + SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); _lockedNamedMutexesHead = value; } } @@ -592,7 +593,15 @@ public void OnThreadExiting() waitableObject.AbandonMutex(); Debug.Assert(LockedMutexesHead != waitableObject); } + } + finally + { + s_lock.Release(); + } + LockHolder scope = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); + try + { while (true) { NamedMutexProcessDataBase? namedMutex = LockedNamedMutexesHead; @@ -607,7 +616,7 @@ public void OnThreadExiting() } finally { - s_lock.Release(); + scope.Dispose(); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs index 8a5b304cd4a57a..388179535d8f87 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs @@ -196,25 +196,13 @@ public static SafeWaitHandle NewMutex(bool initiallyOwned) public static SafeWaitHandle? CreateNamedMutex(bool initiallyOwned, string name, bool isUserScope, out bool createdNew) { - // For initially owned, newly created named mutexes, there is a potential race - // between adding the mutex to the named object table and initially acquiring it. - // To avoid the possibility of another thread retrieving the mutex via its name - // before we managed to acquire it, we perform both steps while holding s_lock. - LockHolder lockHolder = new LockHolder(s_lock); - try - { - NamedMutex? namedMutex = NamedMutex.CreateNamedMutex_Locked(name, isUserScope, initiallyOwned: initiallyOwned, out createdNew); - if (namedMutex == null) - { - return null; - } - SafeWaitHandle safeWaitHandle = NewHandle(namedMutex); - return safeWaitHandle; - } - finally + NamedMutex? namedMutex = NamedMutex.CreateNamedMutex(name, isUserScope, initiallyOwned: initiallyOwned, out createdNew); + if (namedMutex == null) { - lockHolder.Dispose(); + return null; } + SafeWaitHandle safeWaitHandle = NewHandle(namedMutex); + return safeWaitHandle; } public static OpenExistingResult OpenNamedMutex(string name, bool isUserScope, out SafeWaitHandle? result) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs index 7f892a32a7e646..d32a5a2200e1e9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs @@ -82,7 +82,7 @@ public static WaitableObject NewMutex() return new WaitableObject(WaitableObjectType.Mutex, 1, 1, null, new OwnershipInfo()); } - public static WaitableObject? CreateNamedMutex_Locked(string name, out bool createdNew) + public static WaitableObject? CreateNamedMutex(string name, out bool createdNew) { s_lock.VerifyIsLocked(); From 6495e44cb8b9e2077733c5fe4df99afa0612acde Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 14 Jul 2025 21:18:34 +0000 Subject: [PATCH 10/47] Adjust PThread setting default based on testing with our cross-build images --- .../src/System/Threading/NamedMutex.Unix.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 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 cbfd3c216f5320..2d7181c36119df 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 @@ -23,10 +23,13 @@ internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader< protected const uint InvalidProcessId = unchecked((uint)-1); protected const uint InvalidThreadId = unchecked((uint)-1); - [FeatureSwitchDefinition("System.Threading.Mutex.NamedMutexUsePThreadMutex")] - private static bool UsePThreadMutexes { get; } = AppContext.TryGetSwitch("System.Threading.Mutex.NamedMutexUsePThreadMutex", out bool value) - ? value - : false; + // Use PThread mutex-backed named mutexes on all platforms except Apple platforms. + // 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 SharedMemoryProcessDataHeader _processDataHeader = header; protected nuint _lockCount; From 189a71e9d8540f4f00f9ec4ee96b717d72b3de40 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 14 Jul 2025 21:28:33 +0000 Subject: [PATCH 11/47] Handle EOWNERDEAD --- src/native/libs/System.Native/pal_threading.c | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/native/libs/System.Native/pal_threading.c b/src/native/libs/System.Native/pal_threading.c index 4583779fc72e13..f0b7d3f7ca2584 100644 --- a/src/native/libs/System.Native/pal_threading.c +++ b/src/native/libs/System.Native/pal_threading.c @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "pal_config.h" +#include #include "pal_threading.h" #include @@ -320,27 +321,28 @@ int32_t SystemNative_PThreadMutex_Init(void* mutex) return error; } -int32_t SystemNative_PThreadMutex_Acquire(void* mutex, int32_t timeoutMilliseconds) +static int32_t AcquirePThreadMutexWithTimeout(pthread_mutex_t* mutex, int32_t timeoutMilliseconds) { assert(mutex != NULL); if (timeoutMilliseconds == -1) { - return pthread_mutex_lock((pthread_mutex_t*)mutex); + return pthread_mutex_lock(mutex); } else if (timeoutMilliseconds == 0) { - return pthread_mutex_trylock((pthread_mutex_t*)mutex); + return pthread_mutex_trylock(mutex); } // Calculate the time at which a timeout should occur, and wait. Older versions of OSX don't support clock_gettime with // CLOCK_MONOTONIC, so we instead compute the relative timeout duration, and use a relative variant of the timed wait. struct timespec timeoutTimeSpec; + int32_t error = 0; #if HAVE_CLOCK_GETTIME_NSEC_NP timeoutTimeSpec.tv_sec = timeoutMilliseconds / 1000; timeoutTimeSpec.tv_nsec = (timeoutMilliseconds % 1000) * 1000 * 1000; - error = pthread_mutex_reltimedlock_np((pthread_mutex_t*)mutex, &timeoutTimeSpec); + error = pthread_mutex_reltimedlock_np(mutex, &timeoutTimeSpec); #else #if HAVE_PTHREAD_CONDATTR_SETCLOCK && HAVE_CLOCK_MONOTONIC int error = clock_gettime(CLOCK_MONOTONIC, &timeoutTimeSpec); @@ -362,6 +364,20 @@ int32_t SystemNative_PThreadMutex_Acquire(void* mutex, int32_t timeoutMillisecon #endif } +int32_t SystemNative_PThreadMutex_Acquire(void* mutex, int32_t timeoutMilliseconds) +{ + int32_t result = AcquirePThreadMutexWithTimeout((pthread_mutex_t*)mutex, timeoutMilliseconds); + + if (result == EOWNERDEAD) + { + // The mutex was abandoned by the previous owner. + // Make it consistent so that it can be used again. + int setConsistentResult = pthread_mutex_consistent((pthread_mutex_t*)mutex); + } + + return SystemNative_ConvertErrorPlatformToPal(result); +} + int32_t SystemNative_PThreadMutex_Release(void* mutex) { assert(mutex != NULL); From 7cfb931cf8c9fb572b937327684b6f1bd63bcb54 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 14 Jul 2025 21:32:20 +0000 Subject: [PATCH 12/47] Add stubs to wasi --- src/native/libs/System.Native/pal_threading.c | 10 +++---- .../libs/System.Native/pal_threading_wasi.c | 29 +++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/native/libs/System.Native/pal_threading.c b/src/native/libs/System.Native/pal_threading.c index f0b7d3f7ca2584..3e84a16511af28 100644 --- a/src/native/libs/System.Native/pal_threading.c +++ b/src/native/libs/System.Native/pal_threading.c @@ -305,7 +305,7 @@ int32_t SystemNative_PThreadMutex_Init(void* mutex) int error = pthread_mutexattr_init(&mutexAttributes); if (error != 0) { - return error; + return ConvertErrorPlatformToPal(error); } error = pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_RECURSIVE); @@ -318,7 +318,7 @@ int32_t SystemNative_PThreadMutex_Init(void* mutex) assert(error == 0); error = pthread_mutex_init((pthread_mutex_t*)mutex, &mutexAttributes); - return error; + return ConvertErrorPlatformToPal(error); } static int32_t AcquirePThreadMutexWithTimeout(pthread_mutex_t* mutex, int32_t timeoutMilliseconds) @@ -375,19 +375,19 @@ int32_t SystemNative_PThreadMutex_Acquire(void* mutex, int32_t timeoutMillisecon int setConsistentResult = pthread_mutex_consistent((pthread_mutex_t*)mutex); } - return SystemNative_ConvertErrorPlatformToPal(result); + return ConvertErrorPlatformToPal(result); } int32_t SystemNative_PThreadMutex_Release(void* mutex) { assert(mutex != NULL); - return pthread_mutex_unlock((pthread_mutex_t*)mutex); + return ConvertErrorPlatformToPal(pthread_mutex_unlock((pthread_mutex_t*)mutex)); } int32_t SystemNative_PThreadMutex_Destroy(void* mutex) { assert(mutex != NULL); - return pthread_mutex_destroy((pthread_mutex_t*)mutex); + return ConvertErrorPlatformToPal(pthread_mutex_destroy((pthread_mutex_t*)mutex)); } int32_t SystemNative_PThreadMutex_Size(void) diff --git a/src/native/libs/System.Native/pal_threading_wasi.c b/src/native/libs/System.Native/pal_threading_wasi.c index 83fec6be55073b..ad24ae64fcb21c 100644 --- a/src/native/libs/System.Native/pal_threading_wasi.c +++ b/src/native/libs/System.Native/pal_threading_wasi.c @@ -3,6 +3,7 @@ #include "pal_config.h" #include "pal_threading.h" +#include #include #include @@ -94,3 +95,31 @@ uint32_t SystemNative_TryGetUInt32OSThreadId(void) { return (uint32_t)-1; } + +int32_t SystemNative_PThreadMutex_Init(void* mutex) +{ + return 0; +} + +int32_t SystemNative_PThreadMutex_Acquire(void* mutex, int32_t timeoutMilliseconds) +{ + assert(mutex != NULL); + return Error_EINVAL; +} + +int32_t SystemNative_PThreadMutex_Release(void* mutex) +{ + assert(mutex != NULL); + return Error_EINVAL; +} + +int32_t SystemNative_PThreadMutex_Destroy(void* mutex) +{ + assert(mutex != NULL); + return Error_EINVAL; +} + +int32_t SystemNative_PThreadMutex_Size(void) +{ + return 0; +} From 69fe72f33f03a974d969b6e8a9331e200b53ffc1 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 14 Jul 2025 21:59:08 +0000 Subject: [PATCH 13/47] Don't use cross-process mutexes on mobile platforms. Only use the in-proc model --- .../System.Private.CoreLib.Shared.projitems | 10 +++- .../WaitSubsystem.ThreadWaitInfo.Unix.cs | 6 ++ .../System/Threading/WaitSubsystem.Unix.cs | 58 +++++++++++++++++++ .../WaitSubsystem.WaitableObject.Unix.cs | 2 +- 4 files changed, 73 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 9143b6f8c1e572..d9566e9921a223 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -24,6 +24,7 @@ true true true + true $(DefineConstants);BIGENDIAN @@ -49,6 +50,9 @@ $(DefineConstants);TARGET_SOLARIS $(DefineConstants);TARGET_HAIKU + + $(DefineConstants);FEATURE_CROSS_PROCESS_MUTEX + @@ -2838,11 +2842,9 @@ - - @@ -2850,6 +2852,10 @@ + + + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs index 0aa5d720c0b1be..a6dd3b74109e03 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs @@ -87,12 +87,14 @@ public sealed class ThreadWaitInfo /// private WaitableObject? _lockedMutexesHead; +#if FEATURE_CROSS_PROCESS_MUTEX /// /// Linked list of named mutexes that are locked by the thread and need to be abandoned before the thread exits. /// The linked list has only a head and no tail, which means acquired mutexes are prepended and /// mutexes are abandoned in reverse order. /// private NamedMutexProcessDataBase? _lockedNamedMutexesHead; +#endif public ThreadWaitInfo(Thread thread) { @@ -561,6 +563,7 @@ public WaitableObject? LockedMutexesHead } } +#if FEATURE_CROSS_PROCESS_MUTEX public NamedMutexProcessDataBase? LockedNamedMutexesHead { get @@ -574,6 +577,7 @@ public NamedMutexProcessDataBase? LockedNamedMutexesHead _lockedNamedMutexesHead = value; } } +#endif public void OnThreadExiting() { @@ -599,6 +603,7 @@ public void OnThreadExiting() s_lock.Release(); } +#if FEATURE_CROSS_PROCESS_MUTEX LockHolder scope = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); try { @@ -618,6 +623,7 @@ public void OnThreadExiting() { scope.Dispose(); } +#endif } public sealed class WaitedListNode diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs index 388179535d8f87..56c69730c70d4f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs @@ -145,7 +145,10 @@ public void Dispose() } } +#pragma warning disable CA1859 // Change type of parameter 'waitableObject' from 'System.Threading.IWaitableObject' to 'System.Threading.WaitableObject' for improved performance + // Some platforms have more implementations of IWaitableObject than just WaitableObject. private static SafeWaitHandle NewHandle(IWaitableObject waitableObject) +#pragma warning restore CA1859 // Change type of parameter 'waitableObject' from 'System.Threading.IWaitableObject' to 'System.Threading.WaitableObject' for improved performance { var safeWaitHandle = new SafeWaitHandle(); @@ -194,6 +197,7 @@ public static SafeWaitHandle NewMutex(bool initiallyOwned) return safeWaitHandle; } +#if FEATURE_CROSS_PROCESS_MUTEX public static SafeWaitHandle? CreateNamedMutex(bool initiallyOwned, string name, bool isUserScope, out bool createdNew) { NamedMutex? namedMutex = NamedMutex.CreateNamedMutex(name, isUserScope, initiallyOwned: initiallyOwned, out createdNew); @@ -211,6 +215,58 @@ public static OpenExistingResult OpenNamedMutex(string name, bool isUserScope, o result = status == OpenExistingResult.Success ? NewHandle(mutex!) : null; return status; } +#else + public static SafeWaitHandle? CreateNamedMutex(bool initiallyOwned, string name, bool isUserScope, out bool createdNew) + { + // For initially owned, newly created named mutexes, there is a potential race + // between adding the mutex to the named object table and initially acquiring it. + // To avoid the possibility of another thread retrieving the mutex via its name + // before we managed to acquire it, we perform both steps while holding s_lock. + LockHolder lockHolder = new LockHolder(s_lock); + try + { + if (isUserScope) + { + name = $"User\\{name}"; + } + + WaitableObject? waitableObject = WaitableObject.CreateNamedMutex_Locked(name, out createdNew); + if (waitableObject == null) + { + return null; + } + SafeWaitHandle safeWaitHandle = NewHandle(waitableObject); + if (!initiallyOwned || !createdNew) + { + return safeWaitHandle; + } + + // Acquire the mutex. A thread's has a reference to all es locked + // by the thread. See . So, acquire the lock only after all + // possibilities for exceptions have been exhausted. + ThreadWaitInfo waitInfo = Thread.CurrentThread.WaitInfo; + int status = waitableObject.Wait_Locked(waitInfo, timeoutMilliseconds: 0, interruptible: false, prioritize: false, ref lockHolder); + Debug.Assert(status == 0); + return safeWaitHandle; + } + finally + { + lockHolder.Dispose(); + } + } + + public static OpenExistingResult OpenNamedMutex(string name, bool isUserScope, out SafeWaitHandle? result) + { + if (isUserScope) + { + name = $"User\\{name}"; + } + + OpenExistingResult status = WaitableObject.OpenNamedMutex(name, out WaitableObject? mutex); + result = status == OpenExistingResult.Success ? NewHandle(mutex!) : null; + return status; + } +#endif public static void DeleteHandle(IntPtr handle) { @@ -328,11 +384,13 @@ public static int Wait( ThreadWaitInfo waitInfo = Thread.CurrentThread.WaitInfo; +#if FEATURE_CROSS_PROCESS_MUTEX if (waitHandles.Length == 1 && HandleManager.FromHandle(waitHandles[0]) is NamedMutex namedMutex) { // Named mutexes don't participate in the wait subsystem fully. return namedMutex.Wait(waitInfo, timeoutMilliseconds, interruptible: true, prioritize: false); } +#endif WaitableObject?[] waitableObjects = waitInfo.GetWaitedObjectArray(waitHandles.Length); bool success = false; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs index d32a5a2200e1e9..7f892a32a7e646 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs @@ -82,7 +82,7 @@ public static WaitableObject NewMutex() return new WaitableObject(WaitableObjectType.Mutex, 1, 1, null, new OwnershipInfo()); } - public static WaitableObject? CreateNamedMutex(string name, out bool createdNew) + public static WaitableObject? CreateNamedMutex_Locked(string name, out bool createdNew) { s_lock.VerifyIsLocked(); From ee0f7ce38bcc6cb81c14f45e1ef24b88e7e632c9 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 14 Jul 2025 22:57:17 +0000 Subject: [PATCH 14/47] Move the whole "shared data" struct for the pthread mutex impl across the managed->native veil. --- .../Interop.LowLevelCrossProcessMutex.cs | 43 +++++++ .../System.Native/Interop.PThreadMutex.cs | 26 ----- .../System.Private.CoreLib.Shared.projitems | 4 +- .../src/System/Threading/NamedMutex.Unix.cs | 70 ++++-------- src/native/libs/System.Native/entrypoints.c | 14 ++- src/native/libs/System.Native/pal_threading.c | 107 ++++++++++++------ src/native/libs/System.Native/pal_threading.h | 11 ++ 7 files changed, 161 insertions(+), 114 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Unix/System.Native/Interop.LowLevelCrossProcessMutex.cs delete mode 100644 src/libraries/Common/src/Interop/Unix/System.Native/Interop.PThreadMutex.cs diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.LowLevelCrossProcessMutex.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.LowLevelCrossProcessMutex.cs new file mode 100644 index 00000000000000..b479b52d9eae98 --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.LowLevelCrossProcessMutex.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static unsafe partial class Sys + { + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_LowLevelCrossProcessMutex_Init", SetLastError = true)] + internal static partial int LowLevelCrossProcessMutex_Init(void* mutex); + + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_LowLevelCrossProcessMutex_Acquire", SetLastError = true)] + internal static partial int LowLevelCrossProcessMutex_Acquire(void* mutex, int timeoutMilliseconds); + + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_LowLevelCrossProcessMutex_Release", SetLastError = true)] + internal static partial int LowLevelCrossProcessMutex_Release(void* mutex); + + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_LowLevelCrossProcessMutex_Destroy", SetLastError = true)] + internal static partial int LowLevelCrossProcessMutex_Destroy(void* mutex); + + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_LowLevelCrossProcessMutex_Size")] + [SuppressGCTransition] + internal static partial int LowLevelCrossProcessMutex_Size(); + + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_LowLevelCrossProcessMutex_GetOwnerProcessAndThreadId", SetLastError = true)] + [SuppressGCTransition] + internal static partial void LowLevelCrossProcessMutex_GetOwnerProcessAndThreadId(void* mutex, out uint processId, out uint threadId); + + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_LowLevelCrossProcessMutex_SetOwnerProcessAndThreadId", SetLastError = true)] + [SuppressGCTransition] + internal static partial void LowLevelCrossProcessMutex_SetOwnerProcessAndThreadId(void* mutex, uint processId, uint threadId); + + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_LowLevelCrossProcessMutex_IsAbandoned", SetLastError = true)] + [SuppressGCTransition] + [return: MarshalAs(UnmanagedType.U1)] + internal static partial bool LowLevelCrossProcessMutex_IsAbandoned(void* mutex); + + [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_LowLevelCrossProcessMutex_SetAbandoned", SetLastError = true)] + [SuppressGCTransition] + internal static partial void LowLevelCrossProcessMutex_SetAbandoned(void* mutex, [MarshalAs(UnmanagedType.U1)] bool abandoned); + } +} diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PThreadMutex.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PThreadMutex.cs deleted file mode 100644 index fd49ebb36fdcec..00000000000000 --- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.PThreadMutex.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.InteropServices; - -internal static partial class Interop -{ - internal static unsafe partial class Sys - { - [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PThreadMutex_Init", SetLastError = true)] - internal static partial int PThreadMutex_Init(void* mutex); - - [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PThreadMutex_Acquire", SetLastError = true)] - internal static partial int PThreadMutex_Acquire(void* mutex, int timeoutMilliseconds); - - [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PThreadMutex_Release", SetLastError = true)] - internal static partial int PThreadMutex_Release(void* mutex); - - [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PThreadMutex_Destroy", SetLastError = true)] - internal static partial int PThreadMutex_Destroy(void* mutex); - - [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_PThreadMutex_Size")] - [SuppressGCTransition] - internal static partial int PThreadMutex_Size(); - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index d9566e9921a223..7574d388424db9 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2488,8 +2488,8 @@ Common\Interop\Unix\System.Native\Interop.PathConf.cs - - Common\Interop\Unix\System.Native\Interop.PThreadMutex.cs + + Common\Interop\Unix\System.Native\Interop.LowLevelCrossProcessMutex.cs Common\Interop\Unix\System.Native\Interop.PosixFAdvise.cs 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 2d7181c36119df..f38b0972b4b53c 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 @@ -177,7 +177,7 @@ public void Abandon() name, isUserScope, new SharedMemorySharedDataHeader(SharedMemoryType.Mutex, SyncSystemVersion), - UsePThreadMutexes ? NamedMutexProcessDataWithPThreads.SharedData.Size : (nuint)sizeof(NamedMutexProcessDataNoPThreads.SharedData), + UsePThreadMutexes ? NamedMutexProcessDataWithPThreads.SharedDataSize : (nuint)sizeof(NamedMutexProcessDataNoPThreads.SharedData), createIfNotExist, acquireLockIfCreated, out created, @@ -226,15 +226,11 @@ private static unsafe void InitializeSharedData(void* v) { if (UsePThreadMutexes) { - NamedMutexProcessDataWithPThreads.SharedData* sharedData = (NamedMutexProcessDataWithPThreads.SharedData*)v; - if (Interop.Sys.PThreadMutex_Init(sharedData->LockAddress) != 0) + if (Interop.Sys.LowLevelCrossProcessMutex_Init(v) != 0) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); throw Interop.GetExceptionForIoErrno(errorInfo, "Failed to initialize pthread mutex"); } - sharedData->LockOwnerProcessId = InvalidProcessId; - sharedData->LockOwnerThreadId = InvalidThreadId; - sharedData->IsAbandoned = false; } else { @@ -257,32 +253,36 @@ public virtual void Close(bool releaseSharedData) internal sealed unsafe class NamedMutexProcessDataWithPThreads(SharedMemoryProcessDataHeader processDataHeader) : NamedMutexProcessDataBase(processDataHeader) { - private readonly SharedData* _sharedData = (SharedData*)SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader); + public static nuint SharedDataSize { get; } = (nuint)Interop.Sys.LowLevelCrossProcessMutex_Size(); + private readonly void* _sharedData = SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader); public override bool IsLockOwnedByCurrentThread { get { - return _sharedData->LockOwnerProcessId == (uint)Environment.ProcessId && - _sharedData->LockOwnerThreadId == (uint)Thread.CurrentThread.ManagedThreadId; + Interop.Sys.LowLevelCrossProcessMutex_GetOwnerProcessAndThreadId(_sharedData, out uint ownerProcessId, out uint ownerThreadId); + return ownerProcessId == (uint)Environment.ProcessId && + ownerThreadId == (uint)Thread.CurrentThread.ManagedThreadId; } } protected override void SetLockOwnerToCurrentThread() { - _sharedData->LockOwnerProcessId = (uint)Environment.ProcessId; - _sharedData->LockOwnerThreadId = (uint)Thread.CurrentThread.ManagedThreadId; + Interop.Sys.LowLevelCrossProcessMutex_SetOwnerProcessAndThreadId( + _sharedData, + (uint)Environment.ProcessId, + (uint)Thread.CurrentThread.ManagedThreadId); } protected override bool IsAbandoned { - get => _sharedData->IsAbandoned; - set => _sharedData->IsAbandoned = value; + 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.PThreadMutex_Acquire(_sharedData->LockAddress, timeoutMilliseconds); + Interop.Error lockResult = (Interop.Error)Interop.Sys.LowLevelCrossProcessMutex_Acquire(_sharedData, timeoutMilliseconds); MutexTryAcquireLockResult result = lockResult switch { @@ -315,7 +315,7 @@ protected override MutexTryAcquireLockResult AcquireLockCore(int timeoutMillisec finally { // The lock is released upon acquiring a recursive lock from the thread that already owns the lock - Interop.Sys.PThreadMutex_Release(_sharedData->LockAddress); + Interop.Sys.LowLevelCrossProcessMutex_Release(_sharedData); } } @@ -326,10 +326,12 @@ protected override void ReleaseLockCore() { Debug.Assert(IsLockOwnedByCurrentThread); Debug.Assert(_lockCount == 0); - _sharedData->LockOwnerProcessId = InvalidProcessId; - _sharedData->LockOwnerThreadId = InvalidThreadId; + Interop.Sys.LowLevelCrossProcessMutex_SetOwnerProcessAndThreadId( + _sharedData, + InvalidProcessId, + InvalidThreadId); - Interop.Sys.PThreadMutex_Release(_sharedData->LockAddress); + Interop.Sys.LowLevelCrossProcessMutex_Release(_sharedData); } public override void Close(bool releaseSharedData) @@ -339,38 +341,8 @@ public override void Close(bool releaseSharedData) if (releaseSharedData) { // Release the pthread mutex. - Interop.Sys.PThreadMutex_Destroy(_sharedData->LockAddress); - } - } - - [StructLayout(LayoutKind.Sequential)] - internal unsafe ref struct SharedData - { - public static nuint Size => (nuint)PThreadMutexSize + IsAbandonedOffset + sizeof(byte); - private static readonly int PThreadMutexSize = Interop.Sys.PThreadMutex_Size(); - - private const uint LockOwnerProcessIdOffset = 0x0; - private const uint LockOwnerThreadIdOffset = 0x4; - private const uint IsAbandonedOffset = 0x8; - - [UnscopedRef] - public ref uint LockOwnerProcessId => ref *(uint*)((byte*)Unsafe.AsPointer(ref this) + PThreadMutexSize + LockOwnerProcessIdOffset); - [UnscopedRef] - public ref uint LockOwnerThreadId => ref *(uint*)((byte*)Unsafe.AsPointer(ref this) + PThreadMutexSize + LockOwnerThreadIdOffset); - - public bool IsAbandoned - { - get - { - return *((byte*)Unsafe.AsPointer(ref this) + PThreadMutexSize + IsAbandonedOffset) != 0; - } - set - { - *((byte*)Unsafe.AsPointer(ref this) + PThreadMutexSize + IsAbandonedOffset) = value ? (byte)1 : (byte)0; - } + Interop.Sys.LowLevelCrossProcessMutex_Destroy(_sharedData); } - - public void* LockAddress => Unsafe.AsPointer(ref this); } } diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index 4e03ff98bcaee0..1ab65985f1863d 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -285,12 +285,16 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_GetGroupName) DllImportEntry(SystemNative_GetUInt64OSThreadId) DllImportEntry(SystemNative_TryGetUInt32OSThreadId) + DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_Size) + DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_Init) + DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_Acquire) + DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_Release) + DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_Destroy) + DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_GetOwnerProcessAndThreadId) + DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_SetOwnerProcessAndThreadId) + DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_IsAbandoned) + DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_SetAbandoned) DllImportEntry(SystemNative_Select) - DllImportEntry(SystemNative_PThreadMutex_Init) - DllImportEntry(SystemNative_PThreadMutex_Acquire) - DllImportEntry(SystemNative_PThreadMutex_Release) - DllImportEntry(SystemNative_PThreadMutex_Destroy) - DllImportEntry(SystemNative_PThreadMutex_Size) DllImportEntry(SystemNative_GetPageSize) }; diff --git a/src/native/libs/System.Native/pal_threading.c b/src/native/libs/System.Native/pal_threading.c index 3e84a16511af28..1ef898b0039f5c 100644 --- a/src/native/libs/System.Native/pal_threading.c +++ b/src/native/libs/System.Native/pal_threading.c @@ -299,28 +299,6 @@ uint32_t SystemNative_TryGetUInt32OSThreadId(void) return result == 0 ? (uint32_t)-1 : result; } -int32_t SystemNative_PThreadMutex_Init(void* mutex) -{ - pthread_mutexattr_t mutexAttributes; - int error = pthread_mutexattr_init(&mutexAttributes); - if (error != 0) - { - return ConvertErrorPlatformToPal(error); - } - - error = pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_RECURSIVE); - assert(error == 0); - - error = pthread_mutexattr_setrobust(&mutexAttributes, PTHREAD_MUTEX_ROBUST); - assert(error == 0); - - error = pthread_mutexattr_setpshared(&mutexAttributes, PTHREAD_PROCESS_SHARED); - assert(error == 0); - - error = pthread_mutex_init((pthread_mutex_t*)mutex, &mutexAttributes); - return ConvertErrorPlatformToPal(error); -} - static int32_t AcquirePThreadMutexWithTimeout(pthread_mutex_t* mutex, int32_t timeoutMilliseconds) { assert(mutex != NULL); @@ -345,7 +323,7 @@ static int32_t AcquirePThreadMutexWithTimeout(pthread_mutex_t* mutex, int32_t ti error = pthread_mutex_reltimedlock_np(mutex, &timeoutTimeSpec); #else #if HAVE_PTHREAD_CONDATTR_SETCLOCK && HAVE_CLOCK_MONOTONIC - int error = clock_gettime(CLOCK_MONOTONIC, &timeoutTimeSpec); + error = clock_gettime(CLOCK_MONOTONIC, &timeoutTimeSpec); assert(error == 0); #else struct timeval tv; @@ -363,34 +341,99 @@ static int32_t AcquirePThreadMutexWithTimeout(pthread_mutex_t* mutex, int32_t ti return pthread_mutex_timedlock((pthread_mutex_t*)mutex, &timeoutTimeSpec); #endif } +struct LowLevelCrossProcessMutex +{ + pthread_mutex_t Mutex; + uint32_t OwnerProcessId; + uint32_t OwnerThreadId; + uint8_t IsAbandoned; +}; -int32_t SystemNative_PThreadMutex_Acquire(void* mutex, int32_t timeoutMilliseconds) +#define INVALID_PROCESS_ID (uint32_t)(-1) +#define INVALID_THREAD_ID (uint32_t)(-1) + +int32_t SystemNative_LowLevelCrossPlatformMutex_Size(void) { - int32_t result = AcquirePThreadMutexWithTimeout((pthread_mutex_t*)mutex, timeoutMilliseconds); + return (int32_t)sizeof(LowLevelCrossProcessMutex); +} + +int32_t SystemNative_LowLevelCrossPlatformMutex_Init(LowLevelCrossProcessMutex* mutex) +{ + mutex->OwnerProcessId = INVALID_PROCESS_ID; + mutex->OwnerThreadId = INVALID_THREAD_ID; + mutex->IsAbandoned = 0; + pthread_mutexattr_t mutexAttributes; + int error = pthread_mutexattr_init(&mutexAttributes); + if (error != 0) + { + return ConvertErrorPlatformToPal(error); + } + + error = pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_RECURSIVE); + assert(error == 0); + + error = pthread_mutexattr_setrobust(&mutexAttributes, PTHREAD_MUTEX_ROBUST); + assert(error == 0); + + error = pthread_mutexattr_setpshared(&mutexAttributes, PTHREAD_PROCESS_SHARED); + assert(error == 0); + + error = pthread_mutex_init(&mutex->Mutex, &mutexAttributes); + return ConvertErrorPlatformToPal(error); +} + +int32_t SystemNative_LowLevelCrossPlatformMutex_Acquire(LowLevelCrossProcessMutex* mutex, int32_t timeoutMilliseconds) +{ + int32_t result = AcquirePThreadMutexWithTimeout(&mutex->Mutex, timeoutMilliseconds); if (result == EOWNERDEAD) { // The mutex was abandoned by the previous owner. // Make it consistent so that it can be used again. - int setConsistentResult = pthread_mutex_consistent((pthread_mutex_t*)mutex); + int setConsistentResult = pthread_mutex_consistent(&mutex->Mutex); } return ConvertErrorPlatformToPal(result); } -int32_t SystemNative_PThreadMutex_Release(void* mutex) +int32_t SystemNative_LowLevelCrossPlatformMutex_Release(LowLevelCrossProcessMutex* mutex) { assert(mutex != NULL); - return ConvertErrorPlatformToPal(pthread_mutex_unlock((pthread_mutex_t*)mutex)); + return ConvertErrorPlatformToPal(pthread_mutex_unlock(&mutex->Mutex)); } -int32_t SystemNative_PThreadMutex_Destroy(void* mutex) +int32_t SystemNative_LowLevelCrossPlatformMutex_Destroy(LowLevelCrossProcessMutex* mutex) { assert(mutex != NULL); - return ConvertErrorPlatformToPal(pthread_mutex_destroy((pthread_mutex_t*)mutex)); + return ConvertErrorPlatformToPal(pthread_mutex_destroy(&mutex->Mutex)); } -int32_t SystemNative_PThreadMutex_Size(void) +void SystemNative_LowLevelCrossPlatformMutex_GetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t* pOwnerProcessId, uint32_t* pOwnerThreadId) { - return (int32_t)sizeof(pthread_mutex_t); + assert(mutex != NULL); + assert(pOwnerProcessId != NULL); + assert(pOwnerThreadId != NULL); + + *pOwnerProcessId = mutex->OwnerProcessId; + *pOwnerThreadId = mutex->OwnerThreadId; +} + +void SystemNative_LowLevelCrossPlatformMutex_SetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t ownerProcessId, uint32_t ownerThreadId) +{ + assert(mutex != NULL); + + mutex->OwnerProcessId = ownerProcessId; + mutex->OwnerThreadId = ownerThreadId; +} + +uint8_t SystemNative_LowLevelCrossPlatformMutex_IsAbandoned(LowLevelCrossProcessMutex* mutex) +{ + assert(mutex != NULL); + return mutex->IsAbandoned; +} + +void SystemNative_LowLevelCrossPlatformMutex_SetAbandoned(LowLevelCrossProcessMutex* mutex, uint8_t isAbandoned) +{ + assert(mutex != NULL); + mutex->IsAbandoned = isAbandoned; } diff --git a/src/native/libs/System.Native/pal_threading.h b/src/native/libs/System.Native/pal_threading.h index c3bf93d250c8b0..f027a40144d833 100644 --- a/src/native/libs/System.Native/pal_threading.h +++ b/src/native/libs/System.Native/pal_threading.h @@ -7,6 +7,7 @@ #include "pal_types.h" typedef struct LowLevelMonitor LowLevelMonitor; +typedef struct LowLevelCrossProcessMutex LowLevelCrossProcessMutex; PALEXPORT LowLevelMonitor *SystemNative_LowLevelMonitor_Create(void); @@ -42,3 +43,13 @@ PALEXPORT int32_t SystemNative_PThreadMutex_Release(void* mutex); PALEXPORT int32_t SystemNative_PThreadMutex_Destroy(void* mutex); PALEXPORT int32_t SystemNative_PThreadMutex_Size(void); + +PALEXPORT int32_t SystemNative_LowLevelCrossPlatformMutex_Size(void); +PALEXPORT int32_t SystemNative_LowLevelCrossPlatformMutex_Init(LowLevelCrossProcessMutex* mutex); +PALEXPORT int32_t SystemNative_LowLevelCrossPlatformMutex_Acquire(LowLevelCrossProcessMutex* mutex, int32_t timeoutMilliseconds); +PALEXPORT int32_t SystemNative_LowLevelCrossPlatformMutex_Release(LowLevelCrossProcessMutex* mutex); +PALEXPORT int32_t SystemNative_LowLevelCrossPlatformMutex_Destroy(LowLevelCrossProcessMutex* mutex); +PALEXPORT void SystemNative_LowLevelCrossPlatformMutex_GetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t* pOwnerProcessId, uint32_t* pOwnerThreadId); +PALEXPORT void SystemNative_LowLevelCrossPlatformMutex_SetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t ownerProcessId, uint32_t ownerThreadId); +PALEXPORT uint8_t SystemNative_LowLevelCrossPlatformMutex_IsAbandoned(LowLevelCrossProcessMutex* mutex); +PALEXPORT void SystemNative_LowLevelCrossPlatformMutex_SetAbandoned(LowLevelCrossProcessMutex* mutex, uint8_t isAbandoned); From 9787f15ef6aee637603a976fcb0014857ed27406 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 14 Jul 2025 23:05:27 +0000 Subject: [PATCH 15/47] Do a little cleanup --- .../src/System/Threading/NamedMutex.Unix.cs | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 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 f38b0972b4b53c..6c1f616579ba35 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 @@ -31,7 +31,7 @@ internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader< // independently by the processes involved. See https://github.com/dotnet/runtime/issues/10519. private static bool UsePThreadMutexes => !OperatingSystem.IsApplePlatform() && !OperatingSystem.IsFreeBSD(); - private SharedMemoryProcessDataHeader _processDataHeader = header; + private readonly SharedMemoryProcessDataHeader _processDataHeader = header; protected nuint _lockCount; private Thread? _lockOwnerThread; private NamedMutexProcessDataBase? _nextInThreadOwnedNamedMutexList; @@ -43,16 +43,6 @@ internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader< protected abstract void SetLockOwnerToCurrentThread(); - public void IncrementRefCount() - { - _processDataHeader.IncrementRefCount(); - } - - public void DecrementRefCount() - { - _processDataHeader.DecrementRefCount(); - } - public MutexTryAcquireLockResult TryAcquireLock(WaitSubsystem.ThreadWaitInfo waitInfo, int timeoutMilliseconds, ref WaitSubsystem.LockHolder holder) { SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); @@ -74,6 +64,8 @@ public MutexTryAcquireLockResult TryAcquireLock(WaitSubsystem.ThreadWaitInfo wai SetLockOwnerToCurrentThread(); _lockCount = 1; _lockOwnerThread = waitInfo.Thread; + // Add the ref count for the thread's wait info. + _processDataHeader.IncrementRefCount(); AddOwnedNamedMutex(waitInfo.Thread, this); if (IsAbandoned) @@ -88,8 +80,6 @@ public MutexTryAcquireLockResult TryAcquireLock(WaitSubsystem.ThreadWaitInfo wai private static void AddOwnedNamedMutex(Thread thread, NamedMutexProcessDataBase namedMutexProcessDataBase) { SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); - // This adds a reference to the named mutex in the thread's wait info. - namedMutexProcessDataBase.IncrementRefCount(); namedMutexProcessDataBase._nextInThreadOwnedNamedMutexList = thread.WaitInfo.LockedNamedMutexesHead; thread.WaitInfo.LockedNamedMutexesHead = namedMutexProcessDataBase; } @@ -113,8 +103,8 @@ public void ReleaseLock() RemoveOwnedNamedMutex(Thread.CurrentThread, this); _lockOwnerThread = null; ReleaseLockCore(); - // This removes the reference to the named mutex in the thread's wait info. - DecrementRefCount(); + // Remove the refcount from the thread's wait info. + _processDataHeader.DecrementRefCount(); } finally { @@ -157,8 +147,8 @@ public void Abandon() RemoveOwnedNamedMutex(Thread.CurrentThread, this); _lockOwnerThread = null; - // This removes the reference to the named mutex in the thread's wait info. - DecrementRefCount(); + // Remove the refcount from the thread's wait info. + _processDataHeader.DecrementRefCount(); } protected abstract MutexTryAcquireLockResult AcquireLockCore(int timeoutMilliseconds); From bce3af6a2ac45c98bbcbf065eaac557f6f6e4b66 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 14 Jul 2025 23:07:05 +0000 Subject: [PATCH 16/47] Remove unused member --- .../src/System/Threading/Thread.NativeAot.Unix.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Unix.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Unix.cs index 1f2b9a3003c7ef..de77bc91b088e3 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Unix.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.Unix.cs @@ -18,8 +18,6 @@ public sealed partial class Thread internal WaitSubsystem.ThreadWaitInfo WaitInfo => _waitInfo; - internal NamedMutexProcessDataBase? _ownedSharedNamedMutexes; // shared named mutexes owned by this thread - private void PlatformSpecificInitialize() { _waitInfo = new WaitSubsystem.ThreadWaitInfo(this); From 36bd5c0ed69bb4315e256407c8d7673c64cebd19 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 14 Jul 2025 23:08:00 +0000 Subject: [PATCH 17/47] Cleanup projitems --- .../src/System.Private.CoreLib.Shared.projitems | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 7574d388424db9..608899f18d5326 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2488,9 +2488,6 @@ Common\Interop\Unix\System.Native\Interop.PathConf.cs - - Common\Interop\Unix\System.Native\Interop.LowLevelCrossProcessMutex.cs - Common\Interop\Unix\System.Native\Interop.PosixFAdvise.cs @@ -2852,9 +2849,12 @@ - + + + Common\Interop\Unix\System.Native\Interop.LowLevelCrossProcessMutex.cs + From 182c57a1ce485c105f3e3f05396c481ec93b0c1c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 14 Jul 2025 23:12:52 +0000 Subject: [PATCH 18/47] Cleanup libraries pal a little --- src/native/libs/System.Native/entrypoints.c | 2 +- src/native/libs/System.Native/pal_threading.c | 3 +- .../libs/System.Native/pal_threading_wasi.c | 49 ++++++++++++++----- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index 1ab65985f1863d..9d49530ff59449 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -221,6 +221,7 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_SchedSetAffinity) DllImportEntry(SystemNative_SchedGetAffinity) DllImportEntry(SystemNative_GetProcessPath) + DllImportEntry(SystemNative_GetPageSize) DllImportEntry(SystemNative_GetNonCryptographicallySecureRandomBytes) DllImportEntry(SystemNative_GetCryptographicallySecureRandomBytes) DllImportEntry(SystemNative_GetUnixRelease) @@ -295,7 +296,6 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_IsAbandoned) DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_SetAbandoned) DllImportEntry(SystemNative_Select) - DllImportEntry(SystemNative_GetPageSize) }; EXTERN_C const void* SystemResolveDllImport(const char* name); diff --git a/src/native/libs/System.Native/pal_threading.c b/src/native/libs/System.Native/pal_threading.c index 1ef898b0039f5c..86ce882d495b32 100644 --- a/src/native/libs/System.Native/pal_threading.c +++ b/src/native/libs/System.Native/pal_threading.c @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "pal_config.h" -#include +#include "pal_errno.h" #include "pal_threading.h" #include @@ -341,6 +341,7 @@ static int32_t AcquirePThreadMutexWithTimeout(pthread_mutex_t* mutex, int32_t ti return pthread_mutex_timedlock((pthread_mutex_t*)mutex, &timeoutTimeSpec); #endif } + struct LowLevelCrossProcessMutex { pthread_mutex_t Mutex; diff --git a/src/native/libs/System.Native/pal_threading_wasi.c b/src/native/libs/System.Native/pal_threading_wasi.c index ad24ae64fcb21c..0b6ff29288c0a7 100644 --- a/src/native/libs/System.Native/pal_threading_wasi.c +++ b/src/native/libs/System.Native/pal_threading_wasi.c @@ -3,7 +3,7 @@ #include "pal_config.h" #include "pal_threading.h" -#include +#include "pal_errno.h" #include #include @@ -96,30 +96,57 @@ uint32_t SystemNative_TryGetUInt32OSThreadId(void) return (uint32_t)-1; } -int32_t SystemNative_PThreadMutex_Init(void* mutex) +struct LowLevelCrossProcessMutex { - return 0; + bool Dummy; +}; + +int32_t SystemNative_LowLevelCrossPlatformMutex_Size(void) +{ + return (int32_t)sizeof(LowLevelCrossProcessMutex); +} + +int32_t SystemNative_LowLevelCrossPlatformMutex_Init(LowLevelCrossProcessMutex* mutex) +{ + assert(false); + return Error_EINVAL; } -int32_t SystemNative_PThreadMutex_Acquire(void* mutex, int32_t timeoutMilliseconds) +int32_t SystemNative_LowLevelCrossPlatformMutex_Acquire(LowLevelCrossProcessMutex* mutex, int32_t timeoutMilliseconds) { - assert(mutex != NULL); + assert(false); return Error_EINVAL; } -int32_t SystemNative_PThreadMutex_Release(void* mutex) +int32_t SystemNative_LowLevelCrossPlatformMutex_Release(LowLevelCrossProcessMutex* mutex) { - assert(mutex != NULL); + assert(false); return Error_EINVAL; } -int32_t SystemNative_PThreadMutex_Destroy(void* mutex) +int32_t SystemNative_LowLevelCrossPlatformMutex_Destroy(LowLevelCrossProcessMutex* mutex) { - assert(mutex != NULL); + assert(false); return Error_EINVAL; } -int32_t SystemNative_PThreadMutex_Size(void) +void SystemNative_LowLevelCrossPlatformMutex_GetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t* pOwnerProcessId, uint32_t* pOwnerThreadId) { - return 0; + assert(false); +} + +void SystemNative_LowLevelCrossPlatformMutex_SetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t ownerProcessId, uint32_t ownerThreadId) +{ + assert(false); +} + +uint8_t SystemNative_LowLevelCrossPlatformMutex_IsAbandoned(LowLevelCrossProcessMutex* mutex) +{ + assert(false); + return Error_EINVAL; +} + +void SystemNative_LowLevelCrossPlatformMutex_SetAbandoned(LowLevelCrossProcessMutex* mutex, uint8_t isAbandoned) +{ + assert(false); } From 61aabd424543ce8cb49e13f76cba261a8f818bbe Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 15 Jul 2025 15:48:29 +0000 Subject: [PATCH 19/47] Rename and split out the "unsupported" impl --- src/native/libs/System.Native/CMakeLists.txt | 9 + src/native/libs/System.Native/entrypoints.c | 19 ++- src/native/libs/System.Native/pal_mutex.c | 160 ++++++++++++++++++ src/native/libs/System.Native/pal_mutex.h | 27 +++ .../System.Native/pal_mutex_unsupported.c | 68 ++++++++ src/native/libs/System.Native/pal_threading.c | 97 ----------- src/native/libs/System.Native/pal_threading.h | 20 --- .../libs/System.Native/pal_threading_wasi.c | 55 ------ 8 files changed, 274 insertions(+), 181 deletions(-) create mode 100644 src/native/libs/System.Native/pal_mutex.c create mode 100644 src/native/libs/System.Native/pal_mutex.h create mode 100644 src/native/libs/System.Native/pal_mutex_unsupported.c diff --git a/src/native/libs/System.Native/CMakeLists.txt b/src/native/libs/System.Native/CMakeLists.txt index 1407d50e04d2c3..0dd23e452be27d 100644 --- a/src/native/libs/System.Native/CMakeLists.txt +++ b/src/native/libs/System.Native/CMakeLists.txt @@ -41,6 +41,15 @@ else() ) endif() +if (CLR_CMAKE_TARGET_APPLE OR CLR_CMAKE_TARGET_ANDROID OR CLR_CMAKE_TARGET_BROWSER OR CLR_CMAKE_TARGET_WASI) + list (APPEND NATIVE_SOURCES + pal_mutex_unsupported.c) +else() + list (APPEND NATIVE_SOURCES + pal_mutex.c + ) +endif() + if (CLR_CMAKE_TARGET_APPLE) list (APPEND NATIVE_SOURCES_OBJC_NO_ARC pal_autoreleasepool.m diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index 9d49530ff59449..4edcd0bb2e8f4d 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -16,6 +16,7 @@ #include "pal_log.h" #include "pal_memory.h" #include "pal_mount.h" +#include "pal_mutex.h" #include "pal_networkchange.h" #include "pal_networking.h" #include "pal_networkstatistics.h" @@ -286,15 +287,15 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_GetGroupName) DllImportEntry(SystemNative_GetUInt64OSThreadId) DllImportEntry(SystemNative_TryGetUInt32OSThreadId) - DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_Size) - DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_Init) - DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_Acquire) - DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_Release) - DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_Destroy) - DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_GetOwnerProcessAndThreadId) - DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_SetOwnerProcessAndThreadId) - DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_IsAbandoned) - DllImportEntry(SystemNative_LowLevelCrossPlatformMutex_SetAbandoned) + DllImportEntry(SystemNative_LowLevelCrossProcessMutex_Size) + DllImportEntry(SystemNative_LowLevelCrossProcessMutex_Init) + DllImportEntry(SystemNative_LowLevelCrossProcessMutex_Acquire) + DllImportEntry(SystemNative_LowLevelCrossProcessMutex_Release) + DllImportEntry(SystemNative_LowLevelCrossProcessMutex_Destroy) + DllImportEntry(SystemNative_LowLevelCrossProcessMutex_GetOwnerProcessAndThreadId) + DllImportEntry(SystemNative_LowLevelCrossProcessMutex_SetOwnerProcessAndThreadId) + DllImportEntry(SystemNative_LowLevelCrossProcessMutex_IsAbandoned) + DllImportEntry(SystemNative_LowLevelCrossProcessMutex_SetAbandoned) DllImportEntry(SystemNative_Select) }; diff --git a/src/native/libs/System.Native/pal_mutex.c b/src/native/libs/System.Native/pal_mutex.c new file mode 100644 index 00000000000000..52edca9f772aad --- /dev/null +++ b/src/native/libs/System.Native/pal_mutex.c @@ -0,0 +1,160 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "pal_config.h" +#include "pal_errno.h" +#include "pal_mutex.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if HAVE_SCHED_GETCPU +#include +#endif +#include + +static int32_t AcquirePThreadMutexWithTimeout(pthread_mutex_t* mutex, int32_t timeoutMilliseconds) +{ + assert(mutex != NULL); + + if (timeoutMilliseconds == -1) + { + return pthread_mutex_lock(mutex); + } + else if (timeoutMilliseconds == 0) + { + return pthread_mutex_trylock(mutex); + } + + // Calculate the time at which a timeout should occur, and wait. Older versions of OSX don't support clock_gettime with + // CLOCK_MONOTONIC, so we instead compute the relative timeout duration, and use a relative variant of the timed wait. + struct timespec timeoutTimeSpec; + int32_t error = 0; +#if HAVE_CLOCK_GETTIME_NSEC_NP + timeoutTimeSpec.tv_sec = timeoutMilliseconds / 1000; + timeoutTimeSpec.tv_nsec = (timeoutMilliseconds % 1000) * 1000 * 1000; + + error = pthread_mutex_reltimedlock_np(mutex, &timeoutTimeSpec); +#else +#if HAVE_PTHREAD_CONDATTR_SETCLOCK && HAVE_CLOCK_MONOTONIC + error = clock_gettime(CLOCK_MONOTONIC, &timeoutTimeSpec); + assert(error == 0); +#else + struct timeval tv; + + error = gettimeofday(&tv, NULL); + assert(error == 0); + + timeoutTimeSpec.tv_sec = tv.tv_sec; + timeoutTimeSpec.tv_nsec = tv.tv_usec * 1000; +#endif + uint64_t nanoseconds = (uint64_t)timeoutMilliseconds * 1000 * 1000 + (uint64_t)timeoutTimeSpec.tv_nsec; + timeoutTimeSpec.tv_sec += nanoseconds / (1000 * 1000 * 1000); + timeoutTimeSpec.tv_nsec = nanoseconds % (1000 * 1000 * 1000); + + return pthread_mutex_timedlock((pthread_mutex_t*)mutex, &timeoutTimeSpec); +#endif +} + +struct LowLevelCrossProcessMutex +{ + pthread_mutex_t Mutex; + uint32_t OwnerProcessId; + uint32_t OwnerThreadId; + uint8_t IsAbandoned; +}; + +#define INVALID_PROCESS_ID (uint32_t)(-1) +#define INVALID_THREAD_ID (uint32_t)(-1) + +int32_t SystemNative_LowLevelCrossProcessMutex_Size(void) +{ + return (int32_t)sizeof(LowLevelCrossProcessMutex); +} + +int32_t SystemNative_LowLevelCrossProcessMutex_Init(LowLevelCrossProcessMutex* mutex) +{ + mutex->OwnerProcessId = INVALID_PROCESS_ID; + mutex->OwnerThreadId = INVALID_THREAD_ID; + mutex->IsAbandoned = 0; + pthread_mutexattr_t mutexAttributes; + int error = pthread_mutexattr_init(&mutexAttributes); + if (error != 0) + { + return ConvertErrorPlatformToPal(error); + } + + error = pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_RECURSIVE); + assert(error == 0); + + error = pthread_mutexattr_setrobust(&mutexAttributes, PTHREAD_MUTEX_ROBUST); + assert(error == 0); + + error = pthread_mutexattr_setpshared(&mutexAttributes, PTHREAD_PROCESS_SHARED); + assert(error == 0); + + error = pthread_mutex_init(&mutex->Mutex, &mutexAttributes); + return ConvertErrorPlatformToPal(error); +} + +int32_t SystemNative_LowLevelCrossProcessMutex_Acquire(LowLevelCrossProcessMutex* mutex, int32_t timeoutMilliseconds) +{ + int32_t result = AcquirePThreadMutexWithTimeout(&mutex->Mutex, timeoutMilliseconds); + + if (result == EOWNERDEAD) + { + // The mutex was abandoned by the previous owner. + // Make it consistent so that it can be used again. + int setConsistentResult = pthread_mutex_consistent(&mutex->Mutex); + } + + return ConvertErrorPlatformToPal(result); +} + +int32_t SystemNative_LowLevelCrossProcessMutex_Release(LowLevelCrossProcessMutex* mutex) +{ + assert(mutex != NULL); + return ConvertErrorPlatformToPal(pthread_mutex_unlock(&mutex->Mutex)); +} + +int32_t SystemNative_LowLevelCrossProcessMutex_Destroy(LowLevelCrossProcessMutex* mutex) +{ + assert(mutex != NULL); + return ConvertErrorPlatformToPal(pthread_mutex_destroy(&mutex->Mutex)); +} + +void SystemNative_LowLevelCrossProcessMutex_GetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t* pOwnerProcessId, uint32_t* pOwnerThreadId) +{ + assert(mutex != NULL); + assert(pOwnerProcessId != NULL); + assert(pOwnerThreadId != NULL); + + *pOwnerProcessId = mutex->OwnerProcessId; + *pOwnerThreadId = mutex->OwnerThreadId; +} + +void SystemNative_LowLevelCrossProcessMutex_SetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t ownerProcessId, uint32_t ownerThreadId) +{ + assert(mutex != NULL); + + mutex->OwnerProcessId = ownerProcessId; + mutex->OwnerThreadId = ownerThreadId; +} + +uint8_t SystemNative_LowLevelCrossProcessMutex_IsAbandoned(LowLevelCrossProcessMutex* mutex) +{ + assert(mutex != NULL); + return mutex->IsAbandoned; +} + +void SystemNative_LowLevelCrossProcessMutex_SetAbandoned(LowLevelCrossProcessMutex* mutex, uint8_t isAbandoned) +{ + assert(mutex != NULL); + mutex->IsAbandoned = isAbandoned; +} diff --git a/src/native/libs/System.Native/pal_mutex.h b/src/native/libs/System.Native/pal_mutex.h new file mode 100644 index 00000000000000..d48de4bda399e0 --- /dev/null +++ b/src/native/libs/System.Native/pal_mutex.h @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#include "pal_compiler.h" +#include "pal_types.h" + +typedef struct LowLevelCrossProcessMutex LowLevelCrossProcessMutex; + +PALEXPORT int32_t SystemNative_LowLevelCrossProcessMutex_Size(void); + +PALEXPORT int32_t SystemNative_LowLevelCrossProcessMutex_Init(LowLevelCrossProcessMutex* mutex); + +PALEXPORT int32_t SystemNative_LowLevelCrossProcessMutex_Acquire(LowLevelCrossProcessMutex* mutex, int32_t timeoutMilliseconds); + +PALEXPORT int32_t SystemNative_LowLevelCrossProcessMutex_Release(LowLevelCrossProcessMutex* mutex); + +PALEXPORT int32_t SystemNative_LowLevelCrossProcessMutex_Destroy(LowLevelCrossProcessMutex* mutex); + +PALEXPORT void SystemNative_LowLevelCrossProcessMutex_GetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t* pOwnerProcessId, uint32_t* pOwnerThreadId); + +PALEXPORT void SystemNative_LowLevelCrossProcessMutex_SetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t ownerProcessId, uint32_t ownerThreadId); + +PALEXPORT uint8_t SystemNative_LowLevelCrossProcessMutex_IsAbandoned(LowLevelCrossProcessMutex* mutex); + +PALEXPORT void SystemNative_LowLevelCrossProcessMutex_SetAbandoned(LowLevelCrossProcessMutex* mutex, uint8_t isAbandoned); diff --git a/src/native/libs/System.Native/pal_mutex_unsupported.c b/src/native/libs/System.Native/pal_mutex_unsupported.c new file mode 100644 index 00000000000000..57bcafa8ba7f7b --- /dev/null +++ b/src/native/libs/System.Native/pal_mutex_unsupported.c @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "pal_config.h" +#include "pal_mutex.h" +#include "pal_errno.h" + +#include +#include +#include +#include +#include +#include + +struct LowLevelCrossProcessMutex +{ + bool Dummy; +}; + +int32_t SystemNative_LowLevelCrossProcessMutex_Size(void) +{ + return (int32_t)sizeof(LowLevelCrossProcessMutex); +} + +int32_t SystemNative_LowLevelCrossProcessMutex_Init(LowLevelCrossProcessMutex* mutex) +{ + assert(false); + return Error_EINVAL; +} + +int32_t SystemNative_LowLevelCrossProcessMutex_Acquire(LowLevelCrossProcessMutex* mutex, int32_t timeoutMilliseconds) +{ + assert(false); + return Error_EINVAL; +} + +int32_t SystemNative_LowLevelCrossProcessMutex_Release(LowLevelCrossProcessMutex* mutex) +{ + assert(false); + return Error_EINVAL; +} + +int32_t SystemNative_LowLevelCrossProcessMutex_Destroy(LowLevelCrossProcessMutex* mutex) +{ + assert(false); + return Error_EINVAL; +} + +void SystemNative_LowLevelCrossProcessMutex_GetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t* pOwnerProcessId, uint32_t* pOwnerThreadId) +{ + assert(false); +} + +void SystemNative_LowLevelCrossProcessMutex_SetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t ownerProcessId, uint32_t ownerThreadId) +{ + assert(false); +} + +uint8_t SystemNative_LowLevelCrossProcessMutex_IsAbandoned(LowLevelCrossProcessMutex* mutex) +{ + assert(false); + return Error_EINVAL; +} + +void SystemNative_LowLevelCrossProcessMutex_SetAbandoned(LowLevelCrossProcessMutex* mutex, uint8_t isAbandoned) +{ + assert(false); +} diff --git a/src/native/libs/System.Native/pal_threading.c b/src/native/libs/System.Native/pal_threading.c index 86ce882d495b32..393a52e5b7652a 100644 --- a/src/native/libs/System.Native/pal_threading.c +++ b/src/native/libs/System.Native/pal_threading.c @@ -341,100 +341,3 @@ static int32_t AcquirePThreadMutexWithTimeout(pthread_mutex_t* mutex, int32_t ti return pthread_mutex_timedlock((pthread_mutex_t*)mutex, &timeoutTimeSpec); #endif } - -struct LowLevelCrossProcessMutex -{ - pthread_mutex_t Mutex; - uint32_t OwnerProcessId; - uint32_t OwnerThreadId; - uint8_t IsAbandoned; -}; - -#define INVALID_PROCESS_ID (uint32_t)(-1) -#define INVALID_THREAD_ID (uint32_t)(-1) - -int32_t SystemNative_LowLevelCrossPlatformMutex_Size(void) -{ - return (int32_t)sizeof(LowLevelCrossProcessMutex); -} - -int32_t SystemNative_LowLevelCrossPlatformMutex_Init(LowLevelCrossProcessMutex* mutex) -{ - mutex->OwnerProcessId = INVALID_PROCESS_ID; - mutex->OwnerThreadId = INVALID_THREAD_ID; - mutex->IsAbandoned = 0; - pthread_mutexattr_t mutexAttributes; - int error = pthread_mutexattr_init(&mutexAttributes); - if (error != 0) - { - return ConvertErrorPlatformToPal(error); - } - - error = pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_RECURSIVE); - assert(error == 0); - - error = pthread_mutexattr_setrobust(&mutexAttributes, PTHREAD_MUTEX_ROBUST); - assert(error == 0); - - error = pthread_mutexattr_setpshared(&mutexAttributes, PTHREAD_PROCESS_SHARED); - assert(error == 0); - - error = pthread_mutex_init(&mutex->Mutex, &mutexAttributes); - return ConvertErrorPlatformToPal(error); -} - -int32_t SystemNative_LowLevelCrossPlatformMutex_Acquire(LowLevelCrossProcessMutex* mutex, int32_t timeoutMilliseconds) -{ - int32_t result = AcquirePThreadMutexWithTimeout(&mutex->Mutex, timeoutMilliseconds); - - if (result == EOWNERDEAD) - { - // The mutex was abandoned by the previous owner. - // Make it consistent so that it can be used again. - int setConsistentResult = pthread_mutex_consistent(&mutex->Mutex); - } - - return ConvertErrorPlatformToPal(result); -} - -int32_t SystemNative_LowLevelCrossPlatformMutex_Release(LowLevelCrossProcessMutex* mutex) -{ - assert(mutex != NULL); - return ConvertErrorPlatformToPal(pthread_mutex_unlock(&mutex->Mutex)); -} - -int32_t SystemNative_LowLevelCrossPlatformMutex_Destroy(LowLevelCrossProcessMutex* mutex) -{ - assert(mutex != NULL); - return ConvertErrorPlatformToPal(pthread_mutex_destroy(&mutex->Mutex)); -} - -void SystemNative_LowLevelCrossPlatformMutex_GetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t* pOwnerProcessId, uint32_t* pOwnerThreadId) -{ - assert(mutex != NULL); - assert(pOwnerProcessId != NULL); - assert(pOwnerThreadId != NULL); - - *pOwnerProcessId = mutex->OwnerProcessId; - *pOwnerThreadId = mutex->OwnerThreadId; -} - -void SystemNative_LowLevelCrossPlatformMutex_SetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t ownerProcessId, uint32_t ownerThreadId) -{ - assert(mutex != NULL); - - mutex->OwnerProcessId = ownerProcessId; - mutex->OwnerThreadId = ownerThreadId; -} - -uint8_t SystemNative_LowLevelCrossPlatformMutex_IsAbandoned(LowLevelCrossProcessMutex* mutex) -{ - assert(mutex != NULL); - return mutex->IsAbandoned; -} - -void SystemNative_LowLevelCrossPlatformMutex_SetAbandoned(LowLevelCrossProcessMutex* mutex, uint8_t isAbandoned) -{ - assert(mutex != NULL); - mutex->IsAbandoned = isAbandoned; -} diff --git a/src/native/libs/System.Native/pal_threading.h b/src/native/libs/System.Native/pal_threading.h index f027a40144d833..74cba21eb25a29 100644 --- a/src/native/libs/System.Native/pal_threading.h +++ b/src/native/libs/System.Native/pal_threading.h @@ -33,23 +33,3 @@ PALEXPORT __attribute__((noreturn)) void SystemNative_Abort(void); PALEXPORT uint64_t SystemNative_GetUInt64OSThreadId(void); PALEXPORT uint32_t SystemNative_TryGetUInt32OSThreadId(void); - -PALEXPORT int32_t SystemNative_PThreadMutex_Init(void* mutex); - -PALEXPORT int32_t SystemNative_PThreadMutex_Acquire(void* mutex, int32_t timeoutMilliseconds); - -PALEXPORT int32_t SystemNative_PThreadMutex_Release(void* mutex); - -PALEXPORT int32_t SystemNative_PThreadMutex_Destroy(void* mutex); - -PALEXPORT int32_t SystemNative_PThreadMutex_Size(void); - -PALEXPORT int32_t SystemNative_LowLevelCrossPlatformMutex_Size(void); -PALEXPORT int32_t SystemNative_LowLevelCrossPlatformMutex_Init(LowLevelCrossProcessMutex* mutex); -PALEXPORT int32_t SystemNative_LowLevelCrossPlatformMutex_Acquire(LowLevelCrossProcessMutex* mutex, int32_t timeoutMilliseconds); -PALEXPORT int32_t SystemNative_LowLevelCrossPlatformMutex_Release(LowLevelCrossProcessMutex* mutex); -PALEXPORT int32_t SystemNative_LowLevelCrossPlatformMutex_Destroy(LowLevelCrossProcessMutex* mutex); -PALEXPORT void SystemNative_LowLevelCrossPlatformMutex_GetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t* pOwnerProcessId, uint32_t* pOwnerThreadId); -PALEXPORT void SystemNative_LowLevelCrossPlatformMutex_SetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t ownerProcessId, uint32_t ownerThreadId); -PALEXPORT uint8_t SystemNative_LowLevelCrossPlatformMutex_IsAbandoned(LowLevelCrossProcessMutex* mutex); -PALEXPORT void SystemNative_LowLevelCrossPlatformMutex_SetAbandoned(LowLevelCrossProcessMutex* mutex, uint8_t isAbandoned); diff --git a/src/native/libs/System.Native/pal_threading_wasi.c b/src/native/libs/System.Native/pal_threading_wasi.c index 0b6ff29288c0a7..ce703a450c016d 100644 --- a/src/native/libs/System.Native/pal_threading_wasi.c +++ b/src/native/libs/System.Native/pal_threading_wasi.c @@ -95,58 +95,3 @@ uint32_t SystemNative_TryGetUInt32OSThreadId(void) { return (uint32_t)-1; } - -struct LowLevelCrossProcessMutex -{ - bool Dummy; -}; - -int32_t SystemNative_LowLevelCrossPlatformMutex_Size(void) -{ - return (int32_t)sizeof(LowLevelCrossProcessMutex); -} - -int32_t SystemNative_LowLevelCrossPlatformMutex_Init(LowLevelCrossProcessMutex* mutex) -{ - assert(false); - return Error_EINVAL; -} - -int32_t SystemNative_LowLevelCrossPlatformMutex_Acquire(LowLevelCrossProcessMutex* mutex, int32_t timeoutMilliseconds) -{ - assert(false); - return Error_EINVAL; -} - -int32_t SystemNative_LowLevelCrossPlatformMutex_Release(LowLevelCrossProcessMutex* mutex) -{ - assert(false); - return Error_EINVAL; -} - -int32_t SystemNative_LowLevelCrossPlatformMutex_Destroy(LowLevelCrossProcessMutex* mutex) -{ - assert(false); - return Error_EINVAL; -} - -void SystemNative_LowLevelCrossPlatformMutex_GetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t* pOwnerProcessId, uint32_t* pOwnerThreadId) -{ - assert(false); -} - -void SystemNative_LowLevelCrossPlatformMutex_SetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t ownerProcessId, uint32_t ownerThreadId) -{ - assert(false); -} - -uint8_t SystemNative_LowLevelCrossPlatformMutex_IsAbandoned(LowLevelCrossProcessMutex* mutex) -{ - assert(false); - return Error_EINVAL; -} - -void SystemNative_LowLevelCrossPlatformMutex_SetAbandoned(LowLevelCrossProcessMutex* mutex, uint8_t isAbandoned) -{ - assert(false); -} From ba0ecc20f402ff6370a46cda17390452e1fa09eb Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 17 Jul 2025 14:33:43 -0700 Subject: [PATCH 20/47] Rename as requested --- src/native/libs/System.Native/CMakeLists.txt | 4 ++-- src/native/libs/System.Native/entrypoints.c | 2 +- .../System.Native/{pal_mutex.c => pal_crossprocessmutex.c} | 2 +- .../System.Native/{pal_mutex.h => pal_crossprocessmutex.h} | 0 ...utex_unsupported.c => pal_crossprocessmutex_unsupported.c} | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename src/native/libs/System.Native/{pal_mutex.c => pal_crossprocessmutex.c} (99%) rename src/native/libs/System.Native/{pal_mutex.h => pal_crossprocessmutex.h} (100%) rename src/native/libs/System.Native/{pal_mutex_unsupported.c => pal_crossprocessmutex_unsupported.c} (100%) diff --git a/src/native/libs/System.Native/CMakeLists.txt b/src/native/libs/System.Native/CMakeLists.txt index 0dd23e452be27d..0819d01f59bdd2 100644 --- a/src/native/libs/System.Native/CMakeLists.txt +++ b/src/native/libs/System.Native/CMakeLists.txt @@ -43,10 +43,10 @@ endif() if (CLR_CMAKE_TARGET_APPLE OR CLR_CMAKE_TARGET_ANDROID OR CLR_CMAKE_TARGET_BROWSER OR CLR_CMAKE_TARGET_WASI) list (APPEND NATIVE_SOURCES - pal_mutex_unsupported.c) + pal_crossprocessmutex_unsupported.c) else() list (APPEND NATIVE_SOURCES - pal_mutex.c + pal_crossprocessmutex.c ) endif() diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c index 4edcd0bb2e8f4d..986f74f4ee0e4d 100644 --- a/src/native/libs/System.Native/entrypoints.c +++ b/src/native/libs/System.Native/entrypoints.c @@ -16,7 +16,7 @@ #include "pal_log.h" #include "pal_memory.h" #include "pal_mount.h" -#include "pal_mutex.h" +#include "pal_crossprocessmutex.h" #include "pal_networkchange.h" #include "pal_networking.h" #include "pal_networkstatistics.h" diff --git a/src/native/libs/System.Native/pal_mutex.c b/src/native/libs/System.Native/pal_crossprocessmutex.c similarity index 99% rename from src/native/libs/System.Native/pal_mutex.c rename to src/native/libs/System.Native/pal_crossprocessmutex.c index 52edca9f772aad..b7a6caaf99637a 100644 --- a/src/native/libs/System.Native/pal_mutex.c +++ b/src/native/libs/System.Native/pal_crossprocessmutex.c @@ -3,7 +3,7 @@ #include "pal_config.h" #include "pal_errno.h" -#include "pal_mutex.h" +#include "pal_crossprocessmutex.h" #include #include diff --git a/src/native/libs/System.Native/pal_mutex.h b/src/native/libs/System.Native/pal_crossprocessmutex.h similarity index 100% rename from src/native/libs/System.Native/pal_mutex.h rename to src/native/libs/System.Native/pal_crossprocessmutex.h diff --git a/src/native/libs/System.Native/pal_mutex_unsupported.c b/src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c similarity index 100% rename from src/native/libs/System.Native/pal_mutex_unsupported.c rename to src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c From a2d912151bf2ebf1c81317f1b6ca9e23c5d9e0e8 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 17 Jul 2025 16:22:00 -0700 Subject: [PATCH 21/47] Remove unused AcquirePthreadMutexWithTimeout impl --- src/native/libs/System.Native/pal_threading.c | 43 ------------------- 1 file changed, 43 deletions(-) diff --git a/src/native/libs/System.Native/pal_threading.c b/src/native/libs/System.Native/pal_threading.c index 393a52e5b7652a..1d573af075355c 100644 --- a/src/native/libs/System.Native/pal_threading.c +++ b/src/native/libs/System.Native/pal_threading.c @@ -298,46 +298,3 @@ uint32_t SystemNative_TryGetUInt32OSThreadId(void) uint32_t result = (uint32_t)minipal_get_current_thread_id(); return result == 0 ? (uint32_t)-1 : result; } - -static int32_t AcquirePThreadMutexWithTimeout(pthread_mutex_t* mutex, int32_t timeoutMilliseconds) -{ - assert(mutex != NULL); - - if (timeoutMilliseconds == -1) - { - return pthread_mutex_lock(mutex); - } - else if (timeoutMilliseconds == 0) - { - return pthread_mutex_trylock(mutex); - } - - // Calculate the time at which a timeout should occur, and wait. Older versions of OSX don't support clock_gettime with - // CLOCK_MONOTONIC, so we instead compute the relative timeout duration, and use a relative variant of the timed wait. - struct timespec timeoutTimeSpec; - int32_t error = 0; -#if HAVE_CLOCK_GETTIME_NSEC_NP - timeoutTimeSpec.tv_sec = timeoutMilliseconds / 1000; - timeoutTimeSpec.tv_nsec = (timeoutMilliseconds % 1000) * 1000 * 1000; - - error = pthread_mutex_reltimedlock_np(mutex, &timeoutTimeSpec); -#else -#if HAVE_PTHREAD_CONDATTR_SETCLOCK && HAVE_CLOCK_MONOTONIC - error = clock_gettime(CLOCK_MONOTONIC, &timeoutTimeSpec); - assert(error == 0); -#else - struct timeval tv; - - error = gettimeofday(&tv, NULL); - assert(error == 0); - - timeoutTimeSpec.tv_sec = tv.tv_sec; - timeoutTimeSpec.tv_nsec = tv.tv_usec * 1000; -#endif - uint64_t nanoseconds = (uint64_t)timeoutMilliseconds * 1000 * 1000 + (uint64_t)timeoutTimeSpec.tv_nsec; - timeoutTimeSpec.tv_sec += nanoseconds / (1000 * 1000 * 1000); - timeoutTimeSpec.tv_nsec = nanoseconds % (1000 * 1000 * 1000); - - return pthread_mutex_timedlock((pthread_mutex_t*)mutex, &timeoutTimeSpec); -#endif -} From 0cfacc167011e69c8feccf230a9189d6d2360dcd Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 17 Jul 2025 16:23:00 -0700 Subject: [PATCH 22/47] Fix failure on the "unsupported" path --- .../libs/System.Native/pal_crossprocessmutex_unsupported.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c b/src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c index 57bcafa8ba7f7b..0fe61f7e1a63ea 100644 --- a/src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c +++ b/src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "pal_config.h" -#include "pal_mutex.h" +#include "pal_crossprocessmutex.h" #include "pal_errno.h" #include From 5cbaa623f39be688b0f2d3e5d01aea6bc142217f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 18 Jul 2025 18:07:28 +0000 Subject: [PATCH 23/47] Add arg references. --- .../pal_crossprocessmutex_unsupported.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c b/src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c index 0fe61f7e1a63ea..c8ced47ad26e85 100644 --- a/src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c +++ b/src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c @@ -24,45 +24,59 @@ int32_t SystemNative_LowLevelCrossProcessMutex_Size(void) int32_t SystemNative_LowLevelCrossProcessMutex_Init(LowLevelCrossProcessMutex* mutex) { + (void)mutex; assert(false); return Error_EINVAL; } int32_t SystemNative_LowLevelCrossProcessMutex_Acquire(LowLevelCrossProcessMutex* mutex, int32_t timeoutMilliseconds) { + (void)mutex; + (void)timeoutMilliseconds; assert(false); return Error_EINVAL; } int32_t SystemNative_LowLevelCrossProcessMutex_Release(LowLevelCrossProcessMutex* mutex) { + (void)mutex; assert(false); return Error_EINVAL; } int32_t SystemNative_LowLevelCrossProcessMutex_Destroy(LowLevelCrossProcessMutex* mutex) { + (void)mutex; assert(false); return Error_EINVAL; } void SystemNative_LowLevelCrossProcessMutex_GetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t* pOwnerProcessId, uint32_t* pOwnerThreadId) { + (void)mutex; + (void)pOwnerProcessId; + (void)pOwnerThreadId; assert(false); } void SystemNative_LowLevelCrossProcessMutex_SetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t ownerProcessId, uint32_t ownerThreadId) { + (void)mutex; + (void)ownerProcessId; + (void)ownerThreadId; assert(false); } uint8_t SystemNative_LowLevelCrossProcessMutex_IsAbandoned(LowLevelCrossProcessMutex* mutex) { + (void)mutex; assert(false); - return Error_EINVAL; + return false; } void SystemNative_LowLevelCrossProcessMutex_SetAbandoned(LowLevelCrossProcessMutex* mutex, uint8_t isAbandoned) { + (void)mutex; + (void)isAbandoned; assert(false); } From 05cecb63fd7c168c0bf2f1be088eceb05d6026aa Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 19 Jul 2025 00:05:30 +0000 Subject: [PATCH 24/47] Use the correct API for locking with a specific clock. --- src/native/libs/Common/pal_config.h.in | 1 + .../libs/System.Native/pal_crossprocessmutex.c | 15 +++++++++++---- src/native/libs/configure.cmake | 1 + 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/native/libs/Common/pal_config.h.in b/src/native/libs/Common/pal_config.h.in index 580cd68a574335..c379fdda93106e 100644 --- a/src/native/libs/Common/pal_config.h.in +++ b/src/native/libs/Common/pal_config.h.in @@ -99,6 +99,7 @@ #cmakedefine01 HAVE_CLOCK_REALTIME #cmakedefine01 HAVE_CLOCK_GETTIME_NSEC_NP #cmakedefine01 HAVE_PTHREAD_CONDATTR_SETCLOCK +#cmakedefine01 HAVE_PTHREAD_MUTEX_CLOCKLOCK #cmakedefine01 HAVE_TCP_H_TCPSTATE_ENUM #cmakedefine01 HAVE_TCP_FSM_H #cmakedefine01 HAVE_GSSFW_HEADERS diff --git a/src/native/libs/System.Native/pal_crossprocessmutex.c b/src/native/libs/System.Native/pal_crossprocessmutex.c index b7a6caaf99637a..b9694f9f2627b8 100644 --- a/src/native/libs/System.Native/pal_crossprocessmutex.c +++ b/src/native/libs/System.Native/pal_crossprocessmutex.c @@ -41,10 +41,16 @@ static int32_t AcquirePThreadMutexWithTimeout(pthread_mutex_t* mutex, int32_t ti timeoutTimeSpec.tv_nsec = (timeoutMilliseconds % 1000) * 1000 * 1000; error = pthread_mutex_reltimedlock_np(mutex, &timeoutTimeSpec); -#else -#if HAVE_PTHREAD_CONDATTR_SETCLOCK && HAVE_CLOCK_MONOTONIC +#elif HAVE_PTHREAD_MUTEX_CLOCKLOCK && HAVE_CLOCK_MONOTONIC error = clock_gettime(CLOCK_MONOTONIC, &timeoutTimeSpec); assert(error == 0); + + uint64_t nanoseconds = (uint64_t)timeoutMilliseconds * 1000 * 1000 + (uint64_t)timeoutTimeSpec.tv_nsec; + + timeoutTimeSpec.tv_sec += nanoseconds / (1000 * 1000 * 1000); + timeoutTimeSpec.tv_nsec = nanoseconds % (1000 * 1000 * 1000); + + return pthread_mutex_clocklock(mutex, CLOCK_MONOTONIC, &timeoutTimeSpec); #else struct timeval tv; @@ -53,12 +59,13 @@ static int32_t AcquirePThreadMutexWithTimeout(pthread_mutex_t* mutex, int32_t ti timeoutTimeSpec.tv_sec = tv.tv_sec; timeoutTimeSpec.tv_nsec = tv.tv_usec * 1000; -#endif + uint64_t nanoseconds = (uint64_t)timeoutMilliseconds * 1000 * 1000 + (uint64_t)timeoutTimeSpec.tv_nsec; + timeoutTimeSpec.tv_sec += nanoseconds / (1000 * 1000 * 1000); timeoutTimeSpec.tv_nsec = nanoseconds % (1000 * 1000 * 1000); - return pthread_mutex_timedlock((pthread_mutex_t*)mutex, &timeoutTimeSpec); + return pthread_mutex_timedlock(mutex, &timeoutTimeSpec); #endif } diff --git a/src/native/libs/configure.cmake b/src/native/libs/configure.cmake index 5348adb6b2e0b5..25dcc3239f158c 100644 --- a/src/native/libs/configure.cmake +++ b/src/native/libs/configure.cmake @@ -650,6 +650,7 @@ endif() if (NOT CLR_CMAKE_TARGET_WASI) check_library_exists(${PTHREAD_LIBRARY} pthread_condattr_setclock "" HAVE_PTHREAD_CONDATTR_SETCLOCK) + check_library_exists(${PTHREAD_LIBRARY} pthread_mutex_clocklock "" HAVE_PTHREAD_MUTEX_CLOCKLOCK) endif() check_symbol_exists( From e4d595d138694f9094bf3a048e094f37d2bc1204 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 21 Jul 2025 19:16:14 +0000 Subject: [PATCH 25/47] Mark noreturn for debug builds to satisfy compiler --- .../System.Native/pal_crossprocessmutex_unsupported.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c b/src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c index c8ced47ad26e85..810b92c0108340 100644 --- a/src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c +++ b/src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c @@ -12,6 +12,12 @@ #include #include +#ifdef DEBUG +#define DEBUGNOTRETURN __attribute__((noreturn)) +#else +#define DEBUGNOTRETURN +#endif + struct LowLevelCrossProcessMutex { bool Dummy; @@ -51,6 +57,7 @@ int32_t SystemNative_LowLevelCrossProcessMutex_Destroy(LowLevelCrossProcessMutex return Error_EINVAL; } +DEBUGNOTRETURN void SystemNative_LowLevelCrossProcessMutex_GetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t* pOwnerProcessId, uint32_t* pOwnerThreadId) { (void)mutex; @@ -59,6 +66,7 @@ void SystemNative_LowLevelCrossProcessMutex_GetOwnerProcessAndThreadId(LowLevelC assert(false); } +DEBUGNOTRETURN void SystemNative_LowLevelCrossProcessMutex_SetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t ownerProcessId, uint32_t ownerThreadId) { (void)mutex; @@ -74,6 +82,7 @@ uint8_t SystemNative_LowLevelCrossProcessMutex_IsAbandoned(LowLevelCrossProcessM return false; } +DEBUGNOTRETURN void SystemNative_LowLevelCrossProcessMutex_SetAbandoned(LowLevelCrossProcessMutex* mutex, uint8_t isAbandoned) { (void)mutex; From d1dca10938ffc21348e390cd1356d052daff80e0 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 24 Jul 2025 19:17:51 +0000 Subject: [PATCH 26/47] Allow abandonment from the finalizer thread. Sometimes we can only clean up a thread's managed logic from the finalizer thread. --- .../src/System/Threading/Thread.CoreCLR.cs | 4 ++++ src/coreclr/nativeaot/Runtime/FinalizerHelpers.cpp | 6 ++++++ .../src/System/Runtime/RuntimeImports.cs | 6 +++++- .../src/System/Threading/Thread.NativeAot.cs | 5 +++++ src/coreclr/vm/comsynchronizable.cpp | 11 +++++++++++ src/coreclr/vm/comsynchronizable.h | 2 ++ src/coreclr/vm/qcallentrypoints.cpp | 1 + .../src/System/Threading/NamedMutex.Unix.cs | 6 ++++-- .../src/System/Threading/Thread.Mono.cs | 3 +++ src/mono/mono/metadata/icall-decl.h | 1 + src/mono/mono/metadata/icall-def.h | 1 + src/mono/mono/metadata/threads.c | 6 ++++++ 12 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index 99569377896a14..b680f690a5173b 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -513,6 +513,10 @@ private static void PollGC() static void PollGCWorker() => PollGCInternal(); } + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_CurrentThreadIsFinalizerThread")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool CurrentThreadIsFinalizerThread(); + [StructLayout(LayoutKind.Sequential)] private struct NativeThreadClass { diff --git a/src/coreclr/nativeaot/Runtime/FinalizerHelpers.cpp b/src/coreclr/nativeaot/Runtime/FinalizerHelpers.cpp index 730edbc87a3a3b..ed25a700b89b10 100644 --- a/src/coreclr/nativeaot/Runtime/FinalizerHelpers.cpp +++ b/src/coreclr/nativeaot/Runtime/FinalizerHelpers.cpp @@ -262,3 +262,9 @@ FCIMPL0(OBJECTREF, RhpGetNextFinalizableObject) } } FCIMPLEND + +FCIMPL0(FC_BOOL_RET, RhpCurrentThreadIsFinalizerThread) +{ + FC_RETURN_BOOL(ThreadStore::GetCurrentThread() == g_pFinalizerThread); +} +FCIMPLEND diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs index 78093c50ea2588..a0fae3b61a5339 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs @@ -89,9 +89,13 @@ internal static void RhWaitForPendingFinalizers(bool allowReentrantWait) { RhWaitForPendingFinalizers(allowReentrantWait ? 1 : 0); } + + [MethodImpl(MethodImplOptions.InternalCall)] + [RuntimeImport(RuntimeLibrary, "RhpCurrentThreadIsFinalizerThread")] + internal static partial bool RhpCurrentThreadIsFinalizerThread(); // Get maximum GC generation number. - [MethodImplAttribute(MethodImplOptions.InternalCall)] + [MethodImplAttribute(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhGetMaxGcGeneration")] internal static extern int RhGetMaxGcGeneration(); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs index 3758fca9e8a0a2..885ad8607ff9b3 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/Thread.NativeAot.cs @@ -517,5 +517,10 @@ internal static void WaitForForegroundThreads() } s_allDone.WaitOne(); } + + internal static bool CurrentThreadIsFinalizerThread() + { + return RuntimeImports.RhpCurrentThreadIsFinalizerThread(); + } } } diff --git a/src/coreclr/vm/comsynchronizable.cpp b/src/coreclr/vm/comsynchronizable.cpp index 10e78584795d26..634fbcd40e77df 100644 --- a/src/coreclr/vm/comsynchronizable.cpp +++ b/src/coreclr/vm/comsynchronizable.cpp @@ -899,3 +899,14 @@ extern "C" void QCALLTYPE ThreadNative_ResetAbort() pThread->UnmarkThreadForAbort(EEPolicy::TA_Safe); } } + +extern "C" BOOL QCALLTYPE ThreadNative_CurrentThreadIsFinalizerThread() +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + return IsFinalizerThread() ? TRUE : FALSE; + + END_QCALL; +} \ No newline at end of file diff --git a/src/coreclr/vm/comsynchronizable.h b/src/coreclr/vm/comsynchronizable.h index 004da5691c7a65..712e925adac411 100644 --- a/src/coreclr/vm/comsynchronizable.h +++ b/src/coreclr/vm/comsynchronizable.h @@ -71,5 +71,7 @@ extern "C" void QCALLTYPE ThreadNative_Sleep(INT32 iTime); extern "C" void QCALLTYPE ThreadNative_DisableComObjectEagerCleanup(QCall::ThreadHandle thread); #endif // FEATURE_COMINTEROP +extern "C" BOOL QCALLTYPE ThreadNative_CurrentThreadIsFinalizerThread(); + #endif // _COMSYNCHRONIZABLE_H diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index f3343f5555a1f2..578c3d1bdebe93 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -303,6 +303,7 @@ static const Entry s_QCall[] = #ifdef FEATURE_COMINTEROP DllImportEntry(ThreadNative_DisableComObjectEagerCleanup) #endif // FEATURE_COMINTEROP + DllImportEntry(ThreadNative_CurrentThreadIsFinalizerThread) DllImportEntry(WaitHandle_WaitOneCore) DllImportEntry(WaitHandle_WaitMultipleIgnoringSyncContext) DllImportEntry(WaitHandle_SignalAndWait) 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 6c1f616579ba35..07a5cb94ce5ca5 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 @@ -143,6 +143,10 @@ public void Abandon() _lockCount = 0; SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); + + Debug.Assert(IsLockOwnedByCurrentThread || Thread.CurrentThreadIsFinalizerThread(), + "Abandon can only be called from the thread that owns the lock or from the finalizer thread."); + ReleaseLockCore(); RemoveOwnedNamedMutex(Thread.CurrentThread, this); @@ -314,7 +318,6 @@ protected override MutexTryAcquireLockResult AcquireLockCore(int timeoutMillisec protected override void ReleaseLockCore() { - Debug.Assert(IsLockOwnedByCurrentThread); Debug.Assert(_lockCount == 0); Interop.Sys.LowLevelCrossProcessMutex_SetOwnerProcessAndThreadId( _sharedData, @@ -395,7 +398,6 @@ private bool IsLockOwnedByAnyThread protected override void ReleaseLockCore() { - Debug.Assert(IsLockOwnedByCurrentThread); Debug.Assert(_lockCount == 0); _sharedData->LockOwnerProcessId = InvalidProcessId; _sharedData->LockOwnerThreadId = InvalidThreadId; diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs index 5715dd23923f6b..2d0cdefd7daefa 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/Thread.Mono.cs @@ -351,6 +351,9 @@ private static void SpinWait_nop() [MethodImplAttribute(MethodImplOptions.InternalCall)] private static extern void SetPriority(Thread thread, int priority); + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern bool CurrentThreadIsFinalizerThread(); + internal int GetSmallId() => small_id; internal bool HasExternalEventLoop diff --git a/src/mono/mono/metadata/icall-decl.h b/src/mono/mono/metadata/icall-decl.h index 9c5e9f8c56490b..ea8336b4f7bc0f 100644 --- a/src/mono/mono/metadata/icall-decl.h +++ b/src/mono/mono/metadata/icall-decl.h @@ -64,6 +64,7 @@ ICALL_EXPORT MonoAssemblyName* ves_icall_System_Reflection_AssemblyName_GetNativ ICALL_EXPORT MonoBoolean ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_SufficientExecutionStack (void); ICALL_EXPORT MonoBoolean ves_icall_System_Threading_Thread_YieldInternal (void); ICALL_EXPORT MonoThread *ves_icall_System_Threading_Thread_GetCurrentThread (void); +ICALL_EXPORT MonoBoolean ves_icall_System_Threading_Thread_CurrentThreadIsFinalizerThread (void); ICALL_EXPORT void ves_icall_System_ArgIterator_Setup (MonoArgIterator*, char*, char*); ICALL_EXPORT MonoType* ves_icall_System_ArgIterator_IntGetNextArgType (MonoArgIterator*); ICALL_EXPORT void ves_icall_System_ArgIterator_IntGetNextArg (MonoArgIterator*, MonoTypedRef*); diff --git a/src/mono/mono/metadata/icall-def.h b/src/mono/mono/metadata/icall-def.h index 444a41c2b2aa45..3ab137aa253788 100644 --- a/src/mono/mono/metadata/icall-def.h +++ b/src/mono/mono/metadata/icall-def.h @@ -586,6 +586,7 @@ HANDLES(MONIT_9, "try_enter_with_atomic_var", ves_icall_System_Threading_Monitor ICALL_TYPE(THREAD, "System.Threading.Thread", THREAD_1) HANDLES(THREAD_1, "ClrState", ves_icall_System_Threading_Thread_ClrState, void, 2, (MonoInternalThread, guint32)) +NOHANDLES(ICALL(THREAD_16, "CurrentThreadIsFinalizerThread", ves_icall_System_Threading_Thread_CurrentThreadIsFinalizerThread)) HANDLES(ITHREAD_2, "FreeInternal", ves_icall_System_Threading_InternalThread_Thread_free_internal, void, 1, (MonoInternalThread)) HANDLES(THREAD_15, "GetCurrentOSThreadId", ves_icall_System_Threading_Thread_GetCurrentOSThreadId, guint64, 0, ()) NOHANDLES(ICALL(THREAD_5, "GetCurrentThread", ves_icall_System_Threading_Thread_GetCurrentThread)) diff --git a/src/mono/mono/metadata/threads.c b/src/mono/mono/metadata/threads.c index f641c393dd785c..cb0f1001ad53de 100644 --- a/src/mono/mono/metadata/threads.c +++ b/src/mono/mono/metadata/threads.c @@ -1759,6 +1759,12 @@ ves_icall_System_Threading_Thread_GetCurrentThread (void) return mono_thread_current (); } +MonoBoolean +ves_icall_System_Threading_Thread_CurrentThreadIsFinalizerThread (void) +{ + return mono_gc_is_finalizer_internal_thread (mono_thread_internal_current ()); +} + static MonoInternalThread* thread_handle_to_internal_ptr (MonoThreadObjectHandle thread_handle) { From 1c7c39db641ca1053cb5250458e33a9d441e6341 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 25 Jul 2025 00:17:27 +0000 Subject: [PATCH 27/47] Add timeout for one test that was missing it. Fix named mutex tests on Mono --- .../src/System/Threading/NamedMutex.Unix.cs | 67 +++++++++++++------ .../WaitSubsystem.ThreadWaitInfo.Unix.cs | 2 +- .../System.Threading/tests/MutexTests.cs | 2 +- 3 files changed, 48 insertions(+), 23 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 07a5cb94ce5ca5..b4fa063d9b08f0 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 @@ -38,7 +38,8 @@ internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader< public SharedMemoryId Id => _processDataHeader._id; - public abstract bool IsLockOwnedByCurrentThread { get; } + public bool IsLockOwnedByCurrentThread => IsLockOwnedByThreadInThisProcess(Thread.CurrentThread); + protected abstract bool IsLockOwnedByThreadInThisProcess(Thread thread); public bool IsLockOwnedByAnyThreadInThisProcess => _lockOwnerThread is not null; protected abstract void SetLockOwnerToCurrentThread(); @@ -136,20 +137,42 @@ private static void RemoveOwnedNamedMutex(Thread currentThread, NamedMutexProces namedMutexProcessDataBase._nextInThreadOwnedNamedMutexList = null; } - public void Abandon() + public void Abandon(Thread abandonedThread) { + SharedMemoryManager.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. + RemoveOwnedNamedMutex(abandonedThread, this); + return; + } + IsAbandoned = true; _lockCount = 0; - SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); - - Debug.Assert(IsLockOwnedByCurrentThread || Thread.CurrentThreadIsFinalizerThread(), - "Abandon can only be called from the thread that owns the lock or from the finalizer thread."); + Debug.Assert(_lockOwnerThread is not null); ReleaseLockCore(); - RemoveOwnedNamedMutex(Thread.CurrentThread, this); + RemoveOwnedNamedMutex(_lockOwnerThread, this); _lockOwnerThread = null; // Remove the refcount from the thread's wait info. _processDataHeader.DecrementRefCount(); @@ -250,14 +273,11 @@ internal sealed unsafe class NamedMutexProcessDataWithPThreads(SharedMemoryProce public static nuint SharedDataSize { get; } = (nuint)Interop.Sys.LowLevelCrossProcessMutex_Size(); private readonly void* _sharedData = SharedMemoryProcessDataHeader.GetDataPointer(processDataHeader); - public override bool IsLockOwnedByCurrentThread + protected override bool IsLockOwnedByThreadInThisProcess(Thread thread) { - get - { - Interop.Sys.LowLevelCrossProcessMutex_GetOwnerProcessAndThreadId(_sharedData, out uint ownerProcessId, out uint ownerThreadId); - return ownerProcessId == (uint)Environment.ProcessId && - ownerThreadId == (uint)Thread.CurrentThread.ManagedThreadId; - } + Interop.Sys.LowLevelCrossProcessMutex_GetOwnerProcessAndThreadId(_sharedData, out uint ownerProcessId, out uint ownerThreadId); + return ownerProcessId == (uint)Environment.ProcessId && + ownerThreadId == (uint)thread.ManagedThreadId; } protected override void SetLockOwnerToCurrentThread() @@ -293,10 +313,18 @@ protected override MutexTryAcquireLockResult AcquireLockCore(int timeoutMillisec 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(result != MutexTryAcquireLockResult.AcquiredLockButMutexWasAbandoned); Debug.Assert(!IsAbandoned); try { @@ -372,13 +400,10 @@ public NamedMutexProcessDataNoPThreads(SharedMemoryProcessDataHeaderLockOwnerProcessId == (uint)Environment.ProcessId && - _sharedData->LockOwnerThreadId == (uint)Thread.CurrentThread.ManagedThreadId; - } + return _sharedData->LockOwnerProcessId == (uint)Environment.ProcessId && + _sharedData->LockOwnerThreadId == (uint)thread.ManagedThreadId; } protected override void SetLockOwnerToCurrentThread() diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs index a6dd3b74109e03..98c60d51214d51 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs @@ -615,7 +615,7 @@ public void OnThreadExiting() break; } - namedMutex.Abandon(); + namedMutex.Abandon(_thread); Debug.Assert(LockedNamedMutexesHead != namedMutex); } } diff --git a/src/libraries/System.Threading/tests/MutexTests.cs b/src/libraries/System.Threading/tests/MutexTests.cs index 7eb2364b69e0c2..e2d120ce789f30 100644 --- a/src/libraries/System.Threading/tests/MutexTests.cs +++ b/src/libraries/System.Threading/tests/MutexTests.cs @@ -923,7 +923,7 @@ public void NamedMutex_ThreadExitDisposeRaceTest() } return createdNew; } - }); + }, ThreadTestHelpers.UnexpectedTimeoutMilliseconds); } } From e23ccc504f3c08033b8587e395b3b4a495e5d20b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 25 Jul 2025 00:25:59 +0000 Subject: [PATCH 28/47] Fix CoreCLR and NAOT builds --- .../src/System/Runtime/RuntimeImports.cs | 4 ++-- src/coreclr/vm/comsynchronizable.cpp | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs index a0fae3b61a5339..bf254f0e471194 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs @@ -89,10 +89,10 @@ internal static void RhWaitForPendingFinalizers(bool allowReentrantWait) { RhWaitForPendingFinalizers(allowReentrantWait ? 1 : 0); } - + [MethodImpl(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhpCurrentThreadIsFinalizerThread")] - internal static partial bool RhpCurrentThreadIsFinalizerThread(); + internal static extern bool RhpCurrentThreadIsFinalizerThread(); // Get maximum GC generation number. [MethodImplAttribute(MethodImplOptions.InternalCall)] diff --git a/src/coreclr/vm/comsynchronizable.cpp b/src/coreclr/vm/comsynchronizable.cpp index 634fbcd40e77df..65f19318420374 100644 --- a/src/coreclr/vm/comsynchronizable.cpp +++ b/src/coreclr/vm/comsynchronizable.cpp @@ -904,9 +904,13 @@ extern "C" BOOL QCALLTYPE ThreadNative_CurrentThreadIsFinalizerThread() { QCALL_CONTRACT; + BOOL retval; + BEGIN_QCALL; - return IsFinalizerThread() ? TRUE : FALSE; + retval = IsFinalizerThread() ? TRUE : FALSE; END_QCALL; + + return retval; } \ No newline at end of file From 510efcfd485c4d015035680c28ccdc534e9da24d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 25 Jul 2025 00:36:01 +0000 Subject: [PATCH 29/47] Refactor how named mutex ownership is tracked for abandonment to make it more trimmable --- .../src/System/Threading/NamedMutex.Unix.cs | 106 +++++++++++------- .../WaitSubsystem.ThreadWaitInfo.Unix.cs | 37 ++---- 2 files changed, 78 insertions(+), 65 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 b4fa063d9b08f0..9ef7265c07986e 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 @@ -16,6 +16,66 @@ namespace System.Threading { + + internal sealed class NamedMutexOwnershipChain(Thread thread) + { + private Thread _thread = thread; + private NamedMutexProcessDataBase? _head; + + public void Add(NamedMutexProcessDataBase namedMutex) + { + SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); + namedMutex.NextOwnedNamedMutex = _head; + _head = namedMutex; + } + + public void Remove(NamedMutexProcessDataBase namedMutex) + { + SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); + if (_head == namedMutex) + { + _head = namedMutex.NextOwnedNamedMutex; + } + else + { + NamedMutexProcessDataBase? previous = _head; + while (previous?.NextOwnedNamedMutex != namedMutex) + { + previous = previous?.NextOwnedNamedMutex; + } + + if (previous is not null) + { + previous.NextOwnedNamedMutex = namedMutex.NextOwnedNamedMutex; + } + } + + namedMutex.NextOwnedNamedMutex = null; + } + + public void Abandon() + { + WaitSubsystem.LockHolder scope = SharedMemoryManager.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 header) : ISharedMemoryProcessData { private const byte SyncSystemVersion = 1; @@ -34,10 +94,11 @@ internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader< private readonly SharedMemoryProcessDataHeader _processDataHeader = header; protected nuint _lockCount; private Thread? _lockOwnerThread; - private NamedMutexProcessDataBase? _nextInThreadOwnedNamedMutexList; 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; @@ -67,7 +128,7 @@ public MutexTryAcquireLockResult TryAcquireLock(WaitSubsystem.ThreadWaitInfo wai _lockOwnerThread = waitInfo.Thread; // Add the ref count for the thread's wait info. _processDataHeader.IncrementRefCount(); - AddOwnedNamedMutex(waitInfo.Thread, this); + waitInfo.NamedMutexOwnershipChain.Add(this); if (IsAbandoned) { @@ -78,13 +139,6 @@ public MutexTryAcquireLockResult TryAcquireLock(WaitSubsystem.ThreadWaitInfo wai return result; } - private static void AddOwnedNamedMutex(Thread thread, NamedMutexProcessDataBase namedMutexProcessDataBase) - { - SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); - namedMutexProcessDataBase._nextInThreadOwnedNamedMutexList = thread.WaitInfo.LockedNamedMutexesHead; - thread.WaitInfo.LockedNamedMutexesHead = namedMutexProcessDataBase; - } - public void ReleaseLock() { if (!IsLockOwnedByCurrentThread) @@ -101,7 +155,7 @@ public void ReleaseLock() WaitSubsystem.LockHolder scope = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); try { - RemoveOwnedNamedMutex(Thread.CurrentThread, this); + Thread.CurrentThread.WaitInfo.NamedMutexOwnershipChain.Remove(this); _lockOwnerThread = null; ReleaseLockCore(); // Remove the refcount from the thread's wait info. @@ -113,31 +167,7 @@ public void ReleaseLock() } } - private static void RemoveOwnedNamedMutex(Thread currentThread, NamedMutexProcessDataBase namedMutexProcessDataBase) - { - SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); - if (currentThread.WaitInfo.LockedNamedMutexesHead == namedMutexProcessDataBase) - { - currentThread.WaitInfo.LockedNamedMutexesHead = namedMutexProcessDataBase._nextInThreadOwnedNamedMutexList; - } - else - { - NamedMutexProcessDataBase? previous = currentThread.WaitInfo.LockedNamedMutexesHead; - while (previous?._nextInThreadOwnedNamedMutexList != namedMutexProcessDataBase) - { - previous = previous?._nextInThreadOwnedNamedMutexList; - } - - if (previous is not null) - { - previous._nextInThreadOwnedNamedMutexList = namedMutexProcessDataBase._nextInThreadOwnedNamedMutexList; - } - } - - namedMutexProcessDataBase._nextInThreadOwnedNamedMutexList = null; - } - - public void Abandon(Thread abandonedThread) + public void Abandon(NamedMutexOwnershipChain chain, Thread abandonedThread) { SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); @@ -160,7 +190,7 @@ public void Abandon(Thread 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. - RemoveOwnedNamedMutex(abandonedThread, this); + chain.Remove(this); return; } @@ -172,7 +202,7 @@ public void Abandon(Thread abandonedThread) ReleaseLockCore(); - RemoveOwnedNamedMutex(_lockOwnerThread, this); + chain.Remove(this); _lockOwnerThread = null; // Remove the refcount from the thread's wait info. _processDataHeader.DecrementRefCount(); @@ -263,7 +293,7 @@ public virtual void Close(bool releaseSharedData) { if (IsLockOwnedByCurrentThread) { - RemoveOwnedNamedMutex(Thread.CurrentThreadAssumedInitialized, this); + Thread.CurrentThread.WaitInfo.NamedMutexOwnershipChain.Remove(this); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs index 98c60d51214d51..71dba00717c999 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs @@ -93,7 +93,7 @@ public sealed class ThreadWaitInfo /// The linked list has only a head and no tail, which means acquired mutexes are prepended and /// mutexes are abandoned in reverse order. /// - private NamedMutexProcessDataBase? _lockedNamedMutexesHead; + private NamedMutexOwnershipChain? _namedMutexOwnershipChain; #endif public ThreadWaitInfo(Thread thread) @@ -564,17 +564,18 @@ public WaitableObject? LockedMutexesHead } #if FEATURE_CROSS_PROCESS_MUTEX - public NamedMutexProcessDataBase? LockedNamedMutexesHead + public NamedMutexOwnershipChain NamedMutexOwnershipChain { get { SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); - return _lockedNamedMutexesHead; - } - set - { - SharedMemoryManager.Instance.VerifyCreationDeletionProcessLockIsLocked(); - _lockedNamedMutexesHead = value; + return Volatile.Read(ref _namedMutexOwnershipChain) ?? AllocateOwnershipChain(); + + NamedMutexOwnershipChain AllocateOwnershipChain() + { + Interlocked.CompareExchange(ref _namedMutexOwnershipChain, new NamedMutexOwnershipChain(this._thread), null!); + return _namedMutexOwnershipChain; + } } } #endif @@ -604,25 +605,7 @@ public void OnThreadExiting() } #if FEATURE_CROSS_PROCESS_MUTEX - LockHolder scope = SharedMemoryManager.Instance.AcquireCreationDeletionProcessLock(); - try - { - while (true) - { - NamedMutexProcessDataBase? namedMutex = LockedNamedMutexesHead; - if (namedMutex == null) - { - break; - } - - namedMutex.Abandon(_thread); - Debug.Assert(LockedNamedMutexesHead != namedMutex); - } - } - finally - { - scope.Dispose(); - } + _namedMutexOwnershipChain?.Abandon(); #endif } From 895af9eab234185449a92136477da361727594f2 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 24 Jul 2025 20:17:31 -0700 Subject: [PATCH 30/47] Update comsynchronizable.cpp --- src/coreclr/vm/comsynchronizable.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/comsynchronizable.cpp b/src/coreclr/vm/comsynchronizable.cpp index 65f19318420374..fa119716aa47a8 100644 --- a/src/coreclr/vm/comsynchronizable.cpp +++ b/src/coreclr/vm/comsynchronizable.cpp @@ -904,7 +904,7 @@ extern "C" BOOL QCALLTYPE ThreadNative_CurrentThreadIsFinalizerThread() { QCALL_CONTRACT; - BOOL retval; + BOOL retval = FALSE; BEGIN_QCALL; From 4e8027088858252ae15c9b9a5cd1919339b86b44 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 29 Jul 2025 19:25:28 +0000 Subject: [PATCH 31/47] Use a Mutex for process-level locking in the no-pthreads implementation. Its the only primitive that supports abandoment correctly. --- .../src/System/Threading/NamedMutex.Unix.cs | 20 +++++++++++++------ .../WaitSubsystem.ThreadWaitInfo.Unix.cs | 7 ++++--- 2 files changed, 18 insertions(+), 9 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 9ef7265c07986e..9d1c5e5bf8f547 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 @@ -400,7 +400,7 @@ public override void Close(bool releaseSharedData) internal sealed unsafe class NamedMutexProcessDataNoPThreads : NamedMutexProcessDataBase { private const string SharedMemoryLockFilesDirectoryName = "lockfiles"; - private readonly Lock _processLevelLock = new(); + private readonly Mutex _processLevelMutex = new Mutex(initiallyOwned: false); private readonly SafeFileHandle _sharedLockFileHandle; private readonly SharedData* _sharedData; @@ -458,7 +458,7 @@ protected override void ReleaseLockCore() _sharedData->LockOwnerThreadId = InvalidThreadId; Interop.Sys.FLock(_sharedLockFileHandle, Interop.Sys.LockOperations.LOCK_UN); - _processLevelLock.Exit(); + _processLevelMutex.ReleaseMutex(); } protected override bool IsAbandoned @@ -472,7 +472,7 @@ public override void Close(bool releaseSharedData) base.Close(releaseSharedData); _sharedLockFileHandle.Dispose(); - _processLevelLock.Dispose(); + _processLevelMutex.Dispose(); } protected override unsafe MutexTryAcquireLockResult AcquireLockCore(int timeoutMilliseconds) @@ -486,9 +486,17 @@ protected override unsafe MutexTryAcquireLockResult AcquireLockCore(int timeoutM // 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; - if (!_processLevelLock.TryEnter(timeoutMilliseconds)) + try { - return MutexTryAcquireLockResult.TimedOut; + 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 @@ -570,7 +578,7 @@ protected override unsafe MutexTryAcquireLockResult AcquireLockCore(int timeoutM { if (releaseProcessLock) { - _processLevelLock.Exit(); + _processLevelMutex.ReleaseMutex(); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs index 71dba00717c999..7d169c9a6404f5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs @@ -584,6 +584,10 @@ public void OnThreadExiting() { // Abandon locked mutexes. Acquired mutexes are prepended to the linked list, so the mutexes are abandoned in // last-acquired-first-abandoned order. + +#if FEATURE_CROSS_PROCESS_MUTEX + _namedMutexOwnershipChain?.Abandon(); +#endif s_lock.Acquire(); try { @@ -604,9 +608,6 @@ public void OnThreadExiting() s_lock.Release(); } -#if FEATURE_CROSS_PROCESS_MUTEX - _namedMutexOwnershipChain?.Abandon(); -#endif } public sealed class WaitedListNode From 79c78124586dbbf1e9a5bb4d56c0d06867ae83be Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 29 Jul 2025 20:25:58 +0000 Subject: [PATCH 32/47] PR feedback --- .../src/Resources/Strings.resx | 26 ++++++++++++++++ .../src/System/IO/SharedMemoryManager.Unix.cs | 31 ++++++++++++------- .../src/System/Threading/NamedMutex.Unix.cs | 4 +-- .../WaitSubsystem.HandleManager.Unix.cs | 1 - .../pal_crossprocessmutex_unsupported.c | 17 ---------- 5 files changed, 47 insertions(+), 32 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 497a524ed303fa..f0693766a56caa 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -4378,5 +4378,31 @@ Dynamic entrypoint allocation is not supported in the current environment. + + Cannot release a lock that is not owned by the current thread. + + + Failed to initialize pthread mutex + + + The file '{0}' is not owned by the current user with UID {1}. + + + The file '{0}' does not have the expected permissions for user scope: {1}. + + + The path '{0}' exists but is not a directory. + + + The directory '{0}' does not have the expected owner or permissions for system scope: {1}, {2}. + + + The directory '{0}' is not owned by the current user with UID {1}. + + + The directory '{0}' does not have the expected permissions for user scope: {1}. + + + The directory '{0}' does not have the expected owner permissions: {1}. 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 6d4b0d10302fd8..db2e03a41a8913 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 @@ -96,7 +96,7 @@ internal struct SharedMemorySharedDataHeader private struct SharedMemoryAndVersion { public SharedMemoryType Type; - public uint Version; + public byte Version; } [FieldOffset(0)] @@ -106,9 +106,9 @@ private struct SharedMemoryAndVersion private ulong _raw; public readonly SharedMemoryType Type => _data.Type; - public readonly uint Version => _data.Version; + public readonly byte Version => _data.Version; - public SharedMemorySharedDataHeader(SharedMemoryType type, uint version) + public SharedMemorySharedDataHeader(SharedMemoryType type, byte version) { _data = new SharedMemoryAndVersion { @@ -143,7 +143,7 @@ public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandl SharedMemoryManager.Instance.AddProcessDataHeader(this); } - public static void* GetDataPointer(SharedMemoryProcessDataHeader processDataHeader) + public static void* GetDataPointer(SharedMemoryProcessDataHeader? processDataHeader) { return processDataHeader is null ? null @@ -161,7 +161,10 @@ public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandl out AutoReleaseFileLock creationDeletionLockFileHandle) { created = false; - creationDeletionLockFileHandle = new AutoReleaseFileLock(new SafeFileHandle()); + + AutoReleaseFileLock placeholderAutoReleaseLock = new AutoReleaseFileLock(new SafeFileHandle()); + + creationDeletionLockFileHandle = placeholderAutoReleaseLock; SharedMemoryId id = new(name, isUserScope); nuint sharedDataUsedByteCount = (nuint)sizeof(SharedMemorySharedDataHeader) + sharedMemoryDataSize; @@ -274,6 +277,8 @@ public SharedMemoryProcessDataHeader(SharedMemoryId id, SafeFileHandle fileHandl if (!createdFile) { creationDeletionLockFileHandle.Dispose(); + // Reset to the placeholder value to avoid returning a pre-disposed lock. + creationDeletionLockFileHandle = placeholderAutoReleaseLock; } processDataHeader = new SharedMemoryProcessDataHeader( @@ -390,6 +395,8 @@ internal static class SharedMemoryHelpers private const UnixFileMode PermissionsMask_Sticky = UnixFileMode.StickyBit; private const string SharedMemoryUniqueTempNameTemplate = ".dotnet.XXXXXX"; + + // See https://developer.apple.com/documentation/Foundation/FileManager/containerURL(forSecurityApplicationGroupIdentifier:)#App-Groups-in-macOS for details on this path. private const string ApplicationContainerBasePathSuffix = "/Library/Group Containers/"; public static string SharedFilesPath { get; } = InitalizeSharedFilesPath(); @@ -445,13 +452,13 @@ internal static SafeFileHandle CreateOrOpenFile(string sharedMemoryFilePath, Sha if (fileStatus.Uid != id.Uid) { fd.Dispose(); - throw new IOException($"The file '{sharedMemoryFilePath}' is not owned by the current user with UID {id.Uid}."); + throw new IOException(SR.Format(SR.IO_SharedMemory_FileNotOwnedByUid, sharedMemoryFilePath, id.Uid)); } if ((fileStatus.Mode & (int)PermissionsMask_AllUsers_ReadWriteExecute) != (int)PermissionsMask_OwnerUser_ReadWrite) { fd.Dispose(); - throw new IOException($"The file '{sharedMemoryFilePath}' does not have the expected permissions for user scope: {PermissionsMask_OwnerUser_ReadWrite}."); + throw new IOException(SR.Format(SR.IO_SharedMemory_FilePermissionsIncorrect, sharedMemoryFilePath, PermissionsMask_OwnerUser_ReadWrite)); } } createdFile = false; @@ -593,7 +600,7 @@ internal static bool EnsureDirectoryExists(string directoryPath, SharedMemoryId } else { - throw new IOException($"The path '{directoryPath}' exists but is not a directory."); + throw new IOException(SR.Format(SR.IO_SharedMemory_PathExistsButNotDirectory, directoryPath)); } } @@ -620,7 +627,7 @@ internal static bool EnsureDirectoryExists(string directoryPath, SharedMemoryId return true; } - throw new IOException($"The directory '{directoryPath}' does not have the expected owner or permissions for system scope: {fileStatus.Uid}, {Convert.ToString(fileStatus.Mode, 8)}."); + throw new IOException(SR.Format(SR.IO_SharedMemory_DirectoryPermissionsIncorrect, directoryPath, fileStatus.Uid, Convert.ToString(fileStatus.Mode, 8))); } // For non-system directories (such as SharedFilesPath/UserUnscopedRuntimeTempDirectoryName), @@ -630,7 +637,7 @@ internal static bool EnsureDirectoryExists(string directoryPath, SharedMemoryId // For user-scoped directories, verify the owner UID if (id.IsUserScope && fileStatus.Uid != id.Uid) { - throw new IOException($"The directory '{directoryPath}' is not owned by the current user with UID {id.Uid}."); + throw new IOException(SR.Format(SR.IO_SharedMemory_DirectoryNotOwnedByUid, directoryPath, id.Uid)); } // Verify the permissions, or try to change them if possible @@ -644,7 +651,7 @@ internal static bool EnsureDirectoryExists(string directoryPath, SharedMemoryId // since other users aren't sufficiently restricted in permissions. if (id.IsUserScope) { - throw new IOException($"The directory '{directoryPath}' does not have the expected permissions for user scope: {Convert.ToString(fileStatus.Mode, 8)}."); + throw new IOException(SR.Format(SR.IO_SharedMemory_DirectoryPermissionsIncorrectUserScope, directoryPath, Convert.ToString(fileStatus.Mode, 8))); } @@ -652,7 +659,7 @@ internal static bool EnsureDirectoryExists(string directoryPath, SharedMemoryId permissionsMask = PermissionsMask_OwnerUser_ReadWriteExecute; if ((fileStatus.Mode & (int)permissionsMask) != (int)permissionsMask) { - throw new IOException($"The directory '{directoryPath}' does not have the expected owner permissions: {Convert.ToString(fileStatus.Mode, 8)}."); + throw new IOException(SR.Format(SR.IO_SharedMemory_DirectoryOwnerPermissionsIncorrect, directoryPath, Convert.ToString(fileStatus.Mode, 8))); } return true; 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 9d1c5e5bf8f547..7a9225eeffc761 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 @@ -143,7 +143,7 @@ public void ReleaseLock() { if (!IsLockOwnedByCurrentThread) { - throw new InvalidOperationException("Cannot release a lock that is not owned by the current thread."); + throw new InvalidOperationException(SR.Arg_InvalidOperationException_CannotReleaseUnownedMutex); } --_lockCount; @@ -276,7 +276,7 @@ private static unsafe void InitializeSharedData(void* v) if (Interop.Sys.LowLevelCrossProcessMutex_Init(v) != 0) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); - throw Interop.GetExceptionForIoErrno(errorInfo, "Failed to initialize pthread mutex"); + throw Interop.GetExceptionForIoErrno(errorInfo, SR.Arg_FailedToInitializePThreadMutex); } } else diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.HandleManager.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.HandleManager.Unix.cs index 6ac4e76fcd39f6..6bc31a9e1ec931 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.HandleManager.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.HandleManager.Unix.cs @@ -19,7 +19,6 @@ public interface IWaitableObject public static int Wait(this IWaitableObject waitable, ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool interruptible, bool prioritize) { - Debug.Assert(waitInfo != null); Debug.Assert(waitInfo.Thread == Thread.CurrentThread); Debug.Assert(timeoutMilliseconds >= -1); diff --git a/src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c b/src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c index 810b92c0108340..a155fb5708a139 100644 --- a/src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c +++ b/src/native/libs/System.Native/pal_crossprocessmutex_unsupported.c @@ -12,12 +12,6 @@ #include #include -#ifdef DEBUG -#define DEBUGNOTRETURN __attribute__((noreturn)) -#else -#define DEBUGNOTRETURN -#endif - struct LowLevelCrossProcessMutex { bool Dummy; @@ -31,7 +25,6 @@ int32_t SystemNative_LowLevelCrossProcessMutex_Size(void) int32_t SystemNative_LowLevelCrossProcessMutex_Init(LowLevelCrossProcessMutex* mutex) { (void)mutex; - assert(false); return Error_EINVAL; } @@ -39,53 +32,43 @@ int32_t SystemNative_LowLevelCrossProcessMutex_Acquire(LowLevelCrossProcessMutex { (void)mutex; (void)timeoutMilliseconds; - assert(false); return Error_EINVAL; } int32_t SystemNative_LowLevelCrossProcessMutex_Release(LowLevelCrossProcessMutex* mutex) { (void)mutex; - assert(false); return Error_EINVAL; } int32_t SystemNative_LowLevelCrossProcessMutex_Destroy(LowLevelCrossProcessMutex* mutex) { (void)mutex; - assert(false); return Error_EINVAL; } -DEBUGNOTRETURN void SystemNative_LowLevelCrossProcessMutex_GetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t* pOwnerProcessId, uint32_t* pOwnerThreadId) { (void)mutex; (void)pOwnerProcessId; (void)pOwnerThreadId; - assert(false); } -DEBUGNOTRETURN void SystemNative_LowLevelCrossProcessMutex_SetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t ownerProcessId, uint32_t ownerThreadId) { (void)mutex; (void)ownerProcessId; (void)ownerThreadId; - assert(false); } uint8_t SystemNative_LowLevelCrossProcessMutex_IsAbandoned(LowLevelCrossProcessMutex* mutex) { (void)mutex; - assert(false); return false; } -DEBUGNOTRETURN void SystemNative_LowLevelCrossProcessMutex_SetAbandoned(LowLevelCrossProcessMutex* mutex, uint8_t isAbandoned) { (void)mutex; (void)isAbandoned; - assert(false); } From 625b8d6539f9a3988c82a8ce8f2e7193ce3c94d8 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 29 Jul 2025 20:31:48 +0000 Subject: [PATCH 33/47] Do a little bit of cleanup on formatting --- .../src/System/IO/SharedMemoryManager.Unix.cs | 9 +-------- .../src/System/Threading/NamedMutex.Unix.cs | 11 ++--------- 2 files changed, 3 insertions(+), 17 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 db2e03a41a8913..db33fb845b233d 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 @@ -1,17 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; -using System.Runtime.Versioning; -using System.Text; using System.Threading; using Microsoft.Win32.SafeHandles; @@ -309,7 +302,7 @@ static void SetFileSize(SafeFileHandle fd, string path, long size) if (Interop.Sys.FTruncate(fd, size) < 0) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); - if (errorInfo.Error != Interop.Error.EBADF && errorInfo.Error != Interop.Error.EINVAL) + if (errorInfo.Error is not Interop.Error.EBADF and not Interop.Error.EINVAL) { // We know the file descriptor is valid and we know the size argument to FTruncate is correct, // so if EBADF or EINVAL is returned, it means we're dealing with a special file that can't be 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 7a9225eeffc761..d5687059e74238 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 @@ -1,25 +1,17 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.InteropServices.Marshalling; -using System.Runtime.Versioning; -using System.Text; using Microsoft.Win32.SafeHandles; namespace System.Threading { - internal sealed class NamedMutexOwnershipChain(Thread thread) { - private Thread _thread = thread; + private readonly Thread _thread = thread; private NamedMutexProcessDataBase? _head; public void Add(NamedMutexProcessDataBase namedMutex) @@ -76,6 +68,7 @@ public void Abandon() } } } + internal abstract class NamedMutexProcessDataBase(SharedMemoryProcessDataHeader header) : ISharedMemoryProcessData { private const byte SyncSystemVersion = 1; From bccae1d15f20418b6fcd6c5d52fdd047655c88d2 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 29 Jul 2025 21:45:10 +0000 Subject: [PATCH 34/47] Various build fixes --- src/mono/mono/metadata/threads.c | 2 +- src/native/libs/System.Native/pal_crossprocessmutex.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mono/mono/metadata/threads.c b/src/mono/mono/metadata/threads.c index cb0f1001ad53de..9100051a13185b 100644 --- a/src/mono/mono/metadata/threads.c +++ b/src/mono/mono/metadata/threads.c @@ -1762,7 +1762,7 @@ ves_icall_System_Threading_Thread_GetCurrentThread (void) MonoBoolean ves_icall_System_Threading_Thread_CurrentThreadIsFinalizerThread (void) { - return mono_gc_is_finalizer_internal_thread (mono_thread_internal_current ()); + return mono_gc_is_finalizer_internal_thread (mono_thread_internal_current ()) ? TRUE : FALSE; } static MonoInternalThread* diff --git a/src/native/libs/System.Native/pal_crossprocessmutex.c b/src/native/libs/System.Native/pal_crossprocessmutex.c index b9694f9f2627b8..aba44eb633a5d0 100644 --- a/src/native/libs/System.Native/pal_crossprocessmutex.c +++ b/src/native/libs/System.Native/pal_crossprocessmutex.c @@ -58,7 +58,7 @@ static int32_t AcquirePThreadMutexWithTimeout(pthread_mutex_t* mutex, int32_t ti assert(error == 0); timeoutTimeSpec.tv_sec = tv.tv_sec; - timeoutTimeSpec.tv_nsec = tv.tv_usec * 1000; + timeoutTimeSpec.tv_nsec = (long)(tv.tv_usec * 1000); uint64_t nanoseconds = (uint64_t)timeoutMilliseconds * 1000 * 1000 + (uint64_t)timeoutTimeSpec.tv_nsec; From 0d764fddc14db40793c55f1e67dc573423320f6d Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 30 Jul 2025 13:04:32 -0700 Subject: [PATCH 35/47] Don't release when abandoning as we don't know our thread and we can't guarantee that we can release (and we'll abandon anyway) --- .../src/System/Threading/NamedMutex.Unix.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 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 d5687059e74238..6b95434e2b5154 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 @@ -150,7 +150,7 @@ public void ReleaseLock() { Thread.CurrentThread.WaitInfo.NamedMutexOwnershipChain.Remove(this); _lockOwnerThread = null; - ReleaseLockCore(); + ReleaseLockCore(abandoning: false); // Remove the refcount from the thread's wait info. _processDataHeader.DecrementRefCount(); } @@ -193,7 +193,7 @@ public void Abandon(NamedMutexOwnershipChain chain, Thread abandonedThread) Debug.Assert(_lockOwnerThread is not null); - ReleaseLockCore(); + ReleaseLockCore(abandoning: true); chain.Remove(this); _lockOwnerThread = null; @@ -205,7 +205,7 @@ public void Abandon(NamedMutexOwnershipChain chain, Thread abandonedThread) protected abstract bool IsAbandoned { get; set; } - protected abstract void ReleaseLockCore(); + protected abstract void ReleaseLockCore(bool abandoning); internal static unsafe SharedMemoryProcessDataHeader? CreateOrOpen(string name, bool isUserScope, bool createIfNotExist, bool acquireLockIfCreated, out bool created) { @@ -367,7 +367,7 @@ protected override MutexTryAcquireLockResult AcquireLockCore(int timeoutMillisec return result; } - protected override void ReleaseLockCore() + protected override void ReleaseLockCore(bool abandoning) { Debug.Assert(_lockCount == 0); Interop.Sys.LowLevelCrossProcessMutex_SetOwnerProcessAndThreadId( @@ -444,14 +444,19 @@ private bool IsLockOwnedByAnyThread } } - protected override void ReleaseLockCore() + 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 From c5f32cb044d46ddae1a2ecdfa52e6f71b8160f9e Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 30 Jul 2025 13:05:30 -0700 Subject: [PATCH 36/47] Remove unintended whitespace change --- .../System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs index bf254f0e471194..55cf6037e80f95 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs @@ -95,7 +95,7 @@ internal static void RhWaitForPendingFinalizers(bool allowReentrantWait) internal static extern bool RhpCurrentThreadIsFinalizerThread(); // Get maximum GC generation number. - [MethodImplAttribute(MethodImplOptions.InternalCall)] + [MethodImplAttribute(MethodImplOptions.InternalCall)] [RuntimeImport(RuntimeLibrary, "RhGetMaxGcGeneration")] internal static extern int RhGetMaxGcGeneration(); From 47bd54c5cbc93f7d31ee4708cb67f5e2db1ea52b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 30 Jul 2025 21:45:56 +0000 Subject: [PATCH 37/47] Fix indent --- .../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 6b95434e2b5154..deda905c117621 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 @@ -455,7 +455,7 @@ protected override void ReleaseLockCore(bool 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(); + _processLevelMutex.ReleaseMutex(); } } From dda0121bb147e6ac520c84076d5f0efa8eedded7 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 14 Jul 2025 23:59:25 +0000 Subject: [PATCH 38/47] Use the wait subsystem on CoreCLR on Unix platforms. --- .../System.Private.CoreLib.csproj | 3 +- .../System/Threading/Mutex.CoreCLR.Unix.cs | 178 ------------------ .../src/System/Threading/Thread.CoreCLR.cs | 44 +++++ ...reCLR.cs => WaitHandle.CoreCLR.Windows.cs} | 0 src/coreclr/vm/comsynchronizable.cpp | 26 +++ src/coreclr/vm/comsynchronizable.h | 2 + src/coreclr/vm/qcallentrypoints.cpp | 1 + src/coreclr/vm/threads.h | 2 +- .../System.Private.CoreLib.Shared.projitems | 28 +-- .../src/System/Threading/WaitHandle.cs | 8 - .../System/Threading/WaitSubsystem.Unix.cs | 3 - 11 files changed, 89 insertions(+), 206 deletions(-) delete mode 100644 src/coreclr/System.Private.CoreLib/src/System/Threading/Mutex.CoreCLR.Unix.cs rename src/coreclr/System.Private.CoreLib/src/System/Threading/{WaitHandle.CoreCLR.cs => WaitHandle.CoreCLR.Windows.cs} (100%) diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index c12d6c854ac0c0..a64294355f6e44 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -238,7 +238,6 @@ - @@ -291,7 +290,6 @@ - @@ -301,6 +299,7 @@ Common\Interop\Windows\OleAut32\Interop.VariantChangeTypeEx.cs + diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Mutex.CoreCLR.Unix.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Mutex.CoreCLR.Unix.cs deleted file mode 100644 index 08b0748b412c8c..00000000000000 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Mutex.CoreCLR.Unix.cs +++ /dev/null @@ -1,178 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.IO; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; -using Microsoft.Win32.SafeHandles; - -namespace System.Threading -{ - /// - /// Synchronization primitive that can also be used for interprocess synchronization - /// - public sealed partial class Mutex : WaitHandle - { - private unsafe void CreateMutexCore(bool initiallyOwned) - { - SafeWaitHandle handle = - CreateMutex( - initiallyOwned, - name: null, - currentUserOnly: false, - systemCallErrors: null, - systemCallErrorsBufferSize: 0); - if (handle.IsInvalid) - { - int errorCode = Marshal.GetLastPInvokeError(); - handle.SetHandleAsInvalid(); - throw Win32Marshal.GetExceptionForWin32Error(errorCode); - } - - SafeWaitHandle = handle; - } - - private void CreateMutexCore( - bool initiallyOwned, - string? name, - NamedWaitHandleOptionsInternal options, - out bool createdNew) - { - bool currentUserOnly = false; - if (!string.IsNullOrEmpty(name) && options.WasSpecified) - { - name = options.GetNameWithSessionPrefix(name); - currentUserOnly = options.CurrentUserOnly; - } - - SafeWaitHandle mutexHandle = - CreateMutexCore(initiallyOwned, name, currentUserOnly, out int errorCode, out string? errorDetails); - if (mutexHandle.IsInvalid) - { - mutexHandle.SetHandleAsInvalid(); - if (errorCode == Interop.Errors.ERROR_FILENAME_EXCED_RANGE) - // On Unix, length validation is done by CoreCLR's PAL after converting to utf-8 - throw new ArgumentException(SR.Argument_WaitHandleNameTooLong, nameof(name)); - if (errorCode == Interop.Errors.ERROR_INVALID_HANDLE) - throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name)); - - throw Win32Marshal.GetExceptionForWin32Error(errorCode, name, errorDetails); - } - - createdNew = errorCode != Interop.Errors.ERROR_ALREADY_EXISTS; - SafeWaitHandle = mutexHandle; - } - - private static OpenExistingResult OpenExistingWorker( - string name, - NamedWaitHandleOptionsInternal options, - out Mutex? result) - { - ArgumentException.ThrowIfNullOrEmpty(name); - - bool currentUserOnly = false; - if (options.WasSpecified) - { - name = options.GetNameWithSessionPrefix(name); - currentUserOnly = options.CurrentUserOnly; - } - - result = null; - // To allow users to view & edit the ACL's, call OpenMutex - // with parameters to allow us to view & edit the ACL. This will - // fail if we don't have permission to view or edit the ACL's. - // If that happens, ask for less permissions. - SafeWaitHandle myHandle = OpenMutexCore(name, currentUserOnly, out int errorCode, out string? errorDetails); - - if (myHandle.IsInvalid) - { - myHandle.Dispose(); - - if (errorCode == Interop.Errors.ERROR_FILENAME_EXCED_RANGE) - { - // On Unix, length validation is done by CoreCLR's PAL after converting to utf-8 - throw new ArgumentException(SR.Argument_WaitHandleNameTooLong, nameof(name)); - } - if (Interop.Errors.ERROR_FILE_NOT_FOUND == errorCode || Interop.Errors.ERROR_INVALID_NAME == errorCode) - return OpenExistingResult.NameNotFound; - if (Interop.Errors.ERROR_PATH_NOT_FOUND == errorCode) - return OpenExistingResult.PathNotFound; - if (Interop.Errors.ERROR_INVALID_HANDLE == errorCode) - return OpenExistingResult.NameInvalid; - - throw Win32Marshal.GetExceptionForWin32Error(errorCode, name, errorDetails); - } - - result = new Mutex(myHandle); - return OpenExistingResult.Success; - } - - // Note: To call ReleaseMutex, you must have an ACL granting you - // MUTEX_MODIFY_STATE rights (0x0001). The other interesting value - // in a Mutex's ACL is MUTEX_ALL_ACCESS (0x1F0001). - public void ReleaseMutex() - { - if (!Interop.Kernel32.ReleaseMutex(SafeWaitHandle)) - { - throw new ApplicationException(SR.Arg_SynchronizationLockException); - } - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Unix-specific implementation - - private const int SystemCallErrorsBufferSize = 256; - - private static unsafe SafeWaitHandle CreateMutexCore( - bool initialOwner, - string? name, - bool currentUserOnly, - out int errorCode, - out string? errorDetails) - { - byte* systemCallErrors = stackalloc byte[SystemCallErrorsBufferSize]; - SafeWaitHandle mutexHandle = - CreateMutex(initialOwner, name, currentUserOnly, systemCallErrors, SystemCallErrorsBufferSize); - - // Get the error code even if the handle is valid, as it could be ERROR_ALREADY_EXISTS, indicating that the mutex - // already exists and was opened - errorCode = Marshal.GetLastPInvokeError(); - - errorDetails = mutexHandle.IsInvalid ? GetErrorDetails(systemCallErrors) : null; - return mutexHandle; - } - - private static unsafe SafeWaitHandle OpenMutexCore(string name, bool currentUserOnly, out int errorCode, out string? errorDetails) - { - byte* systemCallErrors = stackalloc byte[SystemCallErrorsBufferSize]; - SafeWaitHandle mutexHandle = OpenMutex(name, currentUserOnly, systemCallErrors, SystemCallErrorsBufferSize); - errorCode = mutexHandle.IsInvalid ? Marshal.GetLastPInvokeError() : Interop.Errors.ERROR_SUCCESS; - errorDetails = mutexHandle.IsInvalid ? GetErrorDetails(systemCallErrors) : null; - return mutexHandle; - } - - private static unsafe string? GetErrorDetails(byte* systemCallErrors) - { - int systemCallErrorsLength = - new ReadOnlySpan(systemCallErrors, SystemCallErrorsBufferSize).IndexOf((byte)'\0'); - if (systemCallErrorsLength > 0) - { - try - { - return - SR.Format(SR.Unix_SystemCallErrors, Encoding.UTF8.GetString(systemCallErrors, systemCallErrorsLength)); - } - catch { } // avoid hiding the original error due to an error here - } - - return null; - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "PAL_CreateMutexW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] - private static unsafe partial SafeWaitHandle CreateMutex([MarshalAs(UnmanagedType.Bool)] bool initialOwner, string? name, [MarshalAs(UnmanagedType.Bool)] bool currentUserOnly, byte* systemCallErrors, uint systemCallErrorsBufferSize); - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "PAL_OpenMutexW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] - private static unsafe partial SafeWaitHandle OpenMutex(string name, [MarshalAs(UnmanagedType.Bool)] bool currentUserOnly, byte* systemCallErrors, uint systemCallErrorsBufferSize); - } -} diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index b680f690a5173b..b3a58b2b282cf7 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -62,6 +62,10 @@ public sealed partial class Thread private bool _isDead; private bool _isThreadPool; +#if TARGET_UNIX || TARGET_BROWSER || TARGET_WASI + internal WaitSubsystem.ThreadWaitInfo? _waitInfo; +#endif + private Thread() { } public int ManagedThreadId @@ -298,6 +302,30 @@ public ThreadState ThreadState [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_GetThreadState")] private static partial int GetThreadState(ThreadHandle t); +#if TARGET_UNIX || TARGET_BROWSER || TARGET_WASI + internal void SetWaitSleepJoinState() + { + // This method is called when the thread is about to enter a wait, sleep, or join state. + // It sets the state in the native layer to indicate that the thread is waiting. + SetWaitSleepJoinState(GetNativeHandle()); + GC.KeepAlive(this); + } + + internal void ClearWaitSleepJoinState() + { + // This method is called when the thread is no longer in a wait, sleep, or join state. + // It clears the state in the native layer to indicate that the thread is no longer waiting. + ClearWaitSleepJoinState(GetNativeHandle()); + GC.KeepAlive(this); + } + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_SetWaitSleepJoinState")] + private static partial void SetWaitSleepJoinState(ThreadHandle t); + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_ClearWaitSleepJoinState")] + private static partial void ClearWaitSleepJoinState(ThreadHandle t); +#endif + /// /// An unstarted thread can be marked to indicate that it will host a /// single-threaded or multi-threaded apartment. @@ -513,6 +541,22 @@ private static void PollGC() static void PollGCWorker() => PollGCInternal(); } +#if TARGET_UNIX || TARGET_BROWSER || TARGET_WASI + internal WaitSubsystem.ThreadWaitInfo WaitInfo + { + get + { + return Volatile.Read(ref _waitInfo) ?? AllocateWaitInfo(); + + WaitSubsystem.ThreadWaitInfo AllocateWaitInfo() + { + Interlocked.CompareExchange(ref _waitInfo, new WaitSubsystem.ThreadWaitInfo(this), null!); + return _waitInfo; + } + } + } +#endif + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_CurrentThreadIsFinalizerThread")] [return: MarshalAs(UnmanagedType.Bool)] internal static partial bool CurrentThreadIsFinalizerThread(); diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/WaitHandle.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/WaitHandle.CoreCLR.Windows.cs similarity index 100% rename from src/coreclr/System.Private.CoreLib/src/System/Threading/WaitHandle.CoreCLR.cs rename to src/coreclr/System.Private.CoreLib/src/System/Threading/WaitHandle.CoreCLR.Windows.cs diff --git a/src/coreclr/vm/comsynchronizable.cpp b/src/coreclr/vm/comsynchronizable.cpp index fa119716aa47a8..271e6cd2216246 100644 --- a/src/coreclr/vm/comsynchronizable.cpp +++ b/src/coreclr/vm/comsynchronizable.cpp @@ -447,6 +447,32 @@ extern "C" INT32 QCALLTYPE ThreadNative_GetThreadState(QCall::ThreadHandle threa return res; } +extern "C" void QCALLTYPE ThreadNative_SetWaitSleepJoinState(QCall::ThreadHandle thread) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + // Set the state bits. + thread->SetThreadState(Thread::TS_Interruptible); + thread->SetThreadStateNC(Thread::TSNC_DebuggerSleepWaitJoin); + + END_QCALL; +} + +extern "C" void QCALLTYPE ThreadNative_ClearWaitSleepJoinState(QCall::ThreadHandle thread) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + // Clear the state bits. + thread->ResetThreadState(Thread::TS_Interruptible); + thread->ResetThreadStateNC(Thread::TSNC_DebuggerSleepWaitJoin); + + END_QCALL; +} + #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT // Return whether the thread hosts an STA, is a member of the MTA or is not diff --git a/src/coreclr/vm/comsynchronizable.h b/src/coreclr/vm/comsynchronizable.h index 712e925adac411..81711dd2110ffc 100644 --- a/src/coreclr/vm/comsynchronizable.h +++ b/src/coreclr/vm/comsynchronizable.h @@ -55,6 +55,8 @@ extern "C" void QCALLTYPE ThreadNative_PollGC(); extern "C" UINT64 QCALLTYPE ThreadNative_GetCurrentOSThreadId(); extern "C" void QCALLTYPE ThreadNative_Initialize(QCall::ObjectHandleOnStack t); extern "C" INT32 QCALLTYPE ThreadNative_GetThreadState(QCall::ThreadHandle thread); +extern "C" void QCALLTYPE ThreadNative_SetWaitSleepJoinState(QCall::ThreadHandle thread); +extern "C" void QCALLTYPE ThreadNative_ClearWaitSleepJoinState(QCall::ThreadHandle thread); #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT extern "C" INT32 QCALLTYPE ThreadNative_GetApartmentState(QCall::ObjectHandleOnStack t); diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 578c3d1bdebe93..3a44d91c0d6444 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -289,6 +289,7 @@ static const Entry s_QCall[] = DllImportEntry(ThreadNative_GetCurrentOSThreadId) DllImportEntry(ThreadNative_Initialize) DllImportEntry(ThreadNative_GetThreadState) + DllImportEntry(ThreadNative_SetWaitSleepJoinState) #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT DllImportEntry(ThreadNative_GetApartmentState) DllImportEntry(ThreadNative_SetApartmentState) diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index 02fb634057b343..f4dad97552c315 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -907,8 +907,8 @@ class Thread // the top of the object. Also, we want cache line filling to work for us // so the critical stuff is ordered based on frequency of use. - Volatile m_State; // Bits for the state of the thread + Volatile m_State; // Bits for the state of the thread // If TRUE, GC is scheduled cooperatively with this thread. // NOTE: This "byte" is actually a boolean - we don't allow // recursive disables. diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 608899f18d5326..d5068c6af1510d 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1771,6 +1771,15 @@ Common\Interop\Windows\Kernel32\Interop.CreateFile.cs + + Common\Interop\Windows\Kernel32\Interop.EventWaitHandle.cs + + + Common\Interop\Windows\Kernel32\Interop.Mutex.cs + + + Common\Interop\Windows\Kernel32\Interop.Semaphore.cs + Interop\Windows\Kernel32\Interop.Timer.cs @@ -2229,6 +2238,7 @@ + @@ -2267,10 +2277,12 @@ + + @@ -2295,16 +2307,12 @@ - Common\Interop\Windows\Kernel32\Interop.CloseHandle.cs Common\Interop\Windows\Kernel32\Interop.Constants.cs - - Common\Interop\Windows\Kernel32\Interop.EventWaitHandle.cs - Common\Interop\Windows\Kernel32\Interop.GetEnvironmentVariable.cs @@ -2317,12 +2325,6 @@ Common\Interop\Windows\Kernel32\Interop.FormatMessage.cs - - Common\Interop\Windows\Kernel32\Interop.Mutex.cs - - - Common\Interop\Windows\Kernel32\Interop.Semaphore.cs - Common\Interop\Windows\Kernel32\Interop.SetEnvironmentVariable.cs @@ -2333,8 +2335,6 @@ Common\System\Memory\FixedBufferExtensions.cs - - @@ -2838,7 +2838,7 @@ - + @@ -2849,7 +2849,7 @@ - + diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs index 70bbc3bd2e20e7..bc401ac142d3d0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs @@ -143,7 +143,6 @@ internal bool WaitOneNoCheck( if (!usedSyncContextWait) { -#if !CORECLR // CoreCLR sends the wait events from the native side bool sendWaitEvents = millisecondsTimeout != 0 && !useTrivialWaits && @@ -178,17 +177,14 @@ internal bool WaitOneNoCheck( // When tryNonblockingWaitFirst is true, we have a final wait result from the nonblocking wait above if (!tryNonblockingWaitFirst) -#endif { waitResult = WaitOneCore(waitHandle.DangerousGetHandle(), millisecondsTimeout, useTrivialWaits); } -#if !CORECLR // CoreCLR sends the wait events from the native side if (sendWaitEvents) { NativeRuntimeEventSource.Log.WaitHandleWaitStop(); } -#endif } if (waitResult == WaitAbandoned) @@ -400,7 +396,6 @@ internal static int WaitMultipleIgnoringSyncContext(ReadOnlySpan handles { int waitResult = WaitFailed; -#if !CORECLR // CoreCLR sends the wait events from the native side bool sendWaitEvents = millisecondsTimeout != 0 && NativeRuntimeEventSource.Log.IsEnabled( @@ -432,17 +427,14 @@ internal static int WaitMultipleIgnoringSyncContext(ReadOnlySpan handles // When tryNonblockingWaitFirst is true, we have a final wait result from the nonblocking wait above if (!tryNonblockingWaitFirst) -#endif { waitResult = WaitMultipleIgnoringSyncContextCore(handles, waitAll, millisecondsTimeout); } -#if !CORECLR // CoreCLR sends the wait events from the native side if (sendWaitEvents) { NativeRuntimeEventSource.Log.WaitHandleWaitStop(); } -#endif return waitResult; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs index 56c69730c70d4f..d02f610eda9796 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs @@ -44,9 +44,6 @@ namespace System.Threading /// ## Design goals /// /// Behave similarly to wait operations on Windows - /// - The design is similar to the one used by CoreCLR's PAL, but much simpler due to there being no need for supporting - /// process/thread waits, or cross-process multi-waits (which CoreCLR also does not support but there are many design - /// elements specific to it) /// - Waiting /// - A waiter keeps an array of objects on which it is waiting (see ). /// - The waiter registers a with each From 72ad939fd7ae3873e6fc8ea6a61ba2043770f417 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 15 Jul 2025 00:04:47 +0000 Subject: [PATCH 39/47] Remove wait subsystem qcall entrypoints --- src/coreclr/vm/qcallentrypoints.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 3a44d91c0d6444..433cb61fca5597 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -486,24 +486,12 @@ static const Entry s_QCall[] = #endif #if defined(TARGET_UNIX) DllImportEntry(CloseHandle) - DllImportEntry(CreateEventExW) - DllImportEntry(CreateMutexExW) - DllImportEntry(CreateSemaphoreExW) DllImportEntry(FormatMessageW) DllImportEntry(FreeEnvironmentStringsW) DllImportEntry(GetEnvironmentStringsW) DllImportEntry(GetEnvironmentVariableW) - DllImportEntry(OpenEventW) - DllImportEntry(OpenMutexW) - DllImportEntry(OpenSemaphoreW) DllImportEntry(OutputDebugStringW) - DllImportEntry(PAL_CreateMutexW) - DllImportEntry(PAL_OpenMutexW) - DllImportEntry(ReleaseMutex) - DllImportEntry(ReleaseSemaphore) - DllImportEntry(ResetEvent) DllImportEntry(SetEnvironmentVariableW) - DllImportEntry(SetEvent) #endif #if defined(TARGET_X86) || defined(TARGET_AMD64) DllImportEntry(X86BaseCpuId) From 7cc7b2b0dbe85e2745e5928330d7ffe18904eaa9 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 15 Jul 2025 00:33:04 +0000 Subject: [PATCH 40/47] Remove named mutex support from the CoreCLR PAL --- src/coreclr/pal/inc/pal.h | 31 - src/coreclr/pal/src/CMakeLists.txt | 1 - src/coreclr/pal/src/include/pal/context.h | 1 + src/coreclr/pal/src/include/pal/corunix.hpp | 1 - src/coreclr/pal/src/include/pal/mutex.hpp | 208 -- .../pal/src/include/pal/sharedmemory.h | 317 --- .../pal/src/include/pal/synchobjects.hpp | 8 - src/coreclr/pal/src/init/pal.cpp | 79 - src/coreclr/pal/src/misc/dbgmsg.cpp | 1 + .../pal/src/sharedmemory/sharedmemory.cpp | 1745 ----------------- src/coreclr/pal/src/synchmgr/synchmanager.cpp | 114 +- src/coreclr/pal/src/synchmgr/wait.cpp | 52 - src/coreclr/pal/src/synchobj/mutex.cpp | 1460 +------------- src/coreclr/pal/src/thread/process.cpp | 2 +- src/coreclr/pal/tests/palsuite/CMakeLists.txt | 4 - .../pal/tests/palsuite/compilableTests.txt | 1 - .../pal/tests/palsuite/paltestlist.txt | 1 - .../test2/CreateMutexW.cpp | 174 -- .../threading/NamedMutex/test1/namedmutex.cpp | 1361 ------------- .../threading/NamedMutex/test1/nopal.cpp | 85 - .../WaitForMultipleObjectsEx/test6/child6.cpp | 210 -- .../WaitForMultipleObjectsEx/test6/test6.cpp | 718 ------- 22 files changed, 72 insertions(+), 6502 deletions(-) delete mode 100644 src/coreclr/pal/src/include/pal/sharedmemory.h delete mode 100644 src/coreclr/pal/src/sharedmemory/sharedmemory.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/threading/NamedMutex/test1/namedmutex.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/threading/NamedMutex/test1/nopal.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test6/child6.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test6/test6.cpp diff --git a/src/coreclr/pal/inc/pal.h b/src/coreclr/pal/inc/pal.h index 0adb251a54b68c..4105eaf082944e 100644 --- a/src/coreclr/pal/inc/pal.h +++ b/src/coreclr/pal/inc/pal.h @@ -759,42 +759,11 @@ CreateMutexExW( IN DWORD dwFlags, IN DWORD dwDesiredAccess); -PALIMPORT -HANDLE -PALAPI -PAL_CreateMutexW( - IN BOOL bInitialOwner, - IN LPCWSTR lpName, - IN BOOL bCurrentUserOnly, - IN LPSTR lpSystemCallErrors, - IN DWORD dwSystemCallErrorsBufferSize); - // CreateMutexExW: dwFlags #define CREATE_MUTEX_INITIAL_OWNER ((DWORD)0x1) #define CreateMutex CreateMutexW -PALIMPORT -HANDLE -PALAPI -OpenMutexW( - IN DWORD dwDesiredAccess, - IN BOOL bInheritHandle, - IN LPCWSTR lpName); - -PALIMPORT -HANDLE -PALAPI -PAL_OpenMutexW( - IN LPCWSTR lpName, - IN BOOL bCurrentUserOnly, - IN LPSTR lpSystemCallErrors, - IN DWORD dwSystemCallErrorsBufferSize); - -#ifdef UNICODE -#define OpenMutex OpenMutexW -#endif - PALIMPORT BOOL PALAPI diff --git a/src/coreclr/pal/src/CMakeLists.txt b/src/coreclr/pal/src/CMakeLists.txt index a850607a20c359..1123af90893cbe 100644 --- a/src/coreclr/pal/src/CMakeLists.txt +++ b/src/coreclr/pal/src/CMakeLists.txt @@ -200,7 +200,6 @@ set(SOURCES safecrt/wcsncat_s.cpp safecrt/wcsncpy_s.cpp safecrt/wmakepath_s.cpp - sharedmemory/sharedmemory.cpp synchobj/event.cpp synchobj/semaphore.cpp synchobj/mutex.cpp diff --git a/src/coreclr/pal/src/include/pal/context.h b/src/coreclr/pal/src/include/pal/context.h index 2f5310c4d66195..717d91bbe31c60 100644 --- a/src/coreclr/pal/src/include/pal/context.h +++ b/src/coreclr/pal/src/include/pal/context.h @@ -29,6 +29,7 @@ extern "C" #include #include +#include /* A type to wrap the native context type, which is ucontext_t on some * platforms and another type elsewhere. */ diff --git a/src/coreclr/pal/src/include/pal/corunix.hpp b/src/coreclr/pal/src/include/pal/corunix.hpp index 7e606980dd3434..3bb843cdfa374f 100644 --- a/src/coreclr/pal/src/include/pal/corunix.hpp +++ b/src/coreclr/pal/src/include/pal/corunix.hpp @@ -160,7 +160,6 @@ namespace CorUnix otiAutoResetEvent = 0, otiManualResetEvent, otiMutex, - otiNamedMutex, otiSemaphore, otiFile, otiFileMapping, diff --git a/src/coreclr/pal/src/include/pal/mutex.hpp b/src/coreclr/pal/src/include/pal/mutex.hpp index 9ff179ca43d180..c1f9aab7b08cb9 100644 --- a/src/coreclr/pal/src/include/pal/mutex.hpp +++ b/src/coreclr/pal/src/include/pal/mutex.hpp @@ -21,23 +21,18 @@ Module Name: #define _PAL_MUTEX_H_ #include "corunix.hpp" -#include "sharedmemory.h" #include namespace CorUnix { extern CObjectType otMutex; - extern CObjectType otNamedMutex; PAL_ERROR InternalCreateMutex( - SharedMemorySystemCallErrors *errors, CPalThread *pThread, LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, - LPCSTR lpName, - BOOL bCurrentUserOnly, HANDLE *phMutex ); @@ -46,16 +41,6 @@ namespace CorUnix CPalThread *pThread, HANDLE hMutex ); - - PAL_ERROR - InternalOpenMutex( - SharedMemorySystemCallErrors *errors, - CPalThread *pThread, - LPCSTR lpName, - BOOL bCurrentUserOnly, - HANDLE *phMutex - ); - } #define SYNCSPINLOCK_F_ASYMMETRIC 1 @@ -66,197 +51,4 @@ namespace CorUnix void SPINLOCKAcquire (LONG * lock, unsigned int flags); void SPINLOCKRelease (LONG * lock); DWORD SPINLOCKTryAcquire (LONG * lock); - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Named mutex - -/* -Design - -- On systems that support pthread process-shared robust recursive mutexes, they will be used -- On other systems, file locks are used. File locks unfortunately don't have a timeout in the blocking wait call, and I didn't - find any other sync object with a timed wait with the necessary properties, so polling is done for timed waits. - -Shared memory files -- Session-scoped mutexes (name not prefixed, or prefixed with Local) go in /tmp/.dotnet/shm/session/ -- Globally-scoped mutexes (name prefixed with Global) go in /tmp/.dotnet/shm/global/ -- Contains shared state, and is mmap'ped into the process, see SharedMemorySharedDataHeader and NamedMutexSharedData for data - stored -- Creation and deletion is synchronized using an exclusive file lock on the shm directory -- Any process using the shared memory file holds a shared file lock on the shared memory file -- Upon creation, if the shared memory file already exists, an exclusive file lock is attempted on it, to see if the file data is - valid. If no other processes have the mutex open, the file is reinitialized. -- Upon releasing the last reference to a mutex in a process, it will try to get an exclusive lock on the shared memory file to - see if any other processes have the mutex opened. If not, the file is deleted, along with the session directory if it's empty. - The .dotnet and shm directories are not deleted. -- This allows managing the lifetime of mutex state based on active processes that have the mutex open. Depending on how the - process terminated, the file may still be left over in the tmp directory, I haven't found anything that can be done about - that. - -Lock files when using file locks: -- In addition to the shared memory file, we need another file for the actual synchronization file lock, since a file lock on the - shared memory file is used for lifetime purposes. -- These files go in /tmp/.dotnet/lockfiles/session|global/ -- The file is empty, and is only used for file locks - -Process data -- See SharedMemoryProcessDataHeader and NamedMutexProcessData for data stored -- Per mutex name, there is only one instance of process data that is ref-counted. They are currently stored in a linked list in - SharedMemoryManager. It should use a hash table, but of the many hash table implementations that are already there, none seem - to be easily usable in the PAL. I'll look into that and will fix later. -- Refers to the associated shared memory, and knows how to clean up both the process data and shared data -- When using file locks for synchronization, a process-local mutex is also created for synchronizing threads, since file locks - are owned at the file descriptor level and there is only one open file descriptor in the process per mutex name. The - process-local mutex is locked around the file lock, so that only one thread per process is ever trying to flock on a given - file descriptor. - -Abandon detection -- When a lock is acquired, the process data is added to a linked list on the owning thread -- When a thread exits, the list is walked, each mutex is flagged as abandoned and released -- For detecting process abruptly terminating, pthread robust mutexes give us that. When using file locks, the file lock is - automatically released by the system. Upon acquiring a lock, the lock owner info in the shared memory is checked to see if the - mutex was abandoned. - -Miscellaneous -- CreateMutex and OpenMutex both create new handles for each mutex opened. Each handle just refers to the process data header - for the mutex name. -- Some of the above features are already available in the PAL, but not quite in a way that I can use for this purpose. The - existing shared memory, naming, and waiting infrastructure is not suitable for this purpose, and is not used. -*/ - -// - 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. -// - On OSX, pthread robust mutexes were/are not available at the time of this writing. In case they are made available in the -// future, their use is disabled for compatibility. -#if HAVE_FULLY_FEATURED_PTHREAD_MUTEXES && \ - HAVE_FUNCTIONAL_PTHREAD_ROBUST_MUTEXES && \ - !(defined(__FreeBSD__) || defined(TARGET_OSX)) - - #define NAMED_MUTEX_USE_PTHREAD_MUTEX 1 -#else - #define NAMED_MUTEX_USE_PTHREAD_MUTEX 0 -#endif - -enum class NamedMutexError : DWORD -{ - MaximumRecursiveLocksReached = ERROR_NOT_ENOUGH_MEMORY, - ThreadHasNotAcquiredMutex = ERROR_NOT_OWNER, - Unknown = ERROR_NOT_ENOUGH_MEMORY -}; - -enum class MutexTryAcquireLockResult -{ - AcquiredLock, - AcquiredLockButMutexWasAbandoned, - TimedOut -}; - -#if NAMED_MUTEX_USE_PTHREAD_MUTEX -class MutexHelpers -{ -public: - static void InitializeProcessSharedRobustRecursiveMutex(SharedMemorySystemCallErrors *errors, pthread_mutex_t *mutex); - static void DestroyMutex(pthread_mutex_t *mutex); - - static MutexTryAcquireLockResult TryAcquireLock(SharedMemorySystemCallErrors *errors, pthread_mutex_t *mutex, DWORD timeoutMilliseconds); - static void ReleaseLock(pthread_mutex_t *mutex); -}; -#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX - -class NamedMutexSharedData -{ -private: -#if NAMED_MUTEX_USE_PTHREAD_MUTEX - pthread_mutex_t m_lock; -#else // !NAMED_MUTEX_USE_PTHREAD_MUTEX - UINT32 m_timedWaiterCount; -#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX - UINT32 m_lockOwnerProcessId; - UINT64 m_lockOwnerThreadId; - bool m_isAbandoned; - -public: - NamedMutexSharedData(SharedMemorySystemCallErrors *errors); - ~NamedMutexSharedData(); - -#if NAMED_MUTEX_USE_PTHREAD_MUTEX -public: - pthread_mutex_t *GetLock(); -#else // !NAMED_MUTEX_USE_PTHREAD_MUTEX -public: - bool HasAnyTimedWaiters() const; - void IncTimedWaiterCount(); - void DecTimedWaiterCount(); -#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX - -public: - bool IsAbandoned() const; - void SetIsAbandoned(bool isAbandoned); - -public: - bool IsLockOwnedByAnyThread() const; - bool IsLockOwnedByCurrentThread() const; - void SetLockOwnerToCurrentThread(); - void ClearLockOwner(); -}; - -class NamedMutexProcessData : public SharedMemoryProcessDataBase -{ -private: - static const UINT8 SyncSystemVersion; - static const DWORD PollLoopMaximumSleepMilliseconds; - -private: - SharedMemoryProcessDataHeader *m_processDataHeader; - SIZE_T m_lockCount; -#if !NAMED_MUTEX_USE_PTHREAD_MUTEX - HANDLE m_processLockHandle; - int m_sharedLockFileDescriptor; -#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX - CorUnix::CPalThread *m_lockOwnerThread; - NamedMutexProcessData *m_nextInThreadOwnedNamedMutexList; - bool m_hasRefFromLockOwnerThread; - -public: - static SharedMemoryProcessDataHeader *CreateOrOpen(SharedMemorySystemCallErrors *errors, LPCSTR name, bool isUserScope, bool acquireLockIfCreated, bool *createdRef); - static SharedMemoryProcessDataHeader *Open(SharedMemorySystemCallErrors *errors, LPCSTR name, bool isUserScope); -private: - static SharedMemoryProcessDataHeader *CreateOrOpen(SharedMemorySystemCallErrors *errors, LPCSTR name, bool isUserScope, bool createIfNotExist, bool acquireLockIfCreated, bool *createdRef); - -public: - NamedMutexProcessData( - SharedMemoryProcessDataHeader *processDataHeader - #if !NAMED_MUTEX_USE_PTHREAD_MUTEX - , - int sharedLockFileDescriptor - #endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX - ); - -public: - virtual bool CanClose() const override; - virtual bool HasImplicitRef() const override; - virtual void SetHasImplicitRef(bool value) override; - virtual void Close(bool isAbruptShutdown, bool releaseSharedData) override; - -public: - bool IsLockOwnedByCurrentThread() const - { - return GetSharedData()->IsLockOwnedByCurrentThread(); - } - -private: - NamedMutexSharedData *GetSharedData() const; - void SetLockOwnerThread(CorUnix::CPalThread *lockOwnerThread); -public: - NamedMutexProcessData *GetNextInThreadOwnedNamedMutexList() const; - void SetNextInThreadOwnedNamedMutexList(NamedMutexProcessData *next); - -public: - MutexTryAcquireLockResult TryAcquireLock(SharedMemorySystemCallErrors *errors, DWORD timeoutMilliseconds); - void ReleaseLock(); - void Abandon(); -private: - void ActuallyReleaseLock(); -}; - #endif //_PAL_MUTEX_H_ diff --git a/src/coreclr/pal/src/include/pal/sharedmemory.h b/src/coreclr/pal/src/include/pal/sharedmemory.h deleted file mode 100644 index 84a35d2b237dd6..00000000000000 --- a/src/coreclr/pal/src/include/pal/sharedmemory.h +++ /dev/null @@ -1,317 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#ifndef _PAL_SHARED_MEMORY_H_ -#define _PAL_SHARED_MEMORY_H_ - -#include "corunix.hpp" -#include - -#ifndef static_assert_no_msg -#define static_assert_no_msg( cond ) static_assert( cond, #cond ) -#endif // !static_assert_no_msg - -// The folder used for storing shared memory files and their lock files is defined in -// the gSharedFilesPath global variable. The value of the variable depends on which -// OS is being used, and if the application is running in a sandbox in Mac. -// gSharedFilesPath ends with '/' -// - Global shared memory files go in: -// {gSharedFilesPath}/.dotnet/shm/global/ -// - Session-scoped shared memory files go in: -// {gSharedFilesPath}/.dotnet/shm/session/ -// - Lock files associated with global shared memory files go in: -// {gSharedFilesPath}/.dotnet/lockfiles/global/ -// - Lock files associated with session-scoped shared memory files go in: -// {gSharedFilesPath}/.dotnet/lockfiles/session/ - -#define SHARED_MEMORY_MAX_FILE_NAME_CHAR_COUNT (_MAX_FNAME - 1) -#define SHARED_MEMORY_MAX_NAME_CHAR_COUNT (STRING_LENGTH("Global\\") + SHARED_MEMORY_MAX_FILE_NAME_CHAR_COUNT) - -#define SHARED_MEMORY_USER_UNSCOPED_RUNTIME_TEMP_DIRECTORY_NAME ".dotnet" -#define SHARED_MEMORY_USER_SCOPED_RUNTIME_TEMP_DIRECTORY_NAME_PREFIX ".dotnet-uid" -#define SHARED_MEMORY_SHARED_MEMORY_DIRECTORY_NAME "shm" -#define SHARED_MEMORY_LOCK_FILES_DIRECTORY_NAME "lockfiles" -static_assert_no_msg(STRING_LENGTH(SHARED_MEMORY_LOCK_FILES_DIRECTORY_NAME) >= STRING_LENGTH(SHARED_MEMORY_SHARED_MEMORY_DIRECTORY_NAME)); - -#define SHARED_MEMORY_GLOBAL_DIRECTORY_NAME "global" -#define SHARED_MEMORY_SESSION_DIRECTORY_NAME_PREFIX "session" - -#define SHARED_MEMORY_UNIQUE_TEMP_NAME_TEMPLATE ".dotnet.XXXXXX" - -// Note that this Max size does not include the prefix folder path size which is unknown (in the case of sandbox) until runtime -#define SHARED_MEMORY_MAX_FILE_PATH_CHAR_COUNT \ - ( \ - STRING_LENGTH(SHARED_MEMORY_USER_SCOPED_RUNTIME_TEMP_DIRECTORY_NAME_PREFIX) + \ - 11 /* user ID, path separator */ + \ - STRING_LENGTH(SHARED_MEMORY_LOCK_FILES_DIRECTORY_NAME) + \ - 1 /* path separator */ + \ - STRING_LENGTH(SHARED_MEMORY_SESSION_DIRECTORY_NAME_PREFIX) + \ - 11 /* session ID, path separator */ + \ - SHARED_MEMORY_MAX_FILE_NAME_CHAR_COUNT \ - ) - -class AutoFreeBuffer -{ -private: - void *m_buffer; - bool m_cancel; - -public: - AutoFreeBuffer(void *buffer); - ~AutoFreeBuffer(); - -public: - void Cancel(); -}; - -enum class SharedMemoryError : DWORD -{ - NameEmpty = ERROR_INVALID_PARAMETER, - NameTooLong = ERROR_FILENAME_EXCED_RANGE, - NameInvalid = ERROR_INVALID_NAME, - HeaderMismatch = ERROR_INVALID_HANDLE, - OutOfMemory = ERROR_NOT_ENOUGH_MEMORY, - IO = ERROR_OPEN_FAILED -}; - -class SharedMemoryException -{ -private: - DWORD m_errorCode; - -public: - SharedMemoryException(DWORD errorCode); - DWORD GetErrorCode() const; -}; - -class SharedMemorySystemCallErrors -{ -private: - char *m_buffer; - int m_bufferSize; - int m_length; - bool m_isTracking; - -public: - SharedMemorySystemCallErrors(char *buffer, int bufferSize); - void Append(LPCSTR format, ...); -}; - -class SharedMemoryId; - -class SharedMemoryHelpers -{ -private: - static const mode_t PermissionsMask_OwnerUser_ReadWrite; - static const mode_t PermissionsMask_OwnerUser_ReadWriteExecute; - static const mode_t PermissionsMask_NonOwnerUsers_Write; - static const mode_t PermissionsMask_AllUsers_ReadWrite; - static const mode_t PermissionsMask_AllUsers_ReadWriteExecute; - static const mode_t PermissionsMask_Sticky; -public: - static const UINT32 InvalidProcessId; - static const SIZE_T InvalidThreadId; - static const UINT64 InvalidSharedThreadId; - -public: - static SIZE_T AlignDown(SIZE_T value, SIZE_T alignment); - static SIZE_T AlignUp(SIZE_T value, SIZE_T alignment); - - static void *Alloc(SIZE_T byteCount); - static bool AppendUInt32String(PathCharString& destination, UINT32 value); - - static bool EnsureDirectoryExists(SharedMemorySystemCallErrors *errors, const char *path, const SharedMemoryId *id, bool isGlobalLockAcquired, bool createIfNotExist = true, bool isSystemDirectory = false); -private: - static int Open(SharedMemorySystemCallErrors *errors, LPCSTR path, int flags, mode_t mode = static_cast(0)); -public: - static int OpenDirectory(SharedMemorySystemCallErrors *errors, LPCSTR path); - static int CreateOrOpenFile(SharedMemorySystemCallErrors *errors, LPCSTR path, const SharedMemoryId *id, bool createIfNotExist = true, bool *createdRef = nullptr); - static void CloseFile(int fileDescriptor); - - static int ChangeMode(LPCSTR path, mode_t mode); - - static SIZE_T GetFileSize(SharedMemorySystemCallErrors *errors, LPCSTR filePath, int fileDescriptor); - static void SetFileSize(SharedMemorySystemCallErrors *errors, LPCSTR filePath, int fileDescriptor, SIZE_T byteCount); - - static void *MemoryMapFile(SharedMemorySystemCallErrors *errors, LPCSTR filePath, int fileDescriptor, SIZE_T byteCount); - - static bool TryAcquireFileLock(SharedMemorySystemCallErrors *errors, int fileDescriptor, int operation); - static void ReleaseFileLock(int fileDescriptor); - - static void VerifyStringOperation(bool success); - static void VerifyStringOperation(BOOL success) - { - VerifyStringOperation(success != FALSE); - } -}; - -class SharedMemoryId -{ -private: - LPCSTR m_name; - SIZE_T m_nameCharCount; - bool m_isSessionScope; // false indicates global scope - bool m_isUserScope; - uid_t m_userScopeUid; - -public: - SharedMemoryId(); - SharedMemoryId(LPCSTR name, bool isUserScope); - -public: - LPCSTR GetName() const; - SIZE_T GetNameCharCount() const; - void ReplaceNamePtr(LPCSTR name); - bool IsSessionScope() const; - bool IsUserScope() const; - uid_t GetUserScopeUid() const; - bool Equals(const SharedMemoryId *other) const; - -public: - bool AppendRuntimeTempDirectoryName(PathCharString& path) const; - bool AppendSessionDirectoryName(PathCharString& path) const; -}; - -enum class SharedMemoryType : UINT8 -{ - Mutex -}; - -class SharedMemorySharedDataHeader -{ -private: - union - { - struct - { - SharedMemoryType m_type; - UINT8 m_version; - }; - UINT64 _raw; // use the same size for the header on all archs, and align the data to a pointer - }; - -public: - static SIZE_T GetUsedByteCount(SIZE_T dataByteCount); - static SIZE_T GetTotalByteCount(SIZE_T dataByteCount); - -public: - SharedMemorySharedDataHeader(SharedMemoryType type, UINT8 version); - -public: - SharedMemoryType GetType() const; - UINT8 GetVersion() const; - void *GetData(); -}; - -class SharedMemoryProcessDataBase -{ -public: - virtual bool CanClose() const = 0; - virtual bool HasImplicitRef() const = 0; - virtual void SetHasImplicitRef(bool value) = 0; - virtual void Close(bool isAbruptShutdown, bool releaseSharedData) = 0; - - virtual ~SharedMemoryProcessDataBase() - { - } -}; - -class SharedMemoryProcessDataHeader -{ -private: - SIZE_T m_refCount; - SharedMemoryId m_id; - SharedMemoryProcessDataBase *m_data; - int m_fileDescriptor; - SharedMemorySharedDataHeader *m_sharedDataHeader; - SIZE_T m_sharedDataTotalByteCount; - SharedMemoryProcessDataHeader *m_nextInProcessDataHeaderList; - -public: - static SharedMemoryProcessDataHeader *CreateOrOpen(SharedMemorySystemCallErrors *errors, LPCSTR name, bool isUserScope, SharedMemorySharedDataHeader requiredSharedDataHeader, SIZE_T sharedDataByteCount, bool createIfNotExist, bool *createdRef); - -public: - static SharedMemoryProcessDataHeader *PalObject_GetProcessDataHeader(CorUnix::IPalObject *object); - static void PalObject_SetProcessDataHeader(CorUnix::IPalObject *object, SharedMemoryProcessDataHeader *processDataHeader); - static void PalObject_Close(CorUnix::CPalThread *thread, CorUnix::IPalObject *object, bool isShuttingDown); - -private: - SharedMemoryProcessDataHeader(const SharedMemoryId *id, int fileDescriptor, SharedMemorySharedDataHeader *sharedDataHeader, SIZE_T sharedDataTotalByteCount); -public: - static SharedMemoryProcessDataHeader *New(const SharedMemoryId *id, int fileDescriptor, SharedMemorySharedDataHeader *sharedDataHeader, SIZE_T sharedDataTotalByteCount); - ~SharedMemoryProcessDataHeader(); - void Close(); - -public: - const SharedMemoryId *GetId() const; - SharedMemoryProcessDataBase *GetData() const; - void SetData(SharedMemoryProcessDataBase *data); - SharedMemorySharedDataHeader *GetSharedDataHeader() const; - SIZE_T GetSharedDataTotalByteCount() const; - SharedMemoryProcessDataHeader *GetNextInProcessDataHeaderList() const; - void SetNextInProcessDataHeaderList(SharedMemoryProcessDataHeader *next); - -public: - void IncRefCount(); - void DecRefCount(); -}; - -class SharedMemoryManager -{ -private: - static minipal_mutex s_creationDeletionProcessLock; - static int s_creationDeletionLockFileDescriptor; - - struct UserScopeUidAndFileDescriptor - { - uid_t userScopeUid; - int fileDescriptor; - - UserScopeUidAndFileDescriptor() : userScopeUid((uid_t)0), fileDescriptor(-1) - { - } - - UserScopeUidAndFileDescriptor(uid_t userScopeUid, int fileDescriptor) - : userScopeUid(userScopeUid), fileDescriptor(fileDescriptor) - { - } - }; - - static UserScopeUidAndFileDescriptor *s_userScopeUidToCreationDeletionLockFDs; - static int s_userScopeUidToCreationDeletionLockFDsCount; - static int s_userScopeUidToCreationDeletionLockFDsCapacity; - -private: - static SharedMemoryProcessDataHeader *s_processDataHeaderListHead; - -#ifdef _DEBUG -private: - static SIZE_T s_creationDeletionProcessLockOwnerThreadId; - static SIZE_T s_creationDeletionFileLockOwnerThreadId; -#endif // _DEBUG - -public: - static void StaticInitialize(); - static void StaticClose(); - -public: - static void AcquireCreationDeletionProcessLock(); - static void ReleaseCreationDeletionProcessLock(); - static void AcquireCreationDeletionFileLock(SharedMemorySystemCallErrors *errors, const SharedMemoryId *id); - static void ReleaseCreationDeletionFileLock(const SharedMemoryId *id); - static void AddUserScopeUidCreationDeletionLockFD(uid_t userScopeUid, int creationDeletionLockFD); - static int FindUserScopeCreationDeletionLockFD(uid_t userScopeUid); - -#ifdef _DEBUG -public: - static bool IsCreationDeletionProcessLockAcquired(); - static bool IsCreationDeletionFileLockAcquired(); -#endif // _DEBUG - -public: - static void AddProcessDataHeader(SharedMemoryProcessDataHeader *processDataHeader); - static void RemoveProcessDataHeader(SharedMemoryProcessDataHeader *processDataHeader); - static SharedMemoryProcessDataHeader *FindProcessDataHeader(const SharedMemoryId *id); -}; - -#endif // !_PAL_SHARED_MEMORY_H_ diff --git a/src/coreclr/pal/src/include/pal/synchobjects.hpp b/src/coreclr/pal/src/include/pal/synchobjects.hpp index 089c5bbf0ac27d..5adb4ca34d7e23 100644 --- a/src/coreclr/pal/src/include/pal/synchobjects.hpp +++ b/src/coreclr/pal/src/include/pal/synchobjects.hpp @@ -113,8 +113,6 @@ namespace CorUnix Volatile m_lLocalSynchLockCount; LIST_ENTRY m_leOwnedObjsList; - NamedMutexProcessData *m_ownedNamedMutexListHead; - ThreadNativeWaitData m_tnwdNativeData; ThreadWaitInfo m_twiWaitInfo; @@ -168,12 +166,6 @@ namespace CorUnix void RemoveObjectFromOwnedList(POwnedObjectsListNode pooln); POwnedObjectsListNode RemoveFirstObjectFromOwnedList(void); - void AddOwnedNamedMutex(NamedMutexProcessData *processData); - void RemoveOwnedNamedMutex(NamedMutexProcessData *processData); - NamedMutexProcessData *RemoveFirstOwnedNamedMutex(); - bool OwnsNamedMutex(NamedMutexProcessData *processData); - bool OwnsAnyNamedMutex() const; - // The following methods provide access to the native wait lock for // those implementations that need a lock to protect the support for // native thread blocking (e.g.: pthread conditions) diff --git a/src/coreclr/pal/src/init/pal.cpp b/src/coreclr/pal/src/init/pal.cpp index 64fffbef6e0f2e..8074b5dd60eb87 100644 --- a/src/coreclr/pal/src/init/pal.cpp +++ b/src/coreclr/pal/src/init/pal.cpp @@ -24,7 +24,6 @@ SET_DEFAULT_DEBUG_CHANNEL(PAL); // some headers have code with asserts, so do th #include "../objmgr/listedobjectmanager.hpp" #include "pal/seh.hpp" #include "pal/palinternal.h" -#include "pal/sharedmemory.h" #include "pal/process.h" #include "../thread/procprivate.hpp" #include "pal/module.h" @@ -354,20 +353,6 @@ Initialize( goto CLEANUP0a; } - // The gSharedFilesPath is allocated dynamically so its destructor does not get - // called unexpectedly during cleanup - gSharedFilesPath = new(std::nothrow) PathCharString(); - if (gSharedFilesPath == nullptr) - { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - goto CLEANUP0a; - } - - if (INIT_SharedFilesPath() == FALSE) - { - goto CLEANUP0a; - } - fFirstTimeInit = true; InitializeDefaultStackSize(); @@ -407,8 +392,6 @@ Initialize( // we use large numbers of threads or have many open files. } - SharedMemoryManager::StaticInitialize(); - // // Initialize global process data // @@ -835,8 +818,6 @@ PALCommonCleanup() // CPalSynchMgrController::PrepareForShutdown(); - SharedMemoryManager::StaticClose(); - #ifdef _DEBUG PROCDumpThreadList(); #endif @@ -1135,63 +1116,3 @@ static LPWSTR INIT_GetCurrentEXEPath() return return_value; } - -/*++ -Function: - INIT_SharedFilesPath - -Abstract: - Initializes the shared application ---*/ -static BOOL INIT_SharedFilesPath(void) -{ -#ifdef __APPLE__ - // Store application group Id. It will be null if not set - gApplicationGroupId = getenv("DOTNET_SANDBOX_APPLICATION_GROUP_ID"); - - if (nullptr != gApplicationGroupId) - { - // Verify the length of the application group ID - gApplicationGroupIdLength = strlen(gApplicationGroupId); - if (gApplicationGroupIdLength > MAX_APPLICATION_GROUP_ID_LENGTH) - { - SetLastError(ERROR_BAD_LENGTH); - return FALSE; - } - - // In sandbox, all IPC files (locks, pipes) should be written to the application group - // container. There will be no write permissions to TEMP_DIRECTORY_PATH - if (!GetApplicationContainerFolder(*gSharedFilesPath, gApplicationGroupId, gApplicationGroupIdLength)) - { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - return FALSE; - } - - // Verify the size of the path won't exceed maximum allowed size - if (gSharedFilesPath->GetCount() + SHARED_MEMORY_MAX_FILE_PATH_CHAR_COUNT + 1 /* null terminator */ > MAX_LONGPATH) - { - SetLastError(ERROR_FILENAME_EXCED_RANGE); - return FALSE; - } - - // Check if the path already exists and it's a directory - struct stat statInfo; - int statResult = stat(*gSharedFilesPath, &statInfo); - - // If the path exists, check that it's a directory - if (statResult != 0 || !(statInfo.st_mode & S_IFDIR)) - { - SetLastError(ERROR_PATH_NOT_FOUND); - return FALSE; - } - - return TRUE; - } -#endif // __APPLE__ - - // If we are here, then we are not in sandbox mode, resort to TEMP_DIRECTORY_PATH as shared files path - return gSharedFilesPath->Set(TEMP_DIRECTORY_PATH); - - // We can verify statically the non sandboxed case, since the size is known during compile time - static_assert_no_msg(STRING_LENGTH(TEMP_DIRECTORY_PATH) + SHARED_MEMORY_MAX_FILE_PATH_CHAR_COUNT + 1 /* null terminator */ <= MAX_LONGPATH); -} diff --git a/src/coreclr/pal/src/misc/dbgmsg.cpp b/src/coreclr/pal/src/misc/dbgmsg.cpp index cc1213e8e73a6d..65b0539c80c093 100644 --- a/src/coreclr/pal/src/misc/dbgmsg.cpp +++ b/src/coreclr/pal/src/misc/dbgmsg.cpp @@ -23,6 +23,7 @@ Module Name: #include "pal/cruntime.h" #include "pal/file.h" #include "pal/environ.h" +#include /* standard headers */ diff --git a/src/coreclr/pal/src/sharedmemory/sharedmemory.cpp b/src/coreclr/pal/src/sharedmemory/sharedmemory.cpp deleted file mode 100644 index 1ec85658594d5f..00000000000000 --- a/src/coreclr/pal/src/sharedmemory/sharedmemory.cpp +++ /dev/null @@ -1,1745 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#include "pal/dbgmsg.h" -SET_DEFAULT_DEBUG_CHANNEL(SHMEM); // some headers have code with asserts, so do this first - -#include "pal/sharedmemory.h" - -#include "pal/file.hpp" -#include "pal/thread.hpp" -#include "pal/virtual.h" -#include "pal/process.h" -#include "pal/utils.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -using namespace CorUnix; - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// AutoFreeBuffer - -AutoFreeBuffer::AutoFreeBuffer(void *buffer) : m_buffer(buffer), m_cancel(false) -{ -} - -AutoFreeBuffer::~AutoFreeBuffer() -{ - if (!m_cancel && m_buffer != nullptr) - { - free(m_buffer); - } -} - -void AutoFreeBuffer::Cancel() -{ - m_cancel = true; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// SharedMemoryException - -SharedMemoryException::SharedMemoryException(DWORD errorCode) : m_errorCode(errorCode) -{ -} - -DWORD SharedMemoryException::GetErrorCode() const -{ - return m_errorCode; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// SharedMemorySystemCallErrors - -SharedMemorySystemCallErrors::SharedMemorySystemCallErrors(char *buffer, int bufferSize) - : m_buffer(buffer), m_bufferSize(bufferSize), m_length(0), m_isTracking(bufferSize != 0) -{ - _ASSERTE((buffer == nullptr) == (bufferSize == 0)); - _ASSERTE(bufferSize >= 0); -} - -void SharedMemorySystemCallErrors::Append(LPCSTR format, ...) -{ - if (!m_isTracking) - { - return; - } - - char *buffer = m_buffer; - _ASSERTE(buffer != nullptr); - int bufferSize = m_bufferSize; - _ASSERTE(bufferSize != 0); - int length = m_length; - _ASSERTE(length < bufferSize); - _ASSERTE(buffer[length] == '\0'); - if (length >= bufferSize - 1) - { - return; - } - - if (length != 0) - { - length++; // the previous null terminator will be changed to a space if the append succeeds - } - - va_list args; - va_start(args, format); - int result = _vsnprintf_s(buffer + length, bufferSize - length, bufferSize - 1 - length, format, args); - va_end(args); - - if (result == 0) - { - return; - } - - if (result < 0 || result >= bufferSize - length) - { - // There's not enough space to append this error, discard the append and stop tracking - if (length == 0) - { - buffer[0] = '\0'; - } - m_isTracking = false; - return; - } - - if (length != 0) - { - buffer[length - 1] = ' '; // change the previous null terminator to a space - } - - length += result; - _ASSERTE(buffer[length] == '\0'); - m_length = length; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// SharedMemoryHelpers - -const mode_t SharedMemoryHelpers::PermissionsMask_OwnerUser_ReadWrite = S_IRUSR | S_IWUSR; -const mode_t SharedMemoryHelpers::PermissionsMask_OwnerUser_ReadWriteExecute = S_IRUSR | S_IWUSR | S_IXUSR; -const mode_t SharedMemoryHelpers::PermissionsMask_NonOwnerUsers_Write = S_IWGRP | S_IWOTH; -const mode_t SharedMemoryHelpers::PermissionsMask_AllUsers_ReadWrite = - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; -const mode_t SharedMemoryHelpers::PermissionsMask_AllUsers_ReadWriteExecute = - PermissionsMask_AllUsers_ReadWrite | (S_IXUSR | S_IXGRP | S_IXOTH); -const mode_t SharedMemoryHelpers::PermissionsMask_Sticky = S_ISVTX; -const UINT32 SharedMemoryHelpers::InvalidProcessId = static_cast(-1); -const SIZE_T SharedMemoryHelpers::InvalidThreadId = static_cast(-1); -const UINT64 SharedMemoryHelpers::InvalidSharedThreadId = static_cast(-1); - -void *SharedMemoryHelpers::Alloc(SIZE_T byteCount) -{ - void *buffer = malloc(byteCount != 0 ? byteCount : 1); - if (buffer == nullptr) - { - throw SharedMemoryException(static_cast(SharedMemoryError::OutOfMemory)); - } - return buffer; -} - -SIZE_T SharedMemoryHelpers::AlignDown(SIZE_T value, SIZE_T alignment) -{ - _ASSERTE((alignment & (alignment - 1)) == 0); // must be a power of 2 - return value & ~(alignment - 1); -} - -SIZE_T SharedMemoryHelpers::AlignUp(SIZE_T value, SIZE_T alignment) -{ - _ASSERTE((alignment & (alignment - 1)) == 0); // must be a power of 2 - return AlignDown(value + (alignment - 1), alignment); -} - -bool SharedMemoryHelpers::EnsureDirectoryExists( - SharedMemorySystemCallErrors *errors, - const char *path, - const SharedMemoryId *id, - bool isGlobalLockAcquired, - bool createIfNotExist, - bool isSystemDirectory) -{ - _ASSERTE(path != nullptr); - _ASSERTE(id != nullptr); - _ASSERTE(!(isSystemDirectory && createIfNotExist)); // should not create or change permissions on system directories - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - _ASSERTE(!isGlobalLockAcquired || SharedMemoryManager::IsCreationDeletionFileLockAcquired()); - - mode_t permissionsMask = - id->IsUserScope() ? PermissionsMask_OwnerUser_ReadWriteExecute : PermissionsMask_AllUsers_ReadWriteExecute; - - // Check if the path already exists - struct stat statInfo; - int statResult = stat(path, &statInfo); - if (statResult != 0 && errno == ENOENT) - { - if (!createIfNotExist) - { - return false; - } - - // The path does not exist, create the directory. The permissions mask passed to mkdir() is filtered by the process' - // permissions umask, so mkdir() may not set all of the requested permissions. We need to use chmod() to set the proper - // permissions. That creates a race when there is no global lock acquired when creating the directory. Another user's - // process may create the directory and this user's process may try to use it before the other process sets the full - // permissions. In that case, create a temporary directory first, set the permissions, and rename it to the actual - // directory name. - - if (isGlobalLockAcquired) - { - int operationResult = mkdir(path, permissionsMask); - if (operationResult != 0) - { - if (errors != nullptr) - { - int errorCode = errno; - errors->Append( - "mkdir(\"%s\", %s_ReadWriteExecute) == %d; errno == %s;", - path, - id->IsUserScope() ? "OwnerUser" : "AllUsers", - operationResult, - GetFriendlyErrorCodeString(errorCode)); - } - - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); - } - - operationResult = ChangeMode(path, permissionsMask); - if (operationResult != 0) - { - if (errors != nullptr) - { - int errorCode = errno; - errors->Append( - "chmod(\"%s\", %s_ReadWriteExecute) == %d; errno == %s;", - path, - id->IsUserScope() ? "OwnerUser" : "AllUsers", - operationResult, - GetFriendlyErrorCodeString(errorCode)); - } - - rmdir(path); - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); - } - - return true; - } - - PathCharString tempPath; - VerifyStringOperation(tempPath.Set(*gSharedFilesPath) && tempPath.Append(SHARED_MEMORY_UNIQUE_TEMP_NAME_TEMPLATE)); - - if (mkdtemp(tempPath.OpenStringBuffer()) == nullptr) - { - if (errors != nullptr) - { - int errorCode = errno; - errors->Append( - "mkdtemp(\"%s\") == nullptr; errno == %s;", - (const char *)tempPath, - GetFriendlyErrorCodeString(errorCode)); - } - - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); - } - - int operationResult = ChangeMode(tempPath, permissionsMask); - if (operationResult != 0) - { - if (errors != nullptr) - { - int errorCode = errno; - errors->Append( - "chmod(\"%s\", %s_ReadWriteExecute) == %d; errno == %s;", - (const char *)tempPath, - id->IsUserScope() ? "OwnerUser" : "AllUsers", - operationResult, - GetFriendlyErrorCodeString(errorCode)); - } - - rmdir(tempPath); - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); - } - - if (rename(tempPath, path) == 0) - { - return true; - } - - // Another process may have beaten us to it. Delete the temp directory and continue to check the requested directory to - // see if it meets our needs. - rmdir(tempPath); - statResult = stat(path, &statInfo); - } - - // If the path exists, check that it's a directory - if (statResult != 0 || !(statInfo.st_mode & S_IFDIR)) - { - if (errors != nullptr) - { - if (statResult != 0) - { - int errorCode = errno; - errors->Append( - "stat(\"%s\", ...) == %d; errno == %s;", - path, - statResult, - GetFriendlyErrorCodeString(errorCode)); - } - else - { - errors->Append( - "stat(\"%s\", &info) == 0; info.st_mode == 0x%x; (info.st_mode & 0x%x) == 0;", - path, - (int)statInfo.st_mode, - (int)S_IFDIR); - } - } - - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); - } - - if (isSystemDirectory) - { - // For system directories (such as TEMP_DIRECTORY_PATH), require sufficient permissions only for the - // owner user. For instance, "docker run --mount ..." to mount /tmp to some directory on the host mounts the - // destination directory with the same permissions as the source directory, which may not include some permissions for - // other users. In the docker container, other user permissions are typically not relevant and relaxing the permissions - // requirement allows for that scenario to work without having to work around it by first giving sufficient permissions - // for all users. - // - // If the directory is being used for user-scoped shared memory data, also ensure that either it has the sticky bit or - // it's owned by the current user and without write access for other users. - permissionsMask = PermissionsMask_OwnerUser_ReadWriteExecute; - if ((statInfo.st_mode & permissionsMask) == permissionsMask && - ( - !id->IsUserScope() || - statInfo.st_mode & PermissionsMask_Sticky || - (statInfo.st_uid == id->GetUserScopeUid() && !(statInfo.st_mode & PermissionsMask_NonOwnerUsers_Write)) - )) - { - return true; - } - - if (errors != nullptr) - { - errors->Append( - "stat(\"%s\", &info) == 0; info.st_mode == 0x%x; info.st_uid == %u; info.st_mode || info.st_uid;", - path, - (int)statInfo.st_mode, - (int)statInfo.st_uid); - } - - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); - } - - // For non-system directories (such as gSharedFilesPath/SHARED_MEMORY_USER_UNSCOPED_RUNTIME_TEMP_DIRECTORY_NAME), - // require the sufficient permissions and try to update them if requested to create the directory, so that - // shared memory files may be shared according to its scope. - - // For user-scoped directories, verify the owner UID - if (id->IsUserScope() && statInfo.st_uid != id->GetUserScopeUid()) - { - if (errors != nullptr) - { - errors->Append( - "stat(\"%s\", &info) == 0; info.st_uid == %u; info.st_uid != %u;", - path, - (int)statInfo.st_uid, - (int)id->GetUserScopeUid()); - } - - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); - } - - // Verify the permissions, or try to change them if possible - if ((statInfo.st_mode & PermissionsMask_AllUsers_ReadWriteExecute) == permissionsMask || - (createIfNotExist && ChangeMode(path, permissionsMask) == 0)) - { - return true; - } - - // We were not able to verify or set the necessary permissions. For user-scoped directories, this is treated as a failure - // since other users aren't sufficiently restricted in permissions. - if (id->IsUserScope()) - { - if (errors != nullptr) - { - errors->Append( - "stat(\"%s\", &info) == 0; info.st_mode == 0x%x; (info.st_mode & AllUsers_ReadWriteExecute) != OwnerUser_ReadWriteExecute;", - path, - (int)statInfo.st_mode); - } - - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); - } - - // For user-unscoped directories, as a last resort, check that at least the owner user has full access. - permissionsMask = PermissionsMask_OwnerUser_ReadWriteExecute; - if ((statInfo.st_mode & permissionsMask) != permissionsMask) - { - if (errors != nullptr) - { - errors->Append( - "stat(\"%s\", &info) == 0; info.st_mode == 0x%x; (info.st_mode & OwnerUser_ReadWriteExecute) != OwnerUser_ReadWriteExecute;", - path, - (int)statInfo.st_mode); - } - - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); - } - - return true; -} - -int SharedMemoryHelpers::Open(SharedMemorySystemCallErrors *errors, LPCSTR path, int flags, mode_t mode) -{ - int openErrorCode; - - flags |= O_CLOEXEC; - do - { - int fileDescriptor = InternalOpen(path, flags, mode); - if (fileDescriptor != -1) - { - return fileDescriptor; - } - openErrorCode = errno; - } while (openErrorCode == EINTR); - - SharedMemoryError sharedMemoryError; - switch (openErrorCode) - { - case ENOENT: - _ASSERTE(!(flags & O_CREAT)); - errno = openErrorCode; - return -1; - - case ENAMETOOLONG: - sharedMemoryError = SharedMemoryError::NameTooLong; - break; - - case EMFILE: - case ENFILE: - case ENOMEM: - sharedMemoryError = SharedMemoryError::OutOfMemory; - break; - - default: - sharedMemoryError = SharedMemoryError::IO; - break; - } - - if (sharedMemoryError != SharedMemoryError::NameTooLong && errors != nullptr) - { - errors->Append( - "open(\"%s\", 0x%x, 0x%x) == -1; errno == %s;", - path, - flags, - (int)mode, - GetFriendlyErrorCodeString(openErrorCode)); - } - - throw SharedMemoryException(static_cast(sharedMemoryError)); -} - -int SharedMemoryHelpers::OpenDirectory(SharedMemorySystemCallErrors *errors, LPCSTR path) -{ - _ASSERTE(path != nullptr); - _ASSERTE(path[0] != '\0'); - - int fileDescriptor = Open(errors, path, O_RDONLY); - _ASSERTE(fileDescriptor != -1 || errno == ENOENT); - return fileDescriptor; -} - -int SharedMemoryHelpers::CreateOrOpenFile( - SharedMemorySystemCallErrors *errors, - LPCSTR path, - const SharedMemoryId *id, - bool createIfNotExist, - bool *createdRef) -{ - _ASSERTE(path != nullptr); - _ASSERTE(path[0] != '\0'); - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - _ASSERTE(!createIfNotExist || SharedMemoryManager::IsCreationDeletionFileLockAcquired()); - - // Try to open the file - int openFlags = O_RDWR; - int fileDescriptor = Open(errors, path, openFlags); - if (fileDescriptor != -1) - { - // For user-scoped files, verify the owner UID and permissions - if (id->IsUserScope()) - { - struct stat statInfo; - int statResult = fstat(fileDescriptor, &statInfo); - if (statResult != 0) - { - if (errors != nullptr) - { - int errorCode = errno; - errors->Append( - "fstat(\"%s\", ...) == %d; errno == %s;", - path, - statResult, - GetFriendlyErrorCodeString(errorCode)); - } - - CloseFile(fileDescriptor); - throw SharedMemoryException((DWORD)SharedMemoryError::IO); - } - - if (statInfo.st_uid != id->GetUserScopeUid()) - { - if (errors != nullptr) - { - errors->Append( - "fstat(\"%s\", &info) == 0; info.st_uid == %u; info.st_uid != %u;", - path, - (int)statInfo.st_uid, - (int)id->GetUserScopeUid()); - } - - CloseFile(fileDescriptor); - throw SharedMemoryException((DWORD)SharedMemoryError::IO); - } - - if ((statInfo.st_mode & PermissionsMask_AllUsers_ReadWriteExecute) != PermissionsMask_OwnerUser_ReadWrite) - { - if (errors != nullptr) - { - errors->Append( - "fstat(\"%s\", &info) == 0; info.st_mode == 0x%x; (info.st_mode & AllUsers_ReadWriteExecute) != OwnerUser_ReadWrite;", - path, - (int)statInfo.st_mode); - } - - CloseFile(fileDescriptor); - throw SharedMemoryException((DWORD)SharedMemoryError::IO); - } - } - - if (createdRef != nullptr) - { - *createdRef = false; - } - return fileDescriptor; - } - - _ASSERTE(errno == ENOENT); - if (!createIfNotExist) - { - if (createdRef != nullptr) - { - *createdRef = false; - } - return -1; - } - - // File does not exist, create the file - openFlags |= O_CREAT | O_EXCL; - mode_t permissionsMask = id->IsUserScope() ? PermissionsMask_OwnerUser_ReadWrite : PermissionsMask_AllUsers_ReadWrite; - fileDescriptor = Open(errors, path, openFlags, permissionsMask); - _ASSERTE(fileDescriptor != -1); - - // The permissions mask passed to open() is filtered by the process' permissions umask, so open() may not set all of - // the requested permissions. Use chmod() to set the proper permissions. - int operationResult = ChangeMode(path, permissionsMask); - if (operationResult != 0) - { - if (errors != nullptr) - { - int errorCode = errno; - errors->Append( - "chmod(\"%s\", %s_ReadWrite) == %d; errno == %s;", - path, - id->IsUserScope() ? "OwnerUser" : "AllUsers", - operationResult, - GetFriendlyErrorCodeString(errorCode)); - } - - CloseFile(fileDescriptor); - unlink(path); - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); - } - - if (createdRef != nullptr) - { - *createdRef = true; - } - return fileDescriptor; -} - -void SharedMemoryHelpers::CloseFile(int fileDescriptor) -{ - _ASSERTE(fileDescriptor != -1); - close(fileDescriptor); -} - -int SharedMemoryHelpers::ChangeMode(LPCSTR path, mode_t mode) -{ - _ASSERTE(path != nullptr); - _ASSERTE(path[0] != '\0'); - - int chmodResult; - do - { - chmodResult = chmod(path, mode); - } while (chmodResult != 0 && errno == EINTR); - - return chmodResult; -} - -SIZE_T SharedMemoryHelpers::GetFileSize(SharedMemorySystemCallErrors *errors, LPCSTR filePath, int fileDescriptor) -{ - _ASSERTE(filePath != nullptr); - _ASSERTE(filePath[0] != '\0'); - _ASSERTE(fileDescriptor != -1); - - off_t endOffset = lseek(fileDescriptor, 0, SEEK_END); - if (endOffset == static_cast(-1) || - lseek(fileDescriptor, 0, SEEK_SET) == static_cast(-1)) - { - if (errors != nullptr) - { - int errorCode = errno; - errors->Append( - "lseek(\"%s\", 0, %s) == -1; errno == %s;", - filePath, - endOffset == (off_t)-1 ? "SEEK_END" : "SEEK_SET", - GetFriendlyErrorCodeString(errorCode)); - } - - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); - } - - return endOffset; -} - -void SharedMemoryHelpers::SetFileSize( - SharedMemorySystemCallErrors *errors, - LPCSTR filePath, - int fileDescriptor, - SIZE_T byteCount) -{ - _ASSERTE(filePath != nullptr); - _ASSERTE(filePath[0] != '\0'); - _ASSERTE(fileDescriptor != -1); - _ASSERTE(static_cast(byteCount) == byteCount); - - while (true) - { - int ftruncateResult = ftruncate(fileDescriptor, static_cast(byteCount)); - if (ftruncateResult == 0) - { - break; - } - - int errorCode = errno; - if (errorCode != EINTR) - { - if (errors != nullptr) - { - errors->Append( - "ftruncate(\"%s\", %zu) == %d; errno == %s;", - filePath, - byteCount, - ftruncateResult, - GetFriendlyErrorCodeString(errorCode)); - } - - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); - } - } -} - -void *SharedMemoryHelpers::MemoryMapFile( - SharedMemorySystemCallErrors *errors, - LPCSTR filePath, - int fileDescriptor, - SIZE_T byteCount) -{ - _ASSERTE(filePath != nullptr); - _ASSERTE(filePath[0] != '\0'); - _ASSERTE(fileDescriptor != -1); - _ASSERTE(byteCount > sizeof(SharedMemorySharedDataHeader)); - _ASSERTE(AlignDown(byteCount, GetVirtualPageSize()) == byteCount); - - void *sharedMemoryBuffer = mmap(nullptr, byteCount, PROT_READ | PROT_WRITE, MAP_SHARED, fileDescriptor, 0); - if (sharedMemoryBuffer != MAP_FAILED) - { - return sharedMemoryBuffer; - } - - int errorCode = errno; - SharedMemoryError sharedMemoryError; - switch (errorCode) - { - case EMFILE: - case ENFILE: - case ENOMEM: - sharedMemoryError = SharedMemoryError::OutOfMemory; - break; - - default: - sharedMemoryError = SharedMemoryError::IO; - break; - } - - if (errors != nullptr) - { - errors->Append( - "mmap(nullptr, %zu, PROT_READ | PROT_WRITE, MAP_SHARED, \"%s\", 0) == MAP_FAILED; errno == %s;", - byteCount, - filePath, - GetFriendlyErrorCodeString(errorCode)); - } - - throw SharedMemoryException(static_cast(sharedMemoryError)); -} - -bool SharedMemoryHelpers::TryAcquireFileLock(SharedMemorySystemCallErrors *errors, int fileDescriptor, int operation) -{ - // A file lock is acquired once per file descriptor, so the caller will need to synchronize threads of this process - - _ASSERTE(fileDescriptor != -1); - _ASSERTE((operation & LOCK_EX) ^ (operation & LOCK_SH)); - _ASSERTE(!(operation & LOCK_UN)); - - while (true) - { - int flockResult = flock(fileDescriptor, operation); - if (flockResult == 0) - { - return true; - } - - int flockError = errno; - SharedMemoryError sharedMemoryError = SharedMemoryError::IO; - switch (flockError) - { - case EWOULDBLOCK: - return false; - - case EINTR: - continue; - - case ENOLCK: - sharedMemoryError = SharedMemoryError::OutOfMemory; - break; - } - - if (errors != nullptr) - { - errors->Append( - "flock(%d, %s%s) == %d; errno == %s;", - fileDescriptor, - operation & LOCK_EX ? "LOCK_EX" : "LOCK_SH", - operation & LOCK_NB ? " | LOCK_NB" : "", - flockResult, - GetFriendlyErrorCodeString(flockError)); - } - - throw SharedMemoryException(static_cast(sharedMemoryError)); - } -} - -void SharedMemoryHelpers::ReleaseFileLock(int fileDescriptor) -{ - _ASSERTE(fileDescriptor != -1); - - int flockResult; - do - { - flockResult = flock(fileDescriptor, LOCK_UN); - } while (flockResult != 0 && errno == EINTR); -} - -bool SharedMemoryHelpers::AppendUInt32String( - PathCharString& destination, - UINT32 value) -{ - char int32String[16]; - - int valueCharCount = - sprintf_s(int32String, sizeof(int32String), "%u", value); - _ASSERTE(valueCharCount > 0); - return destination.Append(int32String, valueCharCount) != FALSE; -} - -void SharedMemoryHelpers::VerifyStringOperation(bool success) -{ - if (!success) - { - throw SharedMemoryException(static_cast(SharedMemoryError::OutOfMemory)); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// SharedMemoryId - -SharedMemoryId::SharedMemoryId() - : m_name(nullptr), m_nameCharCount(0), m_isSessionScope(false), m_isUserScope(false), m_userScopeUid((uid_t)0) -{ -} - -SharedMemoryId::SharedMemoryId(LPCSTR name, bool isUserScope) -{ - _ASSERTE(name != nullptr); - - // Look for "Global\" and "Local\" prefixes in the name, and determine the session ID - if (strncmp(name, "Global\\", 7) == 0) - { - m_isSessionScope = false; - name += STRING_LENGTH("Global\\"); - } - else - { - if (strncmp(name, "Local\\", 6) == 0) - { - name += STRING_LENGTH("Local\\"); - } - m_isSessionScope = true; - } - m_name = name; - - m_nameCharCount = strlen(name); - if (m_nameCharCount == 0) - { - throw SharedMemoryException(static_cast(SharedMemoryError::NameEmpty)); - } - if (m_nameCharCount > SHARED_MEMORY_MAX_FILE_NAME_CHAR_COUNT) - { - throw SharedMemoryException(static_cast(SharedMemoryError::NameTooLong)); - } - - // Look for invalid characters '\' and '/' in the name - for (SIZE_T i = 0; i < m_nameCharCount; ++i) - { - char c = name[i]; - if (c == '\\' || c == '/') - { - throw SharedMemoryException(static_cast(SharedMemoryError::NameInvalid)); - } - } - - m_isUserScope = isUserScope; - m_userScopeUid = isUserScope ? geteuid() : (uid_t)0; - - // The uid_t is converted to UINT32 to create a directory name, verify that it's valid - static_assert_no_msg(sizeof(uid_t) <= sizeof(UINT32)); - if ((uid_t)(UINT32)m_userScopeUid != m_userScopeUid) - { - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); - } -} - -LPCSTR SharedMemoryId::GetName() const -{ - _ASSERTE(m_name != nullptr); - return m_name; -} - -SIZE_T SharedMemoryId::GetNameCharCount() const -{ - _ASSERTE(m_name != nullptr); - return m_nameCharCount; -} - -void SharedMemoryId::ReplaceNamePtr(LPCSTR name) -{ - _ASSERTE(name != nullptr); - _ASSERTE(m_nameCharCount != 0); - _ASSERTE(strlen(name) == m_nameCharCount); - - m_name = name; -} - -bool SharedMemoryId::IsSessionScope() const -{ - _ASSERTE(m_name != nullptr); - return m_isSessionScope; -} - -bool SharedMemoryId::IsUserScope() const -{ - _ASSERTE(m_name != nullptr); - return m_isUserScope; -} - -uid_t SharedMemoryId::GetUserScopeUid() const -{ - _ASSERTE(m_name != nullptr); - _ASSERTE(m_isUserScope); - return m_userScopeUid; -} - -bool SharedMemoryId::Equals(const SharedMemoryId *other) const -{ - return - GetNameCharCount() == other->GetNameCharCount() && - IsSessionScope() == other->IsSessionScope() && - IsUserScope() == other->IsUserScope() && - (!IsUserScope() || GetUserScopeUid() == other->GetUserScopeUid()) && - strcmp(GetName(), other->GetName()) == 0; -} - -bool SharedMemoryId::AppendRuntimeTempDirectoryName(PathCharString& path) const -{ - if (IsUserScope()) - { - return - path.Append(SHARED_MEMORY_USER_SCOPED_RUNTIME_TEMP_DIRECTORY_NAME_PREFIX) && - SharedMemoryHelpers::AppendUInt32String(path, (UINT32)GetUserScopeUid()); - } - - return path.Append(SHARED_MEMORY_USER_UNSCOPED_RUNTIME_TEMP_DIRECTORY_NAME); -} - -bool SharedMemoryId::AppendSessionDirectoryName(PathCharString& path) const -{ - if (IsSessionScope()) - { - return path.Append(SHARED_MEMORY_SESSION_DIRECTORY_NAME_PREFIX) != FALSE - && SharedMemoryHelpers::AppendUInt32String(path, GetCurrentSessionId()); - } - else - { - return path.Append(SHARED_MEMORY_GLOBAL_DIRECTORY_NAME) != FALSE; - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// SharedMemorySharedDataHeader - -SIZE_T SharedMemorySharedDataHeader::GetUsedByteCount(SIZE_T dataByteCount) -{ - return sizeof(SharedMemorySharedDataHeader) + dataByteCount; -} - -SIZE_T SharedMemorySharedDataHeader::GetTotalByteCount(SIZE_T dataByteCount) -{ - return SharedMemoryHelpers::AlignUp(GetUsedByteCount(dataByteCount), GetVirtualPageSize()); -} - -SharedMemorySharedDataHeader::SharedMemorySharedDataHeader(SharedMemoryType type, UINT8 version) - : m_type(type), m_version(version) -{ -} - -SharedMemoryType SharedMemorySharedDataHeader::GetType() const -{ - return m_type; -} - -UINT8 SharedMemorySharedDataHeader::GetVersion() const -{ - return m_version; -} - -void *SharedMemorySharedDataHeader::GetData() -{ - return this + 1; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// SharedMemoryProcessDataHeader - -SharedMemoryProcessDataHeader *SharedMemoryProcessDataHeader::CreateOrOpen( - SharedMemorySystemCallErrors *errors, - LPCSTR name, - bool isUserScope, - SharedMemorySharedDataHeader requiredSharedDataHeader, - SIZE_T sharedDataByteCount, - bool createIfNotExist, - bool *createdRef) -{ - _ASSERTE(name != nullptr); - _ASSERTE(sharedDataByteCount != 0); - _ASSERTE(!createIfNotExist || createdRef != nullptr); - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - _ASSERTE(!SharedMemoryManager::IsCreationDeletionFileLockAcquired()); - - if (createdRef != nullptr) - { - *createdRef = false; - } - - PathCharString filePath; - SharedMemoryId id(name, isUserScope); - - struct AutoCleanup - { - const SharedMemoryId *m_acquiredCreationDeletionFileLockForId; - PathCharString *m_filePath; - SIZE_T m_sessionDirectoryPathCharCount; - bool m_createdFile; - int m_fileDescriptor; - bool m_acquiredFileLock; - void *m_mappedBuffer; - SIZE_T m_mappedBufferByteCount; - bool m_cancel; - - AutoCleanup() - : m_acquiredCreationDeletionFileLockForId(nullptr), - m_filePath(nullptr), - m_sessionDirectoryPathCharCount(0), - m_createdFile(false), - m_fileDescriptor(-1), - m_acquiredFileLock(false), - m_mappedBuffer(nullptr), - m_mappedBufferByteCount(0), - m_cancel(false) - { - } - - ~AutoCleanup() - { - if (m_cancel) - { - return; - } - - if (m_mappedBuffer != nullptr) - { - _ASSERTE(m_mappedBufferByteCount != 0); - munmap(m_mappedBuffer, m_mappedBufferByteCount); - } - - if (m_acquiredFileLock) - { - _ASSERTE(m_fileDescriptor != -1); - SharedMemoryHelpers::ReleaseFileLock(m_fileDescriptor); - } - - if (m_fileDescriptor != -1) - { - SharedMemoryHelpers::CloseFile(m_fileDescriptor); - } - - if (m_createdFile) - { - _ASSERTE(m_filePath != nullptr); - unlink(*m_filePath); - } - - if (m_sessionDirectoryPathCharCount != 0) - { - _ASSERTE(*m_filePath != nullptr); - m_filePath->CloseBuffer(m_sessionDirectoryPathCharCount); - rmdir(*m_filePath); - } - - if (m_acquiredCreationDeletionFileLockForId != nullptr) - { - SharedMemoryManager::ReleaseCreationDeletionFileLock(m_acquiredCreationDeletionFileLockForId); - } - } - } autoCleanup; - - SharedMemoryProcessDataHeader *processDataHeader = SharedMemoryManager::FindProcessDataHeader(&id); - if (processDataHeader != nullptr) - { - _ASSERTE( - processDataHeader->GetSharedDataTotalByteCount() == - SharedMemorySharedDataHeader::GetTotalByteCount(sharedDataByteCount)); - processDataHeader->IncRefCount(); - return processDataHeader; - } - - SharedMemoryManager::AcquireCreationDeletionFileLock(errors, &id); - autoCleanup.m_acquiredCreationDeletionFileLockForId = &id; - - // Create the session directory - SharedMemoryHelpers::VerifyStringOperation( - filePath.Set(*gSharedFilesPath) && - id.AppendRuntimeTempDirectoryName(filePath) && - filePath.Append('/') && filePath.Append(SHARED_MEMORY_SHARED_MEMORY_DIRECTORY_NAME) && - filePath.Append('/') && id.AppendSessionDirectoryName(filePath)); - if (!SharedMemoryHelpers::EnsureDirectoryExists(errors, filePath, &id, true /* isGlobalLockAcquired */, createIfNotExist)) - { - _ASSERTE(!createIfNotExist); - return nullptr; - } - autoCleanup.m_filePath = &filePath; - autoCleanup.m_sessionDirectoryPathCharCount = filePath.GetCount(); - - // Create or open the shared memory file - SharedMemoryHelpers::VerifyStringOperation(filePath.Append('/') && filePath.Append(id.GetName(), id.GetNameCharCount())); - bool createdFile; - int fileDescriptor = SharedMemoryHelpers::CreateOrOpenFile(errors, filePath, &id, createIfNotExist, &createdFile); - if (fileDescriptor == -1) - { - _ASSERTE(!createIfNotExist); - return nullptr; - } - autoCleanup.m_createdFile = createdFile; - autoCleanup.m_fileDescriptor = fileDescriptor; - - bool clearContents = false; - if (!createdFile) - { - // A shared file lock on the shared memory file would be held by any process that has opened the same file. Try to take - // an exclusive lock on the file. Successfully acquiring an exclusive lock indicates that no process has a reference to - // the shared memory file, and this process can reinitialize its contents. - if (SharedMemoryHelpers::TryAcquireFileLock(errors, fileDescriptor, LOCK_EX | LOCK_NB)) - { - // The shared memory file is not being used, flag it as created so that its contents will be reinitialized - SharedMemoryHelpers::ReleaseFileLock(fileDescriptor); - autoCleanup.m_createdFile = true; - if (!createIfNotExist) - { - return nullptr; - } - createdFile = true; - clearContents = true; - } - } - - // Set or validate the file length - SIZE_T sharedDataUsedByteCount = SharedMemorySharedDataHeader::GetUsedByteCount(sharedDataByteCount); - SIZE_T sharedDataTotalByteCount = SharedMemorySharedDataHeader::GetTotalByteCount(sharedDataByteCount); - if (createdFile) - { - SharedMemoryHelpers::SetFileSize(errors, filePath, fileDescriptor, sharedDataTotalByteCount); - } - else - { - SIZE_T currentFileSize = SharedMemoryHelpers::GetFileSize(errors, filePath, fileDescriptor); - if (currentFileSize < sharedDataUsedByteCount) - { - throw SharedMemoryException(static_cast(SharedMemoryError::HeaderMismatch)); - } - if (currentFileSize < sharedDataTotalByteCount) - { - SharedMemoryHelpers::SetFileSize(errors, filePath, fileDescriptor, sharedDataTotalByteCount); - } - } - - // Acquire and hold a shared file lock on the shared memory file as long as it is open, to indicate that this process is - // using the file. An exclusive file lock is attempted above to detect whether the file contents are valid, for the case - // where a process crashes or is killed after the file is created. Since we already hold the creation/deletion locks, a - // non-blocking file lock should succeed. - if (!SharedMemoryHelpers::TryAcquireFileLock(errors, fileDescriptor, LOCK_SH | LOCK_NB)) - { - if (errors != nullptr) - { - int errorCode = errno; - errors->Append( - "flock(\"%s\", LOCK_SH | LOCK_NB) == -1; errno == %s;", - (const char *)filePath, - GetFriendlyErrorCodeString(errorCode)); - } - - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); - } - autoCleanup.m_acquiredFileLock = true; - - // Map the file into memory, and initialize or validate the header - void *mappedBuffer = SharedMemoryHelpers::MemoryMapFile(errors, filePath, fileDescriptor, sharedDataTotalByteCount); - autoCleanup.m_mappedBuffer = mappedBuffer; - autoCleanup.m_mappedBufferByteCount = sharedDataTotalByteCount; - SharedMemorySharedDataHeader *sharedDataHeader; - if (createdFile) - { - if (clearContents) - { - memset(mappedBuffer, 0, sharedDataUsedByteCount); - } - sharedDataHeader = new(mappedBuffer) SharedMemorySharedDataHeader(requiredSharedDataHeader); - } - else - { - sharedDataHeader = reinterpret_cast(mappedBuffer); - if (sharedDataHeader->GetType() != requiredSharedDataHeader.GetType() || - sharedDataHeader->GetVersion() != requiredSharedDataHeader.GetVersion()) - { - throw SharedMemoryException(static_cast(SharedMemoryError::HeaderMismatch)); - } - } - - // When *createdRef is true, the creation/deletion file lock will remain locked upon returning for the caller to initialize - // the shared data. The caller must release the file lock afterwards. - if (!createdFile) - { - autoCleanup.m_acquiredCreationDeletionFileLockForId = nullptr; - SharedMemoryManager::ReleaseCreationDeletionFileLock(&id); - } - - processDataHeader = SharedMemoryProcessDataHeader::New(&id, fileDescriptor, sharedDataHeader, sharedDataTotalByteCount); - - autoCleanup.m_cancel = true; - if (createdFile) - { - _ASSERTE(createIfNotExist); - _ASSERTE(createdRef != nullptr); - *createdRef = true; - } - return processDataHeader; -} - -SharedMemoryProcessDataHeader *SharedMemoryProcessDataHeader::PalObject_GetProcessDataHeader(CorUnix::IPalObject *object) -{ - _ASSERTE(object != nullptr); - _ASSERTE(object->GetObjectType()->GetId() == otiNamedMutex); - _ASSERTE(object->GetObjectType()->GetImmutableDataSize() == sizeof(SharedMemoryProcessDataHeader *)); - - void *immutableDataBuffer; - PAL_ERROR errorCode = object->GetImmutableData(&immutableDataBuffer); - _ASSERTE(errorCode == NO_ERROR); - _ASSERTE(immutableDataBuffer != nullptr); - return *reinterpret_cast(immutableDataBuffer); -} - -void SharedMemoryProcessDataHeader::PalObject_SetProcessDataHeader( - CorUnix::IPalObject *object, - SharedMemoryProcessDataHeader *processDataHeader) -{ - _ASSERTE(object != nullptr); - _ASSERTE(object->GetObjectType()->GetId() == otiNamedMutex); - _ASSERTE(object->GetObjectType()->GetImmutableDataSize() == sizeof(SharedMemoryProcessDataHeader *)); - _ASSERTE(processDataHeader != nullptr); - - void *immutableDataBuffer; - PAL_ERROR errorCode = object->GetImmutableData(&immutableDataBuffer); - _ASSERTE(errorCode == NO_ERROR); - _ASSERTE(immutableDataBuffer != nullptr); - *reinterpret_cast(immutableDataBuffer) = processDataHeader; -} - -void SharedMemoryProcessDataHeader::PalObject_Close( - CPalThread *thread, - IPalObject *object, - bool isShuttingDown) -{ - // This function's signature matches OBJECTCLEANUPROUTINE - _ASSERTE(thread != nullptr); - _ASSERTE(object != nullptr); - _ASSERTE(object->GetObjectType()->GetId() == otiNamedMutex); - _ASSERTE(object->GetObjectType()->GetImmutableDataSize() == sizeof(SharedMemoryProcessDataHeader *)); - - SharedMemoryProcessDataHeader *processDataHeader = PalObject_GetProcessDataHeader(object); - if (processDataHeader == nullptr) - { - // The object was created, but an error must have occurred before the process data was initialized - return; - } - - SharedMemoryManager::AcquireCreationDeletionProcessLock(); - processDataHeader->DecRefCount(); - SharedMemoryManager::ReleaseCreationDeletionProcessLock(); -} - -SharedMemoryProcessDataHeader::SharedMemoryProcessDataHeader( - const SharedMemoryId *id, - int fileDescriptor, - SharedMemorySharedDataHeader *sharedDataHeader, - SIZE_T sharedDataTotalByteCount) - : - m_refCount(1), - m_id(*id), - m_data(nullptr), - m_fileDescriptor(fileDescriptor), - m_sharedDataHeader(sharedDataHeader), - m_sharedDataTotalByteCount(sharedDataTotalByteCount), - m_nextInProcessDataHeaderList(nullptr) -{ - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - _ASSERTE(id != nullptr); - _ASSERTE(fileDescriptor != -1); - _ASSERTE(sharedDataHeader != nullptr); - _ASSERTE(sharedDataTotalByteCount > sizeof(SharedMemorySharedDataHeader)); - _ASSERTE(SharedMemoryHelpers::AlignDown(sharedDataTotalByteCount, GetVirtualPageSize()) == sharedDataTotalByteCount); - - // Copy the name and initialize the ID - char *nameCopy = reinterpret_cast(this + 1); - SIZE_T nameByteCount = id->GetNameCharCount() + 1; - memcpy_s(nameCopy, nameByteCount, id->GetName(), nameByteCount); - m_id.ReplaceNamePtr(nameCopy); - - SharedMemoryManager::AddProcessDataHeader(this); -} - -SharedMemoryProcessDataHeader *SharedMemoryProcessDataHeader::New( - const SharedMemoryId *id, - int fileDescriptor, - SharedMemorySharedDataHeader *sharedDataHeader, - SIZE_T sharedDataTotalByteCount) -{ - _ASSERTE(id != nullptr); - - // Allocate space for the header and a copy of the name - SIZE_T nameByteCount = id->GetNameCharCount() + 1; - SIZE_T totalByteCount = sizeof(SharedMemoryProcessDataHeader) + nameByteCount; - void *buffer = SharedMemoryHelpers::Alloc(totalByteCount); - AutoFreeBuffer autoFreeBuffer(buffer); - SharedMemoryProcessDataHeader *processDataHeader = - new(buffer) SharedMemoryProcessDataHeader(id, fileDescriptor, sharedDataHeader, sharedDataTotalByteCount); - autoFreeBuffer.Cancel(); - return processDataHeader; -} - -SharedMemoryProcessDataHeader::~SharedMemoryProcessDataHeader() -{ - _ASSERTE(m_refCount == 0); - Close(); -} - -void SharedMemoryProcessDataHeader::Close() -{ - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - _ASSERTE(!SharedMemoryManager::IsCreationDeletionFileLockAcquired()); - - // If the ref count is nonzero, we are shutting down the process abruptly without having closed some shared memory objects. - // There could still be threads running with active references to the shared memory object. So when the ref count is - // nonzero, don't clean up any object or global process-local state. - if (m_refCount == 0) - { - _ASSERTE(m_data == nullptr || m_data->CanClose()); - SharedMemoryManager::RemoveProcessDataHeader(this); - } - - struct AutoReleaseCreationDeletionFileLock - { - const SharedMemoryId *m_acquiredForId; - - AutoReleaseCreationDeletionFileLock() : m_acquiredForId(nullptr) - { - } - - ~AutoReleaseCreationDeletionFileLock() - { - if (m_acquiredForId != nullptr) - { - SharedMemoryManager::ReleaseCreationDeletionFileLock(m_acquiredForId); - } - } - } autoReleaseCreationDeletionFileLock; - - // A shared file lock on the shared memory file would be held by any process that has opened the same file. Try to take - // an exclusive lock on the file. Successfully acquiring an exclusive lock indicates that no process has a reference to - // the shared memory file, and this process can delete the file. File locks on the shared memory file are only ever acquired - // or released while holding the creation/deletion locks, so holding the creation/deletion locks while trying an exclusive - // lock on the shared memory file guarantees that another process cannot start using the shared memory file after this - // process has decided to delete the file. - bool releaseSharedData = false; - try - { - SharedMemoryManager::AcquireCreationDeletionFileLock(nullptr, GetId()); - autoReleaseCreationDeletionFileLock.m_acquiredForId = GetId(); - - SharedMemoryHelpers::ReleaseFileLock(m_fileDescriptor); - if (SharedMemoryHelpers::TryAcquireFileLock(nullptr, m_fileDescriptor, LOCK_EX | LOCK_NB)) - { - SharedMemoryHelpers::ReleaseFileLock(m_fileDescriptor); - releaseSharedData = true; - } - } - catch (SharedMemoryException) - { - // Ignore the error, just don't release shared data - } - - if (m_data != nullptr) - { - m_data->Close(m_refCount != 0 /* isAbruptShutdown */, releaseSharedData); - } - - if (m_refCount == 0) - { - if (m_data != nullptr) - { - delete m_data; - } - - if (releaseSharedData) - { - m_sharedDataHeader->~SharedMemorySharedDataHeader(); - } - - munmap(m_sharedDataHeader, m_sharedDataTotalByteCount); - SharedMemoryHelpers::CloseFile(m_fileDescriptor); - } - - if (!releaseSharedData) - { - return; - } - - try - { - // Delete the shared memory file, and the session directory if it's not empty - PathCharString path; - SharedMemoryHelpers::VerifyStringOperation( - path.Set(*gSharedFilesPath) && - m_id.AppendRuntimeTempDirectoryName(path) && - path.Append('/') && path.Append(SHARED_MEMORY_SHARED_MEMORY_DIRECTORY_NAME) && - path.Append('/') && m_id.AppendSessionDirectoryName(path) && - path.Append('/')); - SIZE_T sessionDirectoryPathCharCount = path.GetCount(); - SharedMemoryHelpers::VerifyStringOperation(path.Append(m_id.GetName(), m_id.GetNameCharCount())); - unlink(path); - path.CloseBuffer(sessionDirectoryPathCharCount); - rmdir(path); - } - catch (SharedMemoryException) - { - // Ignore the error, just don't release shared data - } -} - -const SharedMemoryId *SharedMemoryProcessDataHeader::GetId() const -{ - return &m_id; -} - -SharedMemoryProcessDataBase *SharedMemoryProcessDataHeader::GetData() const -{ - return m_data; -} - -void SharedMemoryProcessDataHeader::SetData(SharedMemoryProcessDataBase *data) -{ - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - _ASSERTE(m_data == nullptr); - _ASSERTE(data != nullptr); - - m_data = data; -} - -SharedMemorySharedDataHeader *SharedMemoryProcessDataHeader::GetSharedDataHeader() const -{ - return m_sharedDataHeader; -} - -SIZE_T SharedMemoryProcessDataHeader::GetSharedDataTotalByteCount() const -{ - return m_sharedDataTotalByteCount; -} - -SharedMemoryProcessDataHeader *SharedMemoryProcessDataHeader::GetNextInProcessDataHeaderList() const -{ - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - return m_nextInProcessDataHeaderList; -} - -void SharedMemoryProcessDataHeader::SetNextInProcessDataHeaderList(SharedMemoryProcessDataHeader *next) -{ - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - m_nextInProcessDataHeaderList = next; -} - -void SharedMemoryProcessDataHeader::IncRefCount() -{ - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - _ASSERTE(m_refCount != 0); - - if (++m_refCount == 2 && m_data != nullptr && m_data->HasImplicitRef()) - { - // The synchronization object got an explicit ref that will govern its lifetime, remove the implicit ref - --m_refCount; - m_data->SetHasImplicitRef(false); - } -} - -void SharedMemoryProcessDataHeader::DecRefCount() -{ - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - _ASSERTE(m_refCount != 0); - - if (--m_refCount != 0) - { - return; - } - - if (m_data != nullptr && !m_data->CanClose()) - { - // Extend the lifetime of the synchronization object. The process data object is responsible for removing this extra ref - // when the synchronization object transitions into a state where it can be closed. - ++m_refCount; - m_data->SetHasImplicitRef(true); - return; - } - - delete this; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// SharedMemoryManager - -minipal_mutex SharedMemoryManager::s_creationDeletionProcessLock; -int SharedMemoryManager::s_creationDeletionLockFileDescriptor = -1; - -SharedMemoryManager::UserScopeUidAndFileDescriptor *SharedMemoryManager::s_userScopeUidToCreationDeletionLockFDs; -int SharedMemoryManager::s_userScopeUidToCreationDeletionLockFDsCount; -int SharedMemoryManager::s_userScopeUidToCreationDeletionLockFDsCapacity; - -SharedMemoryProcessDataHeader *SharedMemoryManager::s_processDataHeaderListHead = nullptr; - -#ifdef _DEBUG -SIZE_T SharedMemoryManager::s_creationDeletionProcessLockOwnerThreadId = SharedMemoryHelpers::InvalidThreadId; -SIZE_T SharedMemoryManager::s_creationDeletionFileLockOwnerThreadId = SharedMemoryHelpers::InvalidThreadId; -#endif // _DEBUG - -void SharedMemoryManager::StaticInitialize() -{ - minipal_mutex_init(&s_creationDeletionProcessLock); -} - -void SharedMemoryManager::StaticClose() -{ - // This function could very well be running during abrupt shutdown, and there could still be user threads running. - // Synchronize the deletion, and don't remove or delete items in the linked list. - AcquireCreationDeletionProcessLock(); - for (SharedMemoryProcessDataHeader *current = s_processDataHeaderListHead; - current != nullptr; - current = current->GetNextInProcessDataHeaderList()) - { - current->Close(); - } - ReleaseCreationDeletionProcessLock(); - - // This function could very well be running during abrupt shutdown, and there could still be user threads running. Don't - // delete the creation/deletion process lock, the process is shutting down anyway. -} - -void SharedMemoryManager::AcquireCreationDeletionProcessLock() -{ - _ASSERTE(!IsCreationDeletionProcessLockAcquired()); - _ASSERTE(!IsCreationDeletionFileLockAcquired()); - - minipal_mutex_enter(&s_creationDeletionProcessLock); -#ifdef _DEBUG - s_creationDeletionProcessLockOwnerThreadId = THREADSilentGetCurrentThreadId(); -#endif // _DEBUG -} - -void SharedMemoryManager::ReleaseCreationDeletionProcessLock() -{ - _ASSERTE(IsCreationDeletionProcessLockAcquired()); - _ASSERTE(!IsCreationDeletionFileLockAcquired()); - -#ifdef _DEBUG - s_creationDeletionProcessLockOwnerThreadId = SharedMemoryHelpers::InvalidThreadId; -#endif // _DEBUG - minipal_mutex_leave(&s_creationDeletionProcessLock); -} - -void SharedMemoryManager::AcquireCreationDeletionFileLock(SharedMemorySystemCallErrors *errors, const SharedMemoryId *id) -{ - _ASSERTE(id != nullptr); - _ASSERTE(IsCreationDeletionProcessLockAcquired()); - _ASSERTE(!IsCreationDeletionFileLockAcquired()); - - int creationDeletionLockFD = - id->IsUserScope() ? FindUserScopeCreationDeletionLockFD(id->GetUserScopeUid()) : s_creationDeletionLockFileDescriptor; - if (creationDeletionLockFD == -1) - { - // Create the shared files directory - PathCharString dirPath; - SharedMemoryHelpers::VerifyStringOperation(dirPath.Set(*gSharedFilesPath)); - if (!SharedMemoryHelpers::EnsureDirectoryExists( - errors, - dirPath, - id, - false /* isGlobalLockAcquired */, - false /* createIfNotExist */, - true /* isSystemDirectory */)) - { - _ASSERTE(errno == ENOENT); - if (errors != nullptr) - { - errors->Append("stat(\"%s\", ...) == -1; errno == ENOENT;", (const char *)*gSharedFilesPath); - } - - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); - } - - // Create the runtime temp directory - SharedMemoryHelpers::VerifyStringOperation(id->AppendRuntimeTempDirectoryName(dirPath)); - SharedMemoryHelpers::EnsureDirectoryExists(errors, dirPath, id, false /* isGlobalLockAcquired */); - - // Create the shared memory directory - SharedMemoryHelpers::VerifyStringOperation( - dirPath.Append('/') && dirPath.Append(SHARED_MEMORY_SHARED_MEMORY_DIRECTORY_NAME)); - SharedMemoryHelpers::EnsureDirectoryExists(errors, dirPath, id, false /* isGlobalLockAcquired */); - - // Open the shared memory directory - creationDeletionLockFD = SharedMemoryHelpers::OpenDirectory(errors, dirPath); - if (creationDeletionLockFD == -1) - { - if (errors != nullptr) - { - int errorCode = errno; - errors->Append( - "open(\"%s\", O_RDONLY | O_CLOEXEC, 0) == -1; errno == %s;", - (const char *)dirPath, - GetFriendlyErrorCodeString(errorCode)); - } - - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); - } - - if (id->IsUserScope()) - { - AddUserScopeUidCreationDeletionLockFD(id->GetUserScopeUid(), creationDeletionLockFD); - } - else - { - s_creationDeletionLockFileDescriptor = creationDeletionLockFD; - } - } - - bool acquiredFileLock = SharedMemoryHelpers::TryAcquireFileLock(errors, creationDeletionLockFD, LOCK_EX); - _ASSERTE(acquiredFileLock); -#ifdef _DEBUG - s_creationDeletionFileLockOwnerThreadId = THREADSilentGetCurrentThreadId(); -#endif // _DEBUG -} - -void SharedMemoryManager::ReleaseCreationDeletionFileLock(const SharedMemoryId *id) -{ - _ASSERTE(id != nullptr); - _ASSERTE(IsCreationDeletionProcessLockAcquired()); - _ASSERTE(IsCreationDeletionFileLockAcquired()); - - int creationDeletionLockFD = - id->IsUserScope() ? FindUserScopeCreationDeletionLockFD(id->GetUserScopeUid()) : s_creationDeletionLockFileDescriptor; - _ASSERTE(creationDeletionLockFD != -1); - -#ifdef _DEBUG - s_creationDeletionFileLockOwnerThreadId = SharedMemoryHelpers::InvalidThreadId; -#endif // _DEBUG - SharedMemoryHelpers::ReleaseFileLock(creationDeletionLockFD); -} - -void SharedMemoryManager::AddUserScopeUidCreationDeletionLockFD(uid_t userScopeUid, int creationDeletionLockFD) -{ - _ASSERTE(IsCreationDeletionProcessLockAcquired()); - _ASSERTE(creationDeletionLockFD != -1); - _ASSERTE(FindUserScopeCreationDeletionLockFD(userScopeUid) == -1); - - int count = s_userScopeUidToCreationDeletionLockFDsCount; - int capacity = s_userScopeUidToCreationDeletionLockFDsCapacity; - if (count >= capacity) - { - int newCapacity = capacity == 0 ? 1 : capacity * 2; - if (newCapacity <= capacity || - newCapacity * sizeof(UserScopeUidAndFileDescriptor) / sizeof(UserScopeUidAndFileDescriptor) != (SIZE_T)newCapacity) - { - throw SharedMemoryException(static_cast(SharedMemoryError::OutOfMemory)); - } - - UserScopeUidAndFileDescriptor *newArray = new(std::nothrow) UserScopeUidAndFileDescriptor[newCapacity]; - if (newArray == nullptr) - { - throw SharedMemoryException(static_cast(SharedMemoryError::OutOfMemory)); - } - - if (count != 0) - { - UserScopeUidAndFileDescriptor *oldArray = s_userScopeUidToCreationDeletionLockFDs; - CopyMemory(newArray, oldArray, count * sizeof(newArray[0])); - delete[] oldArray; - } - - s_userScopeUidToCreationDeletionLockFDs = newArray; - s_userScopeUidToCreationDeletionLockFDsCapacity = newCapacity; - } - - s_userScopeUidToCreationDeletionLockFDs[count] = UserScopeUidAndFileDescriptor(userScopeUid, creationDeletionLockFD); - s_userScopeUidToCreationDeletionLockFDsCount = count + 1; -} - -int SharedMemoryManager::FindUserScopeCreationDeletionLockFD(uid_t userScopeUid) -{ - _ASSERTE(IsCreationDeletionProcessLockAcquired()); - - UserScopeUidAndFileDescriptor *arr = s_userScopeUidToCreationDeletionLockFDs; - for (int i = 0; i < s_userScopeUidToCreationDeletionLockFDsCount; i++) - { - _ASSERTE(arr[i].fileDescriptor != -1); - if (arr[i].userScopeUid == userScopeUid) - { - return arr[i].fileDescriptor; - } - } - - return -1; -} - -#ifdef _DEBUG -bool SharedMemoryManager::IsCreationDeletionProcessLockAcquired() -{ - return s_creationDeletionProcessLockOwnerThreadId == THREADSilentGetCurrentThreadId(); -} - -bool SharedMemoryManager::IsCreationDeletionFileLockAcquired() -{ - return s_creationDeletionFileLockOwnerThreadId == THREADSilentGetCurrentThreadId(); -} -#endif // _DEBUG - -void SharedMemoryManager::AddProcessDataHeader(SharedMemoryProcessDataHeader *processDataHeader) -{ - _ASSERTE(processDataHeader != nullptr); - _ASSERTE(IsCreationDeletionProcessLockAcquired()); - _ASSERTE(processDataHeader->GetNextInProcessDataHeaderList() == nullptr); - _ASSERTE(FindProcessDataHeader(processDataHeader->GetId()) == nullptr); - - processDataHeader->SetNextInProcessDataHeaderList(s_processDataHeaderListHead); - s_processDataHeaderListHead = processDataHeader; -} - -void SharedMemoryManager::RemoveProcessDataHeader(SharedMemoryProcessDataHeader *processDataHeader) -{ - _ASSERTE(processDataHeader != nullptr); - _ASSERTE(IsCreationDeletionProcessLockAcquired()); - - if (s_processDataHeaderListHead == processDataHeader) - { - s_processDataHeaderListHead = processDataHeader->GetNextInProcessDataHeaderList(); - processDataHeader->SetNextInProcessDataHeaderList(nullptr); - return; - } - for (SharedMemoryProcessDataHeader - *previous = s_processDataHeaderListHead, - *current = previous->GetNextInProcessDataHeaderList(); - current != nullptr; - previous = current, current = current->GetNextInProcessDataHeaderList()) - { - if (current == processDataHeader) - { - previous->SetNextInProcessDataHeaderList(current->GetNextInProcessDataHeaderList()); - current->SetNextInProcessDataHeaderList(nullptr); - return; - } - } - _ASSERTE(false); -} - -SharedMemoryProcessDataHeader *SharedMemoryManager::FindProcessDataHeader(const SharedMemoryId *id) -{ - _ASSERTE(IsCreationDeletionProcessLockAcquired()); - - // TODO: Use a hash table - for (SharedMemoryProcessDataHeader *current = s_processDataHeaderListHead; - current != nullptr; - current = current->GetNextInProcessDataHeaderList()) - { - if (current->GetId()->Equals(id)) - { - return current; - } - } - return nullptr; -} diff --git a/src/coreclr/pal/src/synchmgr/synchmanager.cpp b/src/coreclr/pal/src/synchmgr/synchmanager.cpp index 0665df179caa04..a2fa13c742cedd 100644 --- a/src/coreclr/pal/src/synchmgr/synchmanager.cpp +++ b/src/coreclr/pal/src/synchmgr/synchmanager.cpp @@ -552,17 +552,6 @@ namespace CorUnix CThreadSynchronizationInfo * pSynchInfo = &pthrTarget->synchronizationInfo; CPalSynchronizationManager * pSynchManager = GetInstance(); - // The shared memory manager's process lock is acquired before calling into some PAL synchronization primitives that may - // take the PAL synchronization manager's synch lock (acquired below). For example, when using a file lock - // implementation for a named mutex (see NamedMutexProcessData::NamedMutexProcessData()), under the shared memory - // manager's process lock, CreateMutex is called, which acquires the PAL synchronization manager's synch lock. The same - // lock order needs to be maintained here to avoid a deadlock. - bool abandonNamedMutexes = pSynchInfo->OwnsAnyNamedMutex(); - if (abandonNamedMutexes) - { - SharedMemoryManager::AcquireCreationDeletionProcessLock(); - } - // Local lock AcquireLocalSynchLock(pthrCurrent); @@ -598,20 +587,6 @@ namespace CorUnix pSynchManager->m_cacheOwnedObjectsListNodes.Add(pthrCurrent, poolnItem); } - if (abandonNamedMutexes) - { - // Abandon owned named mutexes - while (true) - { - NamedMutexProcessData *processData = pSynchInfo->RemoveFirstOwnedNamedMutex(); - if (processData == nullptr) - { - break; - } - processData->Abandon(); - } - } - if (pthrTarget != pthrCurrent) { // If the target thead is not the current one, we are being called @@ -645,11 +620,6 @@ namespace CorUnix // Unlock ReleaseLocalSynchLock(pthrCurrent); - if (abandonNamedMutexes) - { - SharedMemoryManager::ReleaseCreationDeletionProcessLock(); - } - DiscardAllPendingAPCs(pthrCurrent, pthrTarget); return palErr; @@ -2989,8 +2959,7 @@ namespace CorUnix CThreadSynchronizationInfo::CThreadSynchronizationInfo() : m_tsThreadState(TS_IDLE), m_shridWaitAwakened(NULL), - m_lLocalSynchLockCount(0), - m_ownedNamedMutexListHead(nullptr) + m_lLocalSynchLockCount(0) { InitializeListHead(&m_leOwnedObjsList); @@ -3236,87 +3205,6 @@ namespace CorUnix return poolnItem; } - void CThreadSynchronizationInfo::AddOwnedNamedMutex(NamedMutexProcessData *processData) - { - _ASSERTE(this == &GetCurrentPalThread()->synchronizationInfo); - _ASSERTE(processData != nullptr); - _ASSERTE(processData->IsLockOwnedByCurrentThread()); - _ASSERTE(processData->GetNextInThreadOwnedNamedMutexList() == nullptr); - - processData->SetNextInThreadOwnedNamedMutexList(m_ownedNamedMutexListHead); - m_ownedNamedMutexListHead = processData; - } - - void CThreadSynchronizationInfo::RemoveOwnedNamedMutex(NamedMutexProcessData *processData) - { - _ASSERTE(this == &GetCurrentPalThread()->synchronizationInfo); - _ASSERTE(processData != nullptr); - _ASSERTE(processData->IsLockOwnedByCurrentThread()); - - if (m_ownedNamedMutexListHead == processData) - { - m_ownedNamedMutexListHead = processData->GetNextInThreadOwnedNamedMutexList(); - processData->SetNextInThreadOwnedNamedMutexList(nullptr); - } - else - { - bool found = false; - for (NamedMutexProcessData - *previous = m_ownedNamedMutexListHead, - *current = previous->GetNextInThreadOwnedNamedMutexList(); - current != nullptr; - previous = current, current = current->GetNextInThreadOwnedNamedMutexList()) - { - if (current == processData) - { - found = true; - previous->SetNextInThreadOwnedNamedMutexList(current->GetNextInThreadOwnedNamedMutexList()); - current->SetNextInThreadOwnedNamedMutexList(nullptr); - break; - } - } - _ASSERTE(found); - } - } - - NamedMutexProcessData *CThreadSynchronizationInfo::RemoveFirstOwnedNamedMutex() - { - _ASSERTE(this == &GetCurrentPalThread()->synchronizationInfo); - - NamedMutexProcessData *processData = m_ownedNamedMutexListHead; - if (processData != nullptr) - { - _ASSERTE(processData->IsLockOwnedByCurrentThread()); - m_ownedNamedMutexListHead = processData->GetNextInThreadOwnedNamedMutexList(); - processData->SetNextInThreadOwnedNamedMutexList(nullptr); - } - return processData; - } - - bool CThreadSynchronizationInfo::OwnsNamedMutex(NamedMutexProcessData *processData) - { - _ASSERTE(this == &GetCurrentPalThread()->synchronizationInfo); - - for (NamedMutexProcessData *current = m_ownedNamedMutexListHead; - current != nullptr; - current = current->GetNextInThreadOwnedNamedMutexList()) - { - _ASSERTE(current->IsLockOwnedByCurrentThread()); - if (current == processData) - { - return true; - } - } - - return false; - } - - bool CThreadSynchronizationInfo::OwnsAnyNamedMutex() const - { - _ASSERTE(this == &GetCurrentPalThread()->synchronizationInfo); - return m_ownedNamedMutexListHead != nullptr; - } - #if SYNCHMGR_SUSPENSION_SAFE_CONDITION_SIGNALING /*++ diff --git a/src/coreclr/pal/src/synchmgr/wait.cpp b/src/coreclr/pal/src/synchmgr/wait.cpp index 495d86b1cd5cd3..1a3aa8c4df18df 100644 --- a/src/coreclr/pal/src/synchmgr/wait.cpp +++ b/src/coreclr/pal/src/synchmgr/wait.cpp @@ -40,7 +40,6 @@ static PalObjectTypeId sg_rgWaitObjectsIds[] = otiAutoResetEvent, otiManualResetEvent, otiMutex, - otiNamedMutex, otiSemaphore, otiProcess, otiThread @@ -53,7 +52,6 @@ static PalObjectTypeId sg_rgSignalableObjectIds[] = otiAutoResetEvent, otiManualResetEvent, otiMutex, - otiNamedMutex, otiSemaphore }; static CAllowedObjectTypes sg_aotSignalableObject(sg_rgSignalableObjectIds, ARRAY_SIZE(sg_rgSignalableObjectIds)); @@ -417,55 +415,6 @@ DWORD CorUnix::InternalWaitForMultipleObjectsEx( goto WFMOExIntExit; } - if (nCount > 1) - { - // Check for any cross-process sync objects. "Wait for any" and "wait for all" operations are not supported on - // cross-process sync objects in the PAL. - for (DWORD i = 0; i < nCount; ++i) - { - if (ppIPalObjs[i]->GetObjectType()->GetId() == otiNamedMutex) - { - ERROR("Attempt to wait for any or all handles including a cross-process sync object", ERROR_NOT_SUPPORTED); - pThread->SetLastError(ERROR_NOT_SUPPORTED); - goto WFMOExIntCleanup; - } - } - } - else if (ppIPalObjs[0]->GetObjectType()->GetId() == otiNamedMutex) - { - SharedMemoryProcessDataHeader *processDataHeader = - SharedMemoryProcessDataHeader::PalObject_GetProcessDataHeader(ppIPalObjs[0]); - _ASSERTE(processDataHeader != nullptr); - try - { - MutexTryAcquireLockResult tryAcquireLockResult = - static_cast(processDataHeader->GetData())->TryAcquireLock(nullptr, dwMilliseconds); - switch (tryAcquireLockResult) - { - case MutexTryAcquireLockResult::AcquiredLock: - dwRet = WAIT_OBJECT_0; - break; - - case MutexTryAcquireLockResult::AcquiredLockButMutexWasAbandoned: - dwRet = WAIT_ABANDONED_0; - break; - - case MutexTryAcquireLockResult::TimedOut: - dwRet = WAIT_TIMEOUT; - break; - - default: - _ASSERTE(false); - break; - } - } - catch (SharedMemoryException ex) - { - pThread->SetLastError(ex.GetErrorCode()); - } - goto WFMOExIntCleanup; - } - if (fWAll) { // For a wait-all operation, check for duplicate wait objects in the array. This just uses a brute-force O(n^2) @@ -761,7 +710,6 @@ DWORD CorUnix::InternalSignalObjectAndWait( break; case otiMutex: - case otiNamedMutex: palError = InternalReleaseMutex(thread, hObjectToSignal); break; diff --git a/src/coreclr/pal/src/synchobj/mutex.cpp b/src/coreclr/pal/src/synchobj/mutex.cpp index f23254612c555c..b189f7269d6879 100644 --- a/src/coreclr/pal/src/synchobj/mutex.cpp +++ b/src/coreclr/pal/src/synchobj/mutex.cpp @@ -55,128 +55,47 @@ CObjectType CorUnix::otMutex( static CAllowedObjectTypes aotMutex(otiMutex); -CObjectType CorUnix::otNamedMutex( - otiNamedMutex, - &SharedMemoryProcessDataHeader::PalObject_Close, // Cleanup routine - sizeof(SharedMemoryProcessDataHeader *), // Immutable data - NULL, // No immutable data copy routine - NULL, // No immutable data cleanup routine - 0, // No process local data - NULL, // No process local data cleanup routine - CObjectType::UnwaitableObject, // PAL's waiting infrastructure is not used - CObjectType::SignalingNotApplicable, // PAL's signaling infrastructure is not used - CObjectType::ThreadReleaseNotApplicable, // PAL's signaling infrastructure is not used - CObjectType::OwnershipNotApplicable // PAL's ownership infrastructure is not used - ); - -static CAllowedObjectTypes aotNamedMutex(otiNamedMutex); - -static PalObjectTypeId anyMutexTypeIds[] = {otiMutex, otiNamedMutex}; -static CAllowedObjectTypes aotAnyMutex(anyMutexTypeIds, ARRAY_SIZE(anyMutexTypeIds)); - /*++ Function: CreateMutexW - See doc for PAL_CreateMutexW. ---*/ - -HANDLE -PALAPI -CreateMutexW( - IN LPSECURITY_ATTRIBUTES lpMutexAttributes, - IN BOOL bInitialOwner, - IN LPCWSTR lpName) -{ - return PAL_CreateMutexW(bInitialOwner, lpName, false /* bCurrentUserOnly */, nullptr, 0); -} - -/*++ -Function: - PAL_CreateMutexW - Note: lpMutexAttributes currently ignored: -- Win32 object security not supported -- handles to mutex objects are not inheritable -Parameters: - lpSystemCallErrors -- An optional buffer into which system call errors are written, for more detailed error information. - dwSystemCallErrorsBufferSize -- Size of the buffer pointed to by lpSystemCallErrors in bytes. - See MSDN docs on CreateMutexW for all other parameters. --*/ HANDLE PALAPI -PAL_CreateMutexW( +CreateMutexW( + IN LPSECURITY_ATTRIBUTES lpMutexAttributes, IN BOOL bInitialOwner, - IN LPCWSTR lpName, - IN BOOL bCurrentUserOnly, - IN LPSTR lpSystemCallErrors, - IN DWORD dwSystemCallErrorsBufferSize) + IN LPCWSTR lpName) { + _ASSERTE(lpName == nullptr); HANDLE hMutex = NULL; PAL_ERROR palError; CPalThread *pthr = NULL; - char utf8Name[SHARED_MEMORY_MAX_NAME_CHAR_COUNT + 1]; - PERF_ENTRY(PAL_CreateMutexW); - ENTRY("PAL_CreateMutexW(bInitialOwner=%d, lpName=%p (%S), lpSystemCallErrors=%p, dwSystemCallErrorsBufferSize=%d\n", - bInitialOwner, - lpName, - lpName?lpName:W16_NULLSTRING, - lpSystemCallErrors, - dwSystemCallErrorsBufferSize); + PERF_ENTRY(CreateMutexW); + ENTRY("CreateMutexW(lpMutexAttributes=%p, bInitialOwner=%d\n", + lpMutexAttributes, + bInitialOwner + ); pthr = InternalGetCurrentThread(); - /* validate parameters */ - if ((int)dwSystemCallErrorsBufferSize < 0 || (lpSystemCallErrors == nullptr) != (dwSystemCallErrorsBufferSize == 0)) - { - ERROR("One or more parameters are invalid\n"); - palError = ERROR_INVALID_PARAMETER; - goto CreateMutexWExit; - } - - if (lpSystemCallErrors != nullptr) - { - lpSystemCallErrors[0] = '\0'; - } - - if (lpName != nullptr) - { - int bytesWritten = WideCharToMultiByte(CP_ACP, 0, lpName, -1, utf8Name, ARRAY_SIZE(utf8Name), nullptr, nullptr); - if (bytesWritten == 0) - { - DWORD errorCode = GetLastError(); - if (errorCode == ERROR_INSUFFICIENT_BUFFER) - { - palError = static_cast(SharedMemoryError::NameTooLong); - } - else - { - ASSERT("WideCharToMultiByte failed (%u)\n", errorCode); - palError = errorCode; - } - goto CreateMutexWExit; - } - } - { - SharedMemorySystemCallErrors errors(lpSystemCallErrors, (int)dwSystemCallErrorsBufferSize); palError = InternalCreateMutex( - &errors, pthr, - nullptr, + nullptr, // lpMutexAttributes currently ignored bInitialOwner, - lpName == nullptr ? nullptr : utf8Name, - bCurrentUserOnly, &hMutex ); } -CreateMutexWExit: // // We always need to set last error, even on success: // we need to protect ourselves from the situation @@ -186,8 +105,8 @@ PAL_CreateMutexW( pthr->SetLastError(palError); - LOGEXIT("PAL_CreateMutexW returns HANDLE %p\n", hMutex); - PERF_EXIT(PAL_CreateMutexW); + LOGEXIT("CreateMutexW returns HANDLE %p\n", hMutex); + PERF_EXIT(CreateMutexW); return hMutex; } @@ -234,12 +153,9 @@ CreateMutexExW( PAL_ERROR CorUnix::InternalCreateMutex( - SharedMemorySystemCallErrors *errors, CPalThread *pthr, LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, - LPCSTR lpName, - BOOL bCurrentUserOnly, HANDLE *phMutex ) { @@ -249,32 +165,21 @@ CorUnix::InternalCreateMutex( IPalObject *pobjRegisteredMutex = NULL; ISynchStateController *pssc = NULL; HANDLE hMutex = nullptr; - bool createdNamedMutex = false; _ASSERTE(NULL != pthr); _ASSERTE(NULL != phMutex); ENTRY("InternalCreateMutex(pthr=%p, lpMutexAttributes=%p, bInitialOwner=%d" - ", lpName=%p, phMutex=%p)\n", + ", phMutex=%p)\n", pthr, lpMutexAttributes, bInitialOwner, - lpName, phMutex ); - if (lpName != nullptr && lpName[0] == '\0') - { - // Empty name is treated as a request for an unnamed process-local mutex - lpName = nullptr; - } - - CObjectType *ot = lpName == nullptr ? &otMutex : &otNamedMutex; - CAllowedObjectTypes *aot = lpName == nullptr ? &aotMutex : &aotNamedMutex; - palError = g_pObjectManager->AllocateObject( pthr, - ot, + &otMutex, &oa, &pobjMutex ); @@ -284,61 +189,42 @@ CorUnix::InternalCreateMutex( goto InternalCreateMutexExit; } - if (lpName == nullptr) - { - palError = pobjMutex->GetSynchStateController( - pthr, - &pssc - ); - - if (NO_ERROR != palError) - { - ASSERT("Unable to create state controller (%d)\n", palError); - goto InternalCreateMutexExit; - } - - if (bInitialOwner) - { - palError = pssc->SetOwner(pthr); - } - else - { - palError = pssc->SetSignalCount(1); - } + palError = pobjMutex->GetSynchStateController( + pthr, + &pssc + ); - pssc->ReleaseController(); + if (NO_ERROR != palError) + { + ASSERT("Unable to create state controller (%d)\n", palError); + goto InternalCreateMutexExit; + } - if (NO_ERROR != palError) - { - ASSERT("Unable to set initial mutex state (%d)\n", palError); - goto InternalCreateMutexExit; - } + if (bInitialOwner) + { + palError = pssc->SetOwner(pthr); } else { - SharedMemoryProcessDataHeader *processDataHeader; - try - { - processDataHeader = - NamedMutexProcessData::CreateOrOpen(errors, lpName, !!bCurrentUserOnly, !!bInitialOwner, &createdNamedMutex); - } - catch (SharedMemoryException ex) - { - palError = ex.GetErrorCode(); - goto InternalCreateMutexExit; - } + palError = pssc->SetSignalCount(1); + } + + pssc->ReleaseController(); - SharedMemoryProcessDataHeader::PalObject_SetProcessDataHeader(pobjMutex, processDataHeader); + if (NO_ERROR != palError) + { + ASSERT("Unable to set initial mutex state (%d)\n", palError); + goto InternalCreateMutexExit; } palError = g_pObjectManager->RegisterObject( pthr, pobjMutex, - aot, + &aotMutex, &hMutex, &pobjRegisteredMutex ); - _ASSERTE(palError != ERROR_ALREADY_EXISTS); // PAL's naming infrastructure is not used for named mutexes + _ASSERTE(palError != ERROR_ALREADY_EXISTS); // Mutexes can't have names _ASSERTE(palError != NO_ERROR || pobjRegisteredMutex == pobjMutex); _ASSERTE((palError == NO_ERROR) == (hMutex != nullptr)); @@ -358,13 +244,6 @@ CorUnix::InternalCreateMutex( *phMutex = hMutex; hMutex = nullptr; - if (lpName != nullptr && !createdNamedMutex) - { - // Indicate to the caller that an existing mutex was opened, and hence the caller will not have initial ownership of the - // mutex if requested through bInitialOwner - palError = ERROR_ALREADY_EXISTS; - } - InternalCreateMutexExit: _ASSERTE(pobjRegisteredMutex == nullptr); @@ -443,7 +322,7 @@ CorUnix::InternalReleaseMutex( palError = g_pObjectManager->ReferenceObjectByHandle( pthr, hMutex, - &aotAnyMutex, + &aotMutex, &pobjMutex ); @@ -453,44 +332,23 @@ CorUnix::InternalReleaseMutex( goto InternalReleaseMutexExit; } - objectTypeId = pobjMutex->GetObjectType()->GetId(); - if (objectTypeId == otiMutex) - { - palError = pobjMutex->GetSynchStateController( - pthr, - &pssc - ); + palError = pobjMutex->GetSynchStateController( + pthr, + &pssc + ); - if (NO_ERROR != palError) - { - ASSERT("Error %d obtaining synch state controller\n", palError); - goto InternalReleaseMutexExit; - } + if (NO_ERROR != palError) + { + ASSERT("Error %d obtaining synch state controller\n", palError); + goto InternalReleaseMutexExit; + } - palError = pssc->DecrementOwnershipCount(); + palError = pssc->DecrementOwnershipCount(); - if (NO_ERROR != palError) - { - ERROR("Error %d decrementing mutex ownership count\n", palError); - goto InternalReleaseMutexExit; - } - } - else + if (NO_ERROR != palError) { - _ASSERTE(objectTypeId == otiNamedMutex); - - SharedMemoryProcessDataHeader *processDataHeader = - SharedMemoryProcessDataHeader::PalObject_GetProcessDataHeader(pobjMutex); - _ASSERTE(processDataHeader != nullptr); - try - { - static_cast(processDataHeader->GetData())->ReleaseLock(); - } - catch (SharedMemoryException ex) - { - palError = ex.GetErrorCode(); - goto InternalReleaseMutexExit; - } + ERROR("Error %d decrementing mutex ownership count\n", palError); + goto InternalReleaseMutexExit; } InternalReleaseMutexExit: @@ -510,1221 +368,39 @@ CorUnix::InternalReleaseMutex( return palError; } -/*++ -Function: - OpenMutexA - -Note: - dwDesiredAccess is currently ignored (no Win32 object security support) - bInheritHandle is currently ignored (handles to mutexes are not inheritable) - -See MSDN doc. ---*/ - -HANDLE -PALAPI -OpenMutexA ( - IN DWORD dwDesiredAccess, - IN BOOL bInheritHandle, - IN LPCSTR lpName) -{ - HANDLE hMutex = NULL; - CPalThread *pthr = NULL; - PAL_ERROR palError; - - PERF_ENTRY(OpenMutexA); - ENTRY("OpenMutexA(dwDesiredAccess=%#x, bInheritHandle=%d, lpName=%p (%s))\n", - dwDesiredAccess, bInheritHandle, lpName, lpName?lpName:"NULL"); - - pthr = InternalGetCurrentThread(); - - /* validate parameters */ - if (lpName == nullptr) - { - ERROR("name is NULL\n"); - palError = ERROR_INVALID_PARAMETER; - goto OpenMutexAExit; - } - - palError = InternalOpenMutex(nullptr, pthr, lpName, false /* bCurrentUserOnly */, &hMutex); - -OpenMutexAExit: - if (NO_ERROR != palError) - { - pthr->SetLastError(palError); - } - - LOGEXIT("OpenMutexA returns HANDLE %p\n", hMutex); - PERF_EXIT(OpenMutexA); - return hMutex; -} - -/*++ -Function: - OpenMutexW - -Parameters: - See doc for PAL_OpenMutexW. ---*/ - -HANDLE -PALAPI -OpenMutexW( - IN DWORD dwDesiredAccess, - IN BOOL bInheritHandle, - IN LPCWSTR lpName) -{ - return PAL_OpenMutexW(lpName, false /* bCurrentUserOnly */, nullptr, 0); -} - -/*++ -Function: - PAL_OpenMutexW - -Note: - dwDesiredAccess is currently ignored (no Win32 object security support) - bInheritHandle is currently ignored (handles to mutexes are not inheritable) - -Parameters: - lpSystemCallErrors -- An optional buffer into which system call errors are written, for more detailed error information. - dwSystemCallErrorsBufferSize -- Size of the buffer pointed to by lpSystemCallErrors in bytes. - - See MSDN docs on OpenMutexW for all other parameters. ---*/ - -HANDLE -PALAPI -PAL_OpenMutexW( - IN LPCWSTR lpName, - IN BOOL bCurrentUserOnly, - IN LPSTR lpSystemCallErrors, - IN DWORD dwSystemCallErrorsBufferSize) +/* Basic spinlock implementation */ +void SPINLOCKAcquire (LONG * lock, unsigned int flags) { - HANDLE hMutex = NULL; - PAL_ERROR palError = NO_ERROR; - CPalThread *pthr = NULL; - char utf8Name[SHARED_MEMORY_MAX_NAME_CHAR_COUNT + 1]; - - PERF_ENTRY(PAL_OpenMutexW); - ENTRY("PAL_OpenMutexW(lpName=%p (%S), lpSystemCallErrors=%p, dwSystemCallErrorsBufferSize=%d)\n", - lpName, - lpName?lpName:W16_NULLSTRING, - lpSystemCallErrors, - dwSystemCallErrorsBufferSize); - - pthr = InternalGetCurrentThread(); - - /* validate parameters */ - if (lpName == nullptr || - lpName[0] == W('\0') || - (int)dwSystemCallErrorsBufferSize < 0 || - (lpSystemCallErrors == nullptr) != (dwSystemCallErrorsBufferSize == 0)) - { - ERROR("One or more parameters are invalid\n"); - palError = ERROR_INVALID_PARAMETER; - goto OpenMutexWExit; - } + size_t loop_seed = 1, loop_count = 0; - if (lpSystemCallErrors != nullptr) + if (flags & SYNCSPINLOCK_F_ASYMMETRIC) { - lpSystemCallErrors[0] = '\0'; + loop_seed = ((size_t)pthread_self() % 10) + 1; } - + while (InterlockedCompareExchange(lock, 1, 0)) { - int bytesWritten = WideCharToMultiByte(CP_ACP, 0, lpName, -1, utf8Name, ARRAY_SIZE(utf8Name), nullptr, nullptr); - if (bytesWritten == 0) + if (!(flags & SYNCSPINLOCK_F_ASYMMETRIC) || (++loop_count % loop_seed)) { - DWORD errorCode = GetLastError(); - if (errorCode == ERROR_INSUFFICIENT_BUFFER) - { - palError = static_cast(SharedMemoryError::NameTooLong); - } - else - { - ASSERT("WideCharToMultiByte failed (%u)\n", errorCode); - palError = errorCode; - } - goto OpenMutexWExit; +#if PAL_IGNORE_NORMAL_THREAD_PRIORITY + struct timespec tsSleepTime; + tsSleepTime.tv_sec = 0; + tsSleepTime.tv_nsec = 1; + nanosleep(&tsSleepTime, NULL); +#else + sched_yield(); +#endif } - - SharedMemorySystemCallErrors errors(lpSystemCallErrors, (int)dwSystemCallErrorsBufferSize); - palError = InternalOpenMutex(&errors, pthr, lpName == nullptr ? nullptr : utf8Name, bCurrentUserOnly, &hMutex); - } - -OpenMutexWExit: - if (NO_ERROR != palError) - { - pthr->SetLastError(palError); } - LOGEXIT("PAL_OpenMutexW returns HANDLE %p\n", hMutex); - PERF_EXIT(PAL_OpenMutexW); - - return hMutex; } -/*++ -Function: - InternalOpenMutex - -Parameters: - errors -- An optional wrapper for system call errors, for more detailed error information. - pthr -- thread data for calling thread - phEvent -- on success, receives the allocated mutex handle - - See MSDN docs on OpenMutex for all other parameters. ---*/ - -PAL_ERROR -CorUnix::InternalOpenMutex( - SharedMemorySystemCallErrors *errors, - CPalThread *pthr, - LPCSTR lpName, - BOOL bCurrentUserOnly, - HANDLE *phMutex - ) +void SPINLOCKRelease (LONG * lock) { - CObjectAttributes oa; - PAL_ERROR palError = NO_ERROR; - IPalObject *pobjMutex = NULL; - IPalObject *pobjRegisteredMutex = NULL; - HANDLE hMutex = nullptr; - - _ASSERTE(NULL != pthr); - _ASSERTE(NULL != lpName); - _ASSERTE(NULL != phMutex); - - ENTRY("InternalOpenMutex(pthr=%p, " - "lpName=%p, phMutex=%p)\n", - pthr, - lpName, - phMutex - ); - - palError = g_pObjectManager->AllocateObject( - pthr, - &otNamedMutex, - &oa, - &pobjMutex - ); - - if (NO_ERROR != palError) - { - goto InternalOpenMutexExit; - } - - { - SharedMemoryProcessDataHeader *processDataHeader; - try - { - processDataHeader = NamedMutexProcessData::Open(errors, lpName, bCurrentUserOnly); - } - catch (SharedMemoryException ex) - { - palError = ex.GetErrorCode(); - goto InternalOpenMutexExit; - } - - if (processDataHeader == nullptr) - { - palError = ERROR_FILE_NOT_FOUND; - goto InternalOpenMutexExit; - } - - SharedMemoryProcessDataHeader::PalObject_SetProcessDataHeader(pobjMutex, processDataHeader); - } - - palError = g_pObjectManager->RegisterObject( - pthr, - pobjMutex, - &aotNamedMutex, - &hMutex, - &pobjRegisteredMutex - ); - _ASSERTE(palError != ERROR_ALREADY_EXISTS); // PAL's naming infrastructure is not used for named mutexes - _ASSERTE(palError != NO_ERROR || pobjRegisteredMutex == pobjMutex); - _ASSERTE((palError == NO_ERROR) == (hMutex != nullptr)); - - // When RegisterObject succeeds, the object would have an additional reference from the handle, and one reference is - // released below through pobjRegisteredMutex. When RegisterObject fails, it releases the initial reference to the object. - // Either way, pobjMutex is invalidated by the above call to RegisterObject. - pobjMutex = nullptr; - - if (palError != NO_ERROR) - { - goto InternalOpenMutexExit; - } - - pobjRegisteredMutex->ReleaseReference(pthr); - pobjRegisteredMutex = nullptr; - - *phMutex = hMutex; - hMutex = nullptr; - -InternalOpenMutexExit: - - _ASSERTE(pobjRegisteredMutex == nullptr); - _ASSERTE(hMutex == nullptr); - - if (pobjMutex != nullptr) - { - pobjMutex->ReleaseReference(pthr); - } - - LOGEXIT("InternalCreateMutex returns %i\n", palError); - - return palError; -} - - -/* Basic spinlock implementation */ -void SPINLOCKAcquire (LONG * lock, unsigned int flags) -{ - size_t loop_seed = 1, loop_count = 0; - - if (flags & SYNCSPINLOCK_F_ASYMMETRIC) - { - loop_seed = ((size_t)pthread_self() % 10) + 1; - } - while (InterlockedCompareExchange(lock, 1, 0)) - { - if (!(flags & SYNCSPINLOCK_F_ASYMMETRIC) || (++loop_count % loop_seed)) - { -#if PAL_IGNORE_NORMAL_THREAD_PRIORITY - struct timespec tsSleepTime; - tsSleepTime.tv_sec = 0; - tsSleepTime.tv_nsec = 1; - nanosleep(&tsSleepTime, NULL); -#else - sched_yield(); -#endif - } - } - -} - -void SPINLOCKRelease (LONG * lock) -{ - VolatileStore(lock, 0); -} + VolatileStore(lock, 0); +} DWORD SPINLOCKTryAcquire (LONG * lock) { return InterlockedCompareExchange(lock, 1, 0); // only returns 0 or 1. } - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// MutexHelpers - -#if NAMED_MUTEX_USE_PTHREAD_MUTEX -void MutexHelpers::InitializeProcessSharedRobustRecursiveMutex(SharedMemorySystemCallErrors *errors, pthread_mutex_t *mutex) -{ - _ASSERTE(mutex != nullptr); - - struct AutoCleanup - { - pthread_mutexattr_t *m_mutexAttributes; - - AutoCleanup() : m_mutexAttributes(nullptr) - { - } - - ~AutoCleanup() - { - if (m_mutexAttributes != nullptr) - { - int error = pthread_mutexattr_destroy(m_mutexAttributes); - _ASSERTE(error == 0); - } - } - } autoCleanup; - - pthread_mutexattr_t mutexAttributes; - int error = pthread_mutexattr_init(&mutexAttributes); - if (error != 0) - { - if (errors != nullptr) - { - errors->Append("pthread_mutexattr_init(...) == %s;", GetFriendlyErrorCodeString(error)); - } - - throw SharedMemoryException(static_cast(SharedMemoryError::OutOfMemory)); - } - autoCleanup.m_mutexAttributes = &mutexAttributes; - - error = pthread_mutexattr_setpshared(&mutexAttributes, PTHREAD_PROCESS_SHARED); - _ASSERTE(error == 0); - - error = pthread_mutexattr_setrobust(&mutexAttributes, PTHREAD_MUTEX_ROBUST); - _ASSERTE(error == 0); - - error = pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_RECURSIVE); - _ASSERTE(error == 0); - - error = pthread_mutex_init(mutex, &mutexAttributes); - if (error != 0) - { - if (errors != nullptr) - { - errors->Append("pthread_mutex_init(...) == %s;", GetFriendlyErrorCodeString(error)); - } - - throw SharedMemoryException(static_cast(error == EPERM ? SharedMemoryError::IO : SharedMemoryError::OutOfMemory)); - } -} - -void MutexHelpers::DestroyMutex(pthread_mutex_t *mutex) -{ - _ASSERTE(mutex != nullptr); - - int error = pthread_mutex_destroy(mutex); - _ASSERTE(error == 0 || error == EBUSY); // the error will be EBUSY if the mutex is locked -} - -MutexTryAcquireLockResult MutexHelpers::TryAcquireLock( - SharedMemorySystemCallErrors *errors, - pthread_mutex_t *mutex, - DWORD timeoutMilliseconds) -{ - _ASSERTE(mutex != nullptr); - - int lockResult; - switch (timeoutMilliseconds) - { - case static_cast(-1): - lockResult = pthread_mutex_lock(mutex); - break; - - case 0: - lockResult = pthread_mutex_trylock(mutex); - break; - - default: - { - struct timespec timeoutTime; - PAL_ERROR palError = CPalSynchronizationManager::GetAbsoluteTimeout(timeoutMilliseconds, &timeoutTime, /*fPreferMonotonicClock*/ FALSE); - _ASSERTE(palError == NO_ERROR); - lockResult = pthread_mutex_timedlock(mutex, &timeoutTime); - break; - } - } - - switch (lockResult) - { - case 0: - return MutexTryAcquireLockResult::AcquiredLock; - - case EBUSY: - _ASSERTE(timeoutMilliseconds == 0); - return MutexTryAcquireLockResult::TimedOut; - - case ETIMEDOUT: - _ASSERTE(timeoutMilliseconds != static_cast(-1)); - _ASSERTE(timeoutMilliseconds != 0); - return MutexTryAcquireLockResult::TimedOut; - - case EOWNERDEAD: - { - int setConsistentResult = pthread_mutex_consistent(mutex); - _ASSERTE(setConsistentResult == 0); - return MutexTryAcquireLockResult::AcquiredLockButMutexWasAbandoned; - } - - case EAGAIN: - throw SharedMemoryException(static_cast(NamedMutexError::MaximumRecursiveLocksReached)); - - default: - { - if (errors != nullptr) - { - errors->Append( - "%s(...) == %s;", - timeoutMilliseconds == (DWORD)-1 ? "pthread_mutex_lock" - : timeoutMilliseconds == 0 ? "pthread_mutex_trylock" - : "pthread_mutex_timedlock", - GetFriendlyErrorCodeString(lockResult)); - } - - throw SharedMemoryException(static_cast(NamedMutexError::Unknown)); - } - } -} - -void MutexHelpers::ReleaseLock(pthread_mutex_t *mutex) -{ - _ASSERTE(mutex != nullptr); - - int unlockResult = pthread_mutex_unlock(mutex); - _ASSERTE(unlockResult == 0); -} -#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// NamedMutexSharedData - -NamedMutexSharedData::NamedMutexSharedData(SharedMemorySystemCallErrors *errors) - : -#if !NAMED_MUTEX_USE_PTHREAD_MUTEX - m_timedWaiterCount(0), -#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX - m_lockOwnerProcessId(SharedMemoryHelpers::InvalidProcessId), - m_lockOwnerThreadId(SharedMemoryHelpers::InvalidSharedThreadId), - m_isAbandoned(false) -{ -#if !NAMED_MUTEX_USE_PTHREAD_MUTEX - static_assert_no_msg(sizeof(m_timedWaiterCount) == sizeof(LONG)); // for interlocked operations -#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX - - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - _ASSERTE(SharedMemoryManager::IsCreationDeletionFileLockAcquired()); - -#if NAMED_MUTEX_USE_PTHREAD_MUTEX - MutexHelpers::InitializeProcessSharedRobustRecursiveMutex(errors, &m_lock); -#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX -} - -NamedMutexSharedData::~NamedMutexSharedData() -{ - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - _ASSERTE(SharedMemoryManager::IsCreationDeletionFileLockAcquired()); - -#if NAMED_MUTEX_USE_PTHREAD_MUTEX - MutexHelpers::DestroyMutex(&m_lock); -#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX -} - -#if NAMED_MUTEX_USE_PTHREAD_MUTEX -pthread_mutex_t *NamedMutexSharedData::GetLock() -{ - return &m_lock; -} -#else // !NAMED_MUTEX_USE_PTHREAD_MUTEX -bool NamedMutexSharedData::HasAnyTimedWaiters() const -{ - return - InterlockedCompareExchange( - const_cast(reinterpret_cast(&m_timedWaiterCount)), - -1 /* Exchange */, - -1 /* Comparand */) != 0; -} - -void NamedMutexSharedData::IncTimedWaiterCount() -{ - ULONG newValue = InterlockedIncrement(reinterpret_cast(&m_timedWaiterCount)); - if (newValue == 0) - { - InterlockedDecrement(reinterpret_cast(&m_timedWaiterCount)); - throw SharedMemoryException(static_cast(SharedMemoryError::OutOfMemory)); - } -} - -void NamedMutexSharedData::DecTimedWaiterCount() -{ - ULONG newValue = InterlockedDecrement(reinterpret_cast(&m_timedWaiterCount)); - _ASSERTE(newValue + 1 != 0); -} -#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX - -bool NamedMutexSharedData::IsAbandoned() const -{ - _ASSERTE(IsLockOwnedByCurrentThread()); - return m_isAbandoned; -} - -void NamedMutexSharedData::SetIsAbandoned(bool isAbandoned) -{ - _ASSERTE(IsLockOwnedByCurrentThread()); - _ASSERTE(m_isAbandoned != isAbandoned); - - m_isAbandoned = isAbandoned; -} - -bool NamedMutexSharedData::IsLockOwnedByAnyThread() const -{ - return - m_lockOwnerProcessId != SharedMemoryHelpers::InvalidProcessId || - m_lockOwnerThreadId != SharedMemoryHelpers::InvalidSharedThreadId; -} - -bool NamedMutexSharedData::IsLockOwnedByCurrentThread() const -{ - return m_lockOwnerProcessId == GetCurrentProcessId() && m_lockOwnerThreadId == THREADSilentGetCurrentThreadId(); -} - -void NamedMutexSharedData::SetLockOwnerToCurrentThread() -{ - m_lockOwnerProcessId = GetCurrentProcessId(); - _ASSERTE(m_lockOwnerProcessId != SharedMemoryHelpers::InvalidProcessId); - m_lockOwnerThreadId = THREADSilentGetCurrentThreadId(); - _ASSERTE(m_lockOwnerThreadId != SharedMemoryHelpers::InvalidSharedThreadId); -} - -void NamedMutexSharedData::ClearLockOwner() -{ - _ASSERTE(IsLockOwnedByCurrentThread()); - - m_lockOwnerProcessId = SharedMemoryHelpers::InvalidProcessId; - m_lockOwnerThreadId = SharedMemoryHelpers::InvalidSharedThreadId; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// NamedMutexProcessData - -// This value should only be incremented if a non-backward-compatible change to the sync system is made. A process would fail to -// open a mutex created with a different sync system version. -const UINT8 NamedMutexProcessData::SyncSystemVersion = 1; - -const DWORD NamedMutexProcessData::PollLoopMaximumSleepMilliseconds = 100; - -SharedMemoryProcessDataHeader *NamedMutexProcessData::CreateOrOpen( - SharedMemorySystemCallErrors *errors, - LPCSTR name, - bool isUserScope, - bool acquireLockIfCreated, - bool *createdRef) -{ - return CreateOrOpen(errors, name, isUserScope, true /* createIfNotExist */, acquireLockIfCreated, createdRef); -} - -SharedMemoryProcessDataHeader *NamedMutexProcessData::Open(SharedMemorySystemCallErrors *errors, LPCSTR name, bool isUserScope) -{ - return - CreateOrOpen( - errors, - name, - isUserScope, - false /* createIfNotExist */, - false /* acquireLockIfCreated */, - nullptr /* createdRef */); -} - -SharedMemoryProcessDataHeader *NamedMutexProcessData::CreateOrOpen( - SharedMemorySystemCallErrors *errors, - LPCSTR name, - bool isUserScope, - bool createIfNotExist, - bool acquireLockIfCreated, - bool *createdRef) -{ - _ASSERTE(name != nullptr); - _ASSERTE(createIfNotExist || !acquireLockIfCreated); - -#if !NAMED_MUTEX_USE_PTHREAD_MUTEX - PathCharString lockFilePath; -#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX - - struct AutoCleanup - { - bool m_acquiredCreationDeletionProcessLock; - bool m_acquiredCreationDeletionFileLock; - SharedMemoryProcessDataHeader *m_processDataHeader; - #if !NAMED_MUTEX_USE_PTHREAD_MUTEX - PathCharString *m_lockFilePath; - SIZE_T m_sessionDirectoryPathCharCount; - bool m_createdLockFile; - int m_lockFileDescriptor; - #endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX - bool m_cancel; - - AutoCleanup() - : m_acquiredCreationDeletionProcessLock(false), - m_acquiredCreationDeletionFileLock(false), - m_processDataHeader(nullptr), - #if !NAMED_MUTEX_USE_PTHREAD_MUTEX - m_lockFilePath(nullptr), - m_sessionDirectoryPathCharCount(0), - m_createdLockFile(false), - m_lockFileDescriptor(-1), - #endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX - m_cancel(false) - { - } - - ~AutoCleanup() - { - #if !NAMED_MUTEX_USE_PTHREAD_MUTEX - if (!m_cancel) - { - if (m_lockFileDescriptor != -1) - { - SharedMemoryHelpers::CloseFile(m_lockFileDescriptor); - } - - if (m_createdLockFile) - { - _ASSERTE(m_lockFilePath != nullptr); - unlink(*m_lockFilePath); - } - - if (m_sessionDirectoryPathCharCount != 0) - { - _ASSERTE(m_lockFilePath != nullptr); - m_lockFilePath->CloseBuffer(m_sessionDirectoryPathCharCount); - rmdir(*m_lockFilePath); - } - } - #endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX - - if (m_acquiredCreationDeletionFileLock) - { - _ASSERTE(m_processDataHeader != nullptr); - SharedMemoryManager::ReleaseCreationDeletionFileLock(m_processDataHeader->GetId()); - } - - if (!m_cancel && m_processDataHeader != nullptr) - { - _ASSERTE(m_acquiredCreationDeletionProcessLock); - m_processDataHeader->DecRefCount(); - } - - if (m_acquiredCreationDeletionProcessLock) - { - SharedMemoryManager::ReleaseCreationDeletionProcessLock(); - } - } - } autoCleanup; - - SharedMemoryManager::AcquireCreationDeletionProcessLock(); - autoCleanup.m_acquiredCreationDeletionProcessLock = true; - - // Create or open the shared memory - bool created; - SharedMemoryProcessDataHeader *processDataHeader = - SharedMemoryProcessDataHeader::CreateOrOpen( - errors, - name, - isUserScope, - SharedMemorySharedDataHeader(SharedMemoryType::Mutex, SyncSystemVersion), - sizeof(NamedMutexSharedData), - createIfNotExist, - &created); - if (createdRef != nullptr) - { - *createdRef = created; - } - if (processDataHeader == nullptr) - { - _ASSERTE(!created); - _ASSERTE(!createIfNotExist); - return nullptr; - } - if (created) - { - // If the shared memory file was created, the creation/deletion file lock would have been acquired so that we can - // initialize the shared data - _ASSERTE(SharedMemoryManager::IsCreationDeletionFileLockAcquired()); - autoCleanup.m_acquiredCreationDeletionFileLock = true; - } - autoCleanup.m_processDataHeader = processDataHeader; - - if (created) - { - // Initialize the shared data - new(processDataHeader->GetSharedDataHeader()->GetData()) NamedMutexSharedData(errors); - } - - if (processDataHeader->GetData() == nullptr) - { - #if !NAMED_MUTEX_USE_PTHREAD_MUTEX - // Create the lock files directory - const SharedMemoryId *id = processDataHeader->GetId(); - SharedMemoryHelpers::VerifyStringOperation( - lockFilePath.Set(*gSharedFilesPath) && - id->AppendRuntimeTempDirectoryName(lockFilePath) && - lockFilePath.Append('/') && lockFilePath.Append(SHARED_MEMORY_LOCK_FILES_DIRECTORY_NAME)); - if (created) - { - SharedMemoryHelpers::EnsureDirectoryExists(errors, lockFilePath, id, true /* isGlobalLockAcquired */); - } - - // Create the session directory - SharedMemoryHelpers::VerifyStringOperation(lockFilePath.Append('/') && id->AppendSessionDirectoryName(lockFilePath)); - if (created) - { - SharedMemoryHelpers::EnsureDirectoryExists(errors, lockFilePath, id, true /* isGlobalLockAcquired */); - autoCleanup.m_lockFilePath = &lockFilePath; - autoCleanup.m_sessionDirectoryPathCharCount = lockFilePath.GetCount(); - } - - // Create or open the lock file - SharedMemoryHelpers::VerifyStringOperation( - lockFilePath.Append('/') && lockFilePath.Append(id->GetName(), id->GetNameCharCount())); - int lockFileDescriptor = SharedMemoryHelpers::CreateOrOpenFile(errors, lockFilePath, id, created); - if (lockFileDescriptor == -1) - { - _ASSERTE(!created); - if (createIfNotExist) - { - if (errors != nullptr) - { - errors->Append( - "open(\"%s\", O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0) == -1; errno == ENOENT;", - (const char *)lockFilePath); - } - - throw SharedMemoryException(static_cast(SharedMemoryError::IO)); - } - - return nullptr; - } - autoCleanup.m_createdLockFile = created; - autoCleanup.m_lockFileDescriptor = lockFileDescriptor; - #endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX - - // Create the process data - void *processDataBuffer = SharedMemoryHelpers::Alloc(sizeof(NamedMutexProcessData)); - AutoFreeBuffer autoFreeProcessDataBuffer(processDataBuffer); - NamedMutexProcessData *processData = - new(processDataBuffer) - NamedMutexProcessData( - processDataHeader - #if !NAMED_MUTEX_USE_PTHREAD_MUTEX - , - lockFileDescriptor - #endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX - ); - autoFreeProcessDataBuffer.Cancel(); - processDataHeader->SetData(processData); - - // If the mutex was created and if requested, acquire the lock initially while holding the creation/deletion locks - if (created && acquireLockIfCreated) - { - MutexTryAcquireLockResult tryAcquireLockResult = processData->TryAcquireLock(errors, 0); - _ASSERTE(tryAcquireLockResult == MutexTryAcquireLockResult::AcquiredLock); - } - } - - autoCleanup.m_cancel = true; - return processDataHeader; -} - -NamedMutexProcessData::NamedMutexProcessData( - SharedMemoryProcessDataHeader *processDataHeader -#if !NAMED_MUTEX_USE_PTHREAD_MUTEX - , - int sharedLockFileDescriptor -#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX -) - : - m_processDataHeader(processDataHeader), - m_lockCount(0), -#if !NAMED_MUTEX_USE_PTHREAD_MUTEX - m_sharedLockFileDescriptor(sharedLockFileDescriptor), -#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX - m_lockOwnerThread(nullptr), - m_nextInThreadOwnedNamedMutexList(nullptr), - m_hasRefFromLockOwnerThread(false) -{ - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - _ASSERTE(processDataHeader != nullptr); - -#if !NAMED_MUTEX_USE_PTHREAD_MUTEX - _ASSERTE(sharedLockFileDescriptor != -1); - - m_processLockHandle = CreateMutex(nullptr /* lpMutexAttributes */, false /* bInitialOwner */, nullptr /* lpName */); - if (m_processLockHandle == nullptr) - { - throw SharedMemoryException(GetLastError()); - } -#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX -} - -bool NamedMutexProcessData::CanClose() const -{ - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - - // When using a pthread robust mutex, the mutex may only be unlocked and destroyed by the thread that owns the lock. When - // using file locks, even though any thread could release that lock, the behavior is kept consistent to the more - // conservative case. If the last handle to the mutex is closed when a different thread owns the lock, the mutex cannot be - // closed. Due to these limitations, the behavior in this corner case is necessarily different from Windows. The caller will - // extend the lifetime of the mutex and will call OnLifetimeExtendedDueToCannotClose() shortly. - return m_lockOwnerThread == nullptr || m_lockOwnerThread == GetCurrentPalThread(); -} - -bool NamedMutexProcessData::HasImplicitRef() const -{ - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - return m_hasRefFromLockOwnerThread; -} - -void NamedMutexProcessData::SetHasImplicitRef(bool value) -{ - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - _ASSERTE(m_hasRefFromLockOwnerThread != value); - _ASSERTE(!value || !CanClose()); - - // If value == true: - // The mutex could not be closed and the caller extended the lifetime of the mutex. Record that the lock owner thread - // should release the ref when the lock is released on that thread. - // Else: - // The mutex has an implicit ref and got the first explicit reference from this process. Remove the implicit ref from the - // lock owner thread. - m_hasRefFromLockOwnerThread = value; -} - -void NamedMutexProcessData::Close(bool isAbruptShutdown, bool releaseSharedData) -{ - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - _ASSERTE(!releaseSharedData || SharedMemoryManager::IsCreationDeletionFileLockAcquired()); - - // If the process is shutting down abruptly without having closed some mutexes, there could still be threads running with - // active references to the mutex. So when shutting down abruptly, don't clean up any object or global process-local state. - if (!isAbruptShutdown) - { - _ASSERTE(CanClose()); - _ASSERTE(!m_hasRefFromLockOwnerThread); - - CPalThread *lockOwnerThread = m_lockOwnerThread; - if (lockOwnerThread == GetCurrentPalThread()) - { - // The mutex was not released before the last handle to it from this process was closed on the lock-owning thread. - // Another process may still have a handle to the mutex, but since it appears as though this process would not be - // releasing the mutex, abandon the mutex. The only way for this process to otherwise release the mutex is to open - // another handle to it and release the lock on the same thread, which would be incorrect-looking code. The behavior - // in this corner case is different from Windows. - lockOwnerThread->synchronizationInfo.RemoveOwnedNamedMutex(this); - Abandon(); - } - else - { - _ASSERTE(lockOwnerThread == nullptr); - } - - if (releaseSharedData) - { - GetSharedData()->~NamedMutexSharedData(); - } - -#if !NAMED_MUTEX_USE_PTHREAD_MUTEX - CloseHandle(m_processLockHandle); - SharedMemoryHelpers::CloseFile(m_sharedLockFileDescriptor); -#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX - - } - -#if !NAMED_MUTEX_USE_PTHREAD_MUTEX - - if (!releaseSharedData) - { - return; - } - - try - { - // Delete the lock file, and the session directory if it's not empty - PathCharString path; - const SharedMemoryId *id = m_processDataHeader->GetId(); - SharedMemoryHelpers::VerifyStringOperation( - path.Set(*gSharedFilesPath) && - id->AppendRuntimeTempDirectoryName(path) && - path.Append('/') && path.Append(SHARED_MEMORY_LOCK_FILES_DIRECTORY_NAME) && - path.Append('/') && id->AppendSessionDirectoryName(path) && - path.Append('/')); - SIZE_T sessionDirectoryPathCharCount = path.GetCount(); - SharedMemoryHelpers::VerifyStringOperation(path.Append(id->GetName(), id->GetNameCharCount())); - unlink(path); - path.CloseBuffer(sessionDirectoryPathCharCount); - rmdir(path); - } - catch (SharedMemoryException) - { - // Ignore the error, just don't release shared data - } -#endif // !NAMED_MUTEX_USE_PTHREAD_MUTEX -} - -NamedMutexSharedData *NamedMutexProcessData::GetSharedData() const -{ - return reinterpret_cast(m_processDataHeader->GetSharedDataHeader()->GetData()); -} - -void NamedMutexProcessData::SetLockOwnerThread(CorUnix::CPalThread *lockOwnerThread) -{ - _ASSERTE(lockOwnerThread == nullptr || lockOwnerThread == GetCurrentPalThread()); - _ASSERTE(IsLockOwnedByCurrentThread()); - - m_lockOwnerThread = lockOwnerThread; -} - -NamedMutexProcessData *NamedMutexProcessData::GetNextInThreadOwnedNamedMutexList() const -{ - _ASSERTE(IsLockOwnedByCurrentThread()); - return m_nextInThreadOwnedNamedMutexList; -} - -void NamedMutexProcessData::SetNextInThreadOwnedNamedMutexList(NamedMutexProcessData *next) -{ - _ASSERTE(IsLockOwnedByCurrentThread()); - m_nextInThreadOwnedNamedMutexList = next; -} - -MutexTryAcquireLockResult NamedMutexProcessData::TryAcquireLock(SharedMemorySystemCallErrors *errors, DWORD timeoutMilliseconds) -{ - NamedMutexSharedData *sharedData = GetSharedData(); - -#if NAMED_MUTEX_USE_PTHREAD_MUTEX - MutexTryAcquireLockResult result = MutexHelpers::TryAcquireLock(errors, sharedData->GetLock(), timeoutMilliseconds); - if (result == MutexTryAcquireLockResult::TimedOut) - { - return result; - } - - // Check if a recursive lock was just taken. The recursion level is tracked manually so that the lock owner can be cleared - // at the appropriate time, see ReleaseLock(). - if (m_lockCount != 0) - { - _ASSERTE(IsLockOwnedByCurrentThread()); // otherwise, this thread would not have acquired the lock - _ASSERTE(GetCurrentPalThread()->synchronizationInfo.OwnsNamedMutex(this)); - - if (m_lockCount + 1 < m_lockCount) - { - MutexHelpers::ReleaseLock(sharedData->GetLock()); - throw SharedMemoryException(static_cast(NamedMutexError::MaximumRecursiveLocksReached)); - } - ++m_lockCount; - - // The lock is released upon acquiring a recursive lock from the thread that already owns the lock - MutexHelpers::ReleaseLock(sharedData->GetLock()); - - _ASSERTE(result != MutexTryAcquireLockResult::AcquiredLockButMutexWasAbandoned); - _ASSERTE(!sharedData->IsAbandoned()); - return result; - } - - // The non-recursive case is handled below (skip the #else and see below that) -#else // !NAMED_MUTEX_USE_PTHREAD_MUTEX - // If a timeout is specified, determine the start time - DWORD startTime = 0; - if (timeoutMilliseconds != static_cast(-1) && timeoutMilliseconds != 0) - { - startTime = (DWORD)minipal_lowres_ticks(); - } - - // 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. - while (true) - { - DWORD waitResult = WaitForSingleObject(m_processLockHandle, timeoutMilliseconds); - switch (waitResult) - { - case WAIT_OBJECT_0: - case WAIT_ABANDONED: // abandoned state for the process lock is irrelevant, the shared lock will also have been abandoned - break; - - case WAIT_TIMEOUT: - return MutexTryAcquireLockResult::TimedOut; - - case WAIT_IO_COMPLETION: - continue; - - case WAIT_FAILED: - throw SharedMemoryException(GetLastError()); - - default: - _ASSERTE(false); - break; - } - break; - } - - struct AutoReleaseProcessLock - { - HANDLE m_processLockHandle; - bool m_cancel; - - AutoReleaseProcessLock(HANDLE processLockHandle) : m_processLockHandle(processLockHandle), m_cancel(false) - { - } - - ~AutoReleaseProcessLock() - { - if (!m_cancel) - { - ReleaseMutex(m_processLockHandle); - } - } - } autoReleaseProcessLock(m_processLockHandle); - - // Check if it's a recursive lock attempt - if (m_lockCount != 0) - { - _ASSERTE(IsLockOwnedByCurrentThread()); // otherwise, this thread would not have acquired the process lock - _ASSERTE(GetCurrentPalThread()->synchronizationInfo.OwnsNamedMutex(this)); - - if (m_lockCount + 1 < m_lockCount) - { - throw SharedMemoryException(static_cast(NamedMutexError::MaximumRecursiveLocksReached)); - } - ++m_lockCount; - - // The process lock is released upon acquiring a recursive lock from the thread that already owns the lock - return MutexTryAcquireLockResult::AcquiredLock; - } - - switch (timeoutMilliseconds) - { - case static_cast(-1): - { - // The file lock API does not have a timeout on the wait, so timed waiters will poll the file lock in a loop, - // sleeping for a short duration in-between. Due to the polling nature of a timed wait, timed waiters will almost - // never acquire the file lock as long as there are also untimed waiters. So, in order to make the file lock - // acquisition reasonable, when there are timed waiters, have untimed waiters also use polling. - bool acquiredFileLock = false; - while (sharedData->HasAnyTimedWaiters()) - { - if (SharedMemoryHelpers::TryAcquireFileLock(errors, m_sharedLockFileDescriptor, LOCK_EX | LOCK_NB)) - { - acquiredFileLock = true; - break; - } - Sleep(PollLoopMaximumSleepMilliseconds); - } - if (acquiredFileLock) - { - break; - } - - acquiredFileLock = SharedMemoryHelpers::TryAcquireFileLock(errors, m_sharedLockFileDescriptor, LOCK_EX); - _ASSERTE(acquiredFileLock); - break; - } - - case 0: - if (!SharedMemoryHelpers::TryAcquireFileLock(errors, m_sharedLockFileDescriptor, LOCK_EX | LOCK_NB)) - { - return MutexTryAcquireLockResult::TimedOut; - } - break; - - default: - { - // Try to acquire the file lock without waiting - if (SharedMemoryHelpers::TryAcquireFileLock(errors, m_sharedLockFileDescriptor, LOCK_EX | LOCK_NB)) - { - break; - } - - // The file lock API does not have a timeout on the wait, so timed waiters need to poll the file lock in a loop, - // sleeping for a short duration in-between. Due to the polling nature of a timed wait, timed waiters will almost - // never acquire the file lock as long as there are also untimed waiters. So, in order to make the file lock - // acquisition reasonable, record that there is a timed waiter, to have untimed waiters also use polling. - sharedData->IncTimedWaiterCount(); - struct AutoDecTimedWaiterCount - { - NamedMutexSharedData *m_sharedData; - - AutoDecTimedWaiterCount(NamedMutexSharedData *sharedData) : m_sharedData(sharedData) - { - } - - ~AutoDecTimedWaiterCount() - { - m_sharedData->DecTimedWaiterCount(); - } - } autoDecTimedWaiterCount(sharedData); - - // Poll for the file lock - do - { - DWORD elapsedMilliseconds = (DWORD)minipal_lowres_ticks() - startTime; - if (elapsedMilliseconds >= timeoutMilliseconds) - { - return MutexTryAcquireLockResult::TimedOut; - } - - DWORD remainingMilliseconds = timeoutMilliseconds - elapsedMilliseconds; - DWORD sleepMilliseconds = - remainingMilliseconds < PollLoopMaximumSleepMilliseconds - ? remainingMilliseconds - : PollLoopMaximumSleepMilliseconds; - Sleep(sleepMilliseconds); - } while (!SharedMemoryHelpers::TryAcquireFileLock(errors, m_sharedLockFileDescriptor, LOCK_EX | LOCK_NB)); - break; - } - } - - // There cannot be any exceptions after this - autoReleaseProcessLock.m_cancel = true; - - // After acquiring the file lock, if we find that a lock owner is already designated, the process that previously owned the - // lock must have terminated while holding the lock. - MutexTryAcquireLockResult result = - sharedData->IsLockOwnedByAnyThread() - ? MutexTryAcquireLockResult::AcquiredLockButMutexWasAbandoned - : MutexTryAcquireLockResult::AcquiredLock; -#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX - - sharedData->SetLockOwnerToCurrentThread(); - m_lockCount = 1; - CPalThread *currentThread = GetCurrentPalThread(); - SetLockOwnerThread(currentThread); - currentThread->synchronizationInfo.AddOwnedNamedMutex(this); - - if (sharedData->IsAbandoned()) - { - // The thread that previously owned the lock did not release it before exiting - sharedData->SetIsAbandoned(false); - result = MutexTryAcquireLockResult::AcquiredLockButMutexWasAbandoned; - } - return result; -} - -void NamedMutexProcessData::ReleaseLock() -{ - if (!IsLockOwnedByCurrentThread()) - { - throw SharedMemoryException(static_cast(NamedMutexError::ThreadHasNotAcquiredMutex)); - } - - _ASSERTE(GetCurrentPalThread()->synchronizationInfo.OwnsNamedMutex(this)); - _ASSERTE(!m_hasRefFromLockOwnerThread); - - _ASSERTE(m_lockCount != 0); - --m_lockCount; - if (m_lockCount != 0) - { - return; - } - - GetCurrentPalThread()->synchronizationInfo.RemoveOwnedNamedMutex(this); - SetLockOwnerThread(nullptr); - ActuallyReleaseLock(); -} - -void NamedMutexProcessData::Abandon() -{ - _ASSERTE(SharedMemoryManager::IsCreationDeletionProcessLockAcquired()); - - NamedMutexSharedData *sharedData = GetSharedData(); - _ASSERTE(IsLockOwnedByCurrentThread()); - _ASSERTE(m_lockCount != 0); - - sharedData->SetIsAbandoned(true); - m_lockCount = 0; - SetLockOwnerThread(nullptr); - ActuallyReleaseLock(); - - if (m_hasRefFromLockOwnerThread) - { - m_hasRefFromLockOwnerThread = false; - m_processDataHeader->DecRefCount(); - } -} - -void NamedMutexProcessData::ActuallyReleaseLock() -{ - _ASSERTE(IsLockOwnedByCurrentThread()); - _ASSERTE(!GetCurrentPalThread()->synchronizationInfo.OwnsNamedMutex(this)); - _ASSERTE(m_lockCount == 0); - - NamedMutexSharedData *sharedData = GetSharedData(); - sharedData->ClearLockOwner(); - -#if NAMED_MUTEX_USE_PTHREAD_MUTEX - MutexHelpers::ReleaseLock(sharedData->GetLock()); -#else // !NAMED_MUTEX_USE_PTHREAD_MUTEX - SharedMemoryHelpers::ReleaseFileLock(m_sharedLockFileDescriptor); - ReleaseMutex(m_processLockHandle); -#endif // NAMED_MUTEX_USE_PTHREAD_MUTEX -} diff --git a/src/coreclr/pal/src/thread/process.cpp b/src/coreclr/pal/src/thread/process.cpp index cb00d493be4fb6..057540776a61d5 100644 --- a/src/coreclr/pal/src/thread/process.cpp +++ b/src/coreclr/pal/src/thread/process.cpp @@ -34,6 +34,7 @@ SET_DEFAULT_DEBUG_CHANNEL(PROCESS); // some headers have code with asserts, so d #include #include +#include #include #if HAVE_POLL @@ -186,7 +187,6 @@ DWORD gSID = (DWORD) -1; LPCSTR gApplicationGroupId = nullptr; int gApplicationGroupIdLength = 0; #endif // __APPLE__ -PathCharString* gSharedFilesPath = nullptr; // The lowest common supported semaphore length, including null character // NetBSD-7.99.25: 15 characters diff --git a/src/coreclr/pal/tests/palsuite/CMakeLists.txt b/src/coreclr/pal/tests/palsuite/CMakeLists.txt index a6338791711233..5b96ab5fa81568 100644 --- a/src/coreclr/pal/tests/palsuite/CMakeLists.txt +++ b/src/coreclr/pal/tests/palsuite/CMakeLists.txt @@ -415,8 +415,6 @@ add_executable_clr(paltests threading/GetCurrentThreadId/test1/threadId.cpp threading/GetExitCodeProcess/test1/childProcess.cpp threading/GetExitCodeProcess/test1/test1.cpp - threading/NamedMutex/test1/namedmutex.cpp - threading/NamedMutex/test1/nopal.cpp threading/OpenEventW/test1/test1.cpp threading/OpenEventW/test2/test2.cpp threading/OpenEventW/test3/childprocess.cpp @@ -459,8 +457,6 @@ add_executable_clr(paltests threading/WaitForMultipleObjectsEx/test4/test4.cpp threading/WaitForMultipleObjectsEx/test5/helper.cpp threading/WaitForMultipleObjectsEx/test5/test5.cpp - threading/WaitForMultipleObjectsEx/test6/child6.cpp - threading/WaitForMultipleObjectsEx/test6/test6.cpp threading/WaitForSingleObject/test1/test1.cpp threading/WaitForSingleObject/WFSOExMutexTest/WFSOExMutexTest.cpp threading/WaitForSingleObject/WFSOExSemaphoreTest/WFSOExSemaphoreTest.cpp diff --git a/src/coreclr/pal/tests/palsuite/compilableTests.txt b/src/coreclr/pal/tests/palsuite/compilableTests.txt index ee5ba036ce7a31..930d5979fa0318 100644 --- a/src/coreclr/pal/tests/palsuite/compilableTests.txt +++ b/src/coreclr/pal/tests/palsuite/compilableTests.txt @@ -312,7 +312,6 @@ threading/GetCurrentThread/test1/paltest_getcurrentthread_test1 threading/GetCurrentThread/test2/paltest_getcurrentthread_test2 threading/GetCurrentThreadId/test1/paltest_getcurrentthreadid_test1 threading/GetExitCodeProcess/test1/paltest_getexitcodeprocess_test1 -threading/NamedMutex/test1/paltest_namedmutex_test1 threading/OpenEventW/test1/paltest_openeventw_test1 threading/OpenEventW/test2/paltest_openeventw_test2 threading/OpenEventW/test3/paltest_openeventw_test3 diff --git a/src/coreclr/pal/tests/palsuite/paltestlist.txt b/src/coreclr/pal/tests/palsuite/paltestlist.txt index b55a93735f16f2..62a943e4726b7c 100644 --- a/src/coreclr/pal/tests/palsuite/paltestlist.txt +++ b/src/coreclr/pal/tests/palsuite/paltestlist.txt @@ -269,7 +269,6 @@ threading/ExitThread/test1/paltest_exitthread_test1 threading/GetCurrentProcessId/test1/paltest_getcurrentprocessid_test1 threading/GetCurrentThread/test1/paltest_getcurrentthread_test1 threading/GetCurrentThread/test2/paltest_getcurrentthread_test2 -threading/NamedMutex/test1/paltest_namedmutex_test1 threading/QueryThreadCycleTime/test1/paltest_querythreadcycletime_test1 threading/QueueUserAPC/test2/paltest_queueuserapc_test2 threading/QueueUserAPC/test3/paltest_queueuserapc_test3 diff --git a/src/coreclr/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test2/CreateMutexW.cpp b/src/coreclr/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test2/CreateMutexW.cpp index ea50041168746e..5d41efb9d6fa9d 100644 --- a/src/coreclr/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test2/CreateMutexW.cpp +++ b/src/coreclr/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test2/CreateMutexW.cpp @@ -37,8 +37,6 @@ #define szEmpty "" /* Function Prototypes */ -BOOL TestNamedMutex_CreateMutexW_ReleaseMutex_test2(const char *szMutexName); -DWORD NamedMutexThread_CreateMutexW_ReleaseMutex_test2(LPVOID lpParam); BOOL NegativeReleaseMutexTests_CreateMutexW_ReleaseMutex_test2(); struct ThreadData @@ -59,43 +57,6 @@ PALTEST(threading_CreateMutexW_ReleaseMutex_test2_paltest_createmutexw_releasemu return ( FAIL ); } - - /* - * Test named Mutexes with ordinary string - */ - - if (!TestNamedMutex_CreateMutexW_ReleaseMutex_test2(szMutex)) - { - bFailures = TRUE; - } - - - /* - * Test named Mutexes with empty ("") string - */ - - if (!TestNamedMutex_CreateMutexW_ReleaseMutex_test2(szEmpty)) - { - bFailures = TRUE; - } - - - /* - * Test named Mutexes with string of length MAX_LONGPATH - */ - - szMaxPath = (char *)malloc(MAX_LONGPATH+2); - memset(szMaxPath, 'A', MAX_LONGPATH-60); - szMaxPath[MAX_LONGPATH-60] = 0; - - if (!TestNamedMutex_CreateMutexW_ReleaseMutex_test2(szMaxPath)) - { - bFailures = TRUE; - } - - free(szMaxPath); - - /* * Run some negative tests on ReleaseMutex */ @@ -119,141 +80,6 @@ PALTEST(threading_CreateMutexW_ReleaseMutex_test2_paltest_createmutexw_releasemu return ( PASS ); } - -/* - * Testing Function - * - * Try to get multiple handles to a named Mutex and test - * to make sure they actually refer to same Mutex object. - */ -BOOL TestNamedMutex_CreateMutexW_ReleaseMutex_test2(const char *szMutexName) -{ - DWORD dwData; - HANDLE hMutex1; - HANDLE hMutex2; - HANDLE hThread; - WCHAR *swzMutexName; - THREADDATA threadData; - - /* Convert the Mutex name to wide characters */ - swzMutexName = convert((char *)szMutexName); - - /* Create a mutex and take ownership immediately */ - hMutex1 = CreateMutexW (NULL, TRUE, swzMutexName); - - if (NULL == hMutex1) - { - Trace("ERROR: CreateMutex #1 failed. GetLastError returned %u\n", - GetLastError()); - free(swzMutexName); - return FALSE; - } - - /* Try to wait on the Mutex we just created. We should not block. */ - if (WaitForSingleObject(hMutex1, 1000) == WAIT_TIMEOUT) - { - Trace("WaitForSingleObject blocked on a Mutex that we owned.\n"); - free(swzMutexName); - return FALSE; - } - /* We have to call ReleaseMutex here because of the Wait */ - if (ReleaseMutex(hMutex1) == FALSE) - { - Trace("ReleaseMutex Failed.\n"); - return FALSE; - } - - /* Get a second handle to the same mutex */ - hMutex2 = CreateMutexW (NULL, FALSE, swzMutexName); - - if (NULL == hMutex2) - { - Trace("ERROR: CreateMutex #2 failed. GetLastError returned %u\n", - GetLastError()); - free(swzMutexName); - return FALSE; - } - - /* Get rid of the wide character string */ - free(swzMutexName); - - /* - * Create a thread that will Wait on the second handle. - */ - threadData.hMutex = hMutex2; - hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)NamedMutexThread_CreateMutexW_ReleaseMutex_test2, - (LPVOID)&threadData, 0, &dwData); - - if (NULL == hThread) - { - Trace("ERROR: CreateThread failed. GetLastError returned %u\n", - GetLastError()); - return FALSE; - } - - /* Give the thread a little time to execute & wait*/ - Sleep(500); - - /* Signal the first handle */ - if (ReleaseMutex(hMutex1) == FALSE) - { - Trace("ReleaseMutex Failed.\n"); - return FALSE; - } - - /* Give the thread some time to finish */ - Sleep(2000); - - /* Clean Up */ - if (CloseHandle(hMutex1) == FALSE || - CloseHandle(hMutex2) == FALSE || - CloseHandle(hThread) == FALSE) - { - Trace("ERROR: CloseHandle failed.\n"); - return FALSE; - } - - /* Check the return code to see if signalling the first */ - /* Mutex handle woke up the thread which was Waiting on */ - /* the second handle. */ - if (threadData.bReturnCode != FALSE) - { - Trace("ERROR: The handles did not refer to the same Mutex object.\n"); - return FALSE; - } - - return TRUE; -} - - -/* - * Thread function used with above testing function. - */ -DWORD NamedMutexThread_CreateMutexW_ReleaseMutex_test2(LPVOID lpParam) -{ - BOOL bTimedOut = FALSE; - THREADDATA *lpThreadData = (THREADDATA *)lpParam; - - /* Wait on the Mutex that was passed to us */ - if (WaitForSingleObject(lpThreadData->hMutex, 10000) == WAIT_TIMEOUT) - { - /* The Mutex was not signaled in the allotted time */ - bTimedOut = TRUE; - } - if (ReleaseMutex(lpThreadData->hMutex) == FALSE) - { - Trace("ERROR: ReleaseMutex failed.\n"); - lpThreadData->bReturnCode = FALSE; - return 0; - } - - /* Indicate whether we timed out Waiting on the Mutex */ - lpThreadData->bReturnCode = bTimedOut; - - return 0; -} - - /* * Testing Function * diff --git a/src/coreclr/pal/tests/palsuite/threading/NamedMutex/test1/namedmutex.cpp b/src/coreclr/pal/tests/palsuite/threading/NamedMutex/test1/namedmutex.cpp deleted file mode 100644 index 340a83e67d2104..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/NamedMutex/test1/namedmutex.cpp +++ /dev/null @@ -1,1361 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// These test cases test named mutexes, including positive -// and negative cases, cross - thread and cross - process, mutual -// exclusion, abandon detection, etc. - -#include - -const char CurrentSessionOnlyPrefix[] = "Local\\"; -const char AllSessionsPrefix[] = "Global\\"; - -const char NamePrefix[] = "paltest_namedmutex_test1_"; -const char TempNamePrefix[] = "paltest_namedmutex_test1_temp_"; -const char HeaderMismatchTestsNamePrefix[] = "paltest_namedmutex_test1_headermismatchtests_"; -const char InvalidNamePrefix0[] = "paltest\\namedmutex_"; -const char InvalidNamePrefix1[] = "paltest/namedmutex_"; -const char ParentEventNamePrefix0[] = "paltest_namedmutex_test1_pe0_"; -const char ParentEventNamePrefix1[] = "paltest_namedmutex_test1_pe1_"; -const char ChildEventNamePrefix0[] = "paltest_namedmutex_test1_ce0_"; -const char ChildEventNamePrefix1[] = "paltest_namedmutex_test1_ce1_"; -const char ChildRunningEventNamePrefix[] = "paltest_namedmutex_test1_cr_"; - -#define MaxPathSize 200 -const DWORD PollLoopSleepMilliseconds = 100; -const DWORD FailTimeoutMilliseconds = 30000; -DWORD g_expectedTimeoutMilliseconds = 500; - -bool g_isParent = true; -bool g_currentUserOnly = true; -bool g_currentSessionOnly = true; -bool g_isStress = false; -#define MaxProcessPathSize 4096 -char g_processPath[MaxProcessPathSize], g_processCommandLinePath[MaxProcessPathSize]; -DWORD g_parentPid = static_cast(-1); - -extern char *(*test_strcpy)(char *dest, const char *src); -extern int (*test_strcmp)(const char *s1, const char *s2); -extern size_t (*test_strlen)(const char *s); -extern int (*test_snprintf)(char *str, size_t size, const char *format, ...); -extern int (*test_sscanf)(const char *str, const char *format, ...); -extern int(*test_close)(int fd); -extern int (*test_unlink)(const char *pathname); -extern unsigned int test_getpid(); -extern unsigned int test_getsid(); -extern unsigned int test_geteuid(); -extern int test_kill(unsigned int pid); - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Test helpers - -extern bool TestFileExists(const char *path); -extern bool WriteHeaderInfo(const char *path, bool currentUserOnly, char sharedMemoryType, char version, int *fdRef); - -#define TestAssert(expression) \ - do \ - { \ - if (!(expression)) \ - { \ - if (!g_isParent) \ - { \ - Trace( \ - "'paltest_namedmutex_test1' child process failed at line %u. CurrentUserOnly: %d, CurrentSessionOnly: %d. Expression: " #expression "\n", \ - __LINE__, \ - (int)g_currentUserOnly, \ - (int)g_currentSessionOnly); \ - } \ - else \ - { \ - Trace( \ - "'paltest_namedmutex_test1' failed at line %u. CurrentUserOnly: %d, CurrentSessionOnly: %d. Expression: " #expression "\n", \ - __LINE__, \ - (int)g_currentUserOnly, \ - (int)g_currentSessionOnly); \ - } \ - fflush(stdout); \ - return false; \ - } \ - } while(false) - -char *BuildName(const char *testName, char *buffer, const char *namePrefix = nullptr) -{ - size_t nameLength = 0; - if (!g_currentSessionOnly) - { - test_strcpy(&buffer[nameLength], AllSessionsPrefix); - nameLength += STRING_LENGTH(AllSessionsPrefix); - } - - if (namePrefix != nullptr) - { - nameLength += test_snprintf(&buffer[nameLength], MaxPathSize - nameLength, "%s", namePrefix); - } - - if (g_isStress) - { - // Append the test name so that tests can run in parallel - nameLength += test_snprintf(&buffer[nameLength], MaxPathSize - nameLength, "%s_", testName); - } - - nameLength += test_snprintf(&buffer[nameLength], MaxPathSize - nameLength, "%u", g_parentPid); - return buffer; -} - -char *BuildShmFilePath(const char *testName, char *buffer, const char *namePrefix) -{ - size_t pathLength = 0; - if (g_currentUserOnly) - { - pathLength += test_snprintf(&buffer[pathLength], MaxPathSize - pathLength, "/tmp/.dotnet-uid%u/shm/", test_geteuid()); - } - else - { - pathLength += test_snprintf(&buffer[pathLength], MaxPathSize - pathLength, "%s", "/tmp/.dotnet/shm/"); - } - - if (g_currentSessionOnly) - { - pathLength += test_snprintf(&buffer[pathLength], MaxPathSize - pathLength, "session%u/", test_getsid()); - } - else - { - pathLength += test_snprintf(&buffer[pathLength], MaxPathSize - pathLength, "%s", "global/"); - } - - pathLength += test_snprintf(&buffer[pathLength], MaxPathSize - pathLength, "%s", namePrefix); - - if (g_isStress) - { - // Append the test name so that tests can run in parallel - pathLength += test_snprintf(&buffer[pathLength], MaxPathSize - pathLength, "%s_", testName); - } - - pathLength += test_snprintf(&buffer[pathLength], MaxPathSize - pathLength, "%u", g_parentPid); - return buffer; -} - -class AutoCloseMutexHandle -{ -private: - HANDLE m_handle; - -public: - AutoCloseMutexHandle(HANDLE handle = nullptr) : m_handle(handle) - { - } - - ~AutoCloseMutexHandle() - { - Close(); - } - -public: - HANDLE GetHandle() const - { - return m_handle; - } - - bool Release() - { - return !!ReleaseMutex(m_handle); - } - - void Close() - { - if (m_handle != nullptr) - { - CloseHandle(m_handle); - m_handle = nullptr; - } - } - - void Abandon() - { - // Don't close the handle - m_handle = nullptr; - } - - AutoCloseMutexHandle &operator =(HANDLE handle) - { - Close(); - m_handle = handle; - return *this; - } - - operator HANDLE() const - { - return m_handle; - } - -private: - AutoCloseMutexHandle(const AutoCloseMutexHandle &other); - AutoCloseMutexHandle(AutoCloseMutexHandle &&other); - AutoCloseMutexHandle &operator =(const AutoCloseMutexHandle &other); -}; - -void TestCreateMutex(AutoCloseMutexHandle &m, const char *name, bool initiallyOwned = false) -{ - m.Close(); - LPWSTR nameW = convert(name); - m = PAL_CreateMutexW(initiallyOwned, nameW, g_currentUserOnly, nullptr, 0); - free(nameW); -} - -HANDLE TestOpenMutex(const char *name) -{ - LPWSTR nameW = convert(name); - HANDLE h = PAL_OpenMutexW(nameW, g_currentUserOnly, nullptr, 0); - free(nameW); - return h; -} - -bool StartProcess(const char *funcName) -{ - // Command line format: - // <0|1> /* currentUserOnly */ <0|1> /* currentSessionOnly */ [stress] - test_snprintf( - g_processCommandLinePath, - MaxProcessPathSize, - "\"%s\" %s %u %s %u %u%s", - g_processPath, - "threading/NamedMutex/test1/paltest_namedmutex_test1", - g_parentPid, - funcName, - g_currentUserOnly ? 1 : 0, - g_currentSessionOnly ? 1 : 0, - g_isStress ? " stress" : ""); - - STARTUPINFO si; - memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); - PROCESS_INFORMATION pi; - memset(&pi, 0, sizeof(pi)); - LPWSTR nameW = convert(g_processCommandLinePath); - if (!CreateProcessW(nullptr, nameW, nullptr, nullptr, false, 0, nullptr, nullptr, &si, &pi)) - { - free(nameW); - return false; - } - - free(nameW); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - return true; -} - -bool StartThread(LPTHREAD_START_ROUTINE func, void *arg = nullptr, HANDLE *threadHandleRef = nullptr) -{ - DWORD threadId; - HANDLE handle = CreateThread(nullptr, 0, func, arg, 0, &threadId); - if (handle != nullptr) - { - if (threadHandleRef == nullptr) - { - CloseHandle(handle); - } - else - { - *threadHandleRef = handle; - } - return true; - } - return false; -} - -bool WaitForMutexToBeCreated(const char *testName, AutoCloseMutexHandle &m, const char *eventNamePrefix) -{ - char eventName[MaxPathSize]; - BuildName(testName, eventName, eventNamePrefix); - DWORD startTime = (DWORD)minipal_lowres_ticks(); - while (true) - { - m = TestOpenMutex(eventName); - if (m != nullptr) - { - return true; - } - if ((DWORD)minipal_lowres_ticks() - startTime >= FailTimeoutMilliseconds) - { - return false; - } - Sleep(PollLoopSleepMilliseconds); - } -} - -// The following functions are used for parent/child tests, where the child runs in a separate thread or process. The tests are -// organized such that one the parent or child is ever running code, and they yield control and wait for the other. Since the -// named mutex is the only type of cross-process sync object available, they are used as events to synchronize. The parent and -// child have a pair of event mutexes each, which they own initially. To release the other waiting thread/process, the -// thread/process releases one of its mutexes, which the other thread/process would be waiting on. To wait, the thread/process -// waits on one of the other thread/process' mutexes. All the while, they ping-pong between the two mutexes. YieldToChild() and -// YieldToParent() below control the releasing, waiting, and ping-ponging, to help create a deterministic path through the -// parent and child tests while both are running concurrently. - -bool AcquireChildRunningEvent(const char *testName, AutoCloseMutexHandle &childRunningEvent) -{ - char name[MaxPathSize]; - TestCreateMutex(childRunningEvent, BuildName(testName, name, ChildRunningEventNamePrefix)); - TestAssert(WaitForSingleObject(childRunningEvent, FailTimeoutMilliseconds) == WAIT_OBJECT_0); - return true; -} - -bool InitializeParent(const char *testName, AutoCloseMutexHandle parentEvents[2], AutoCloseMutexHandle childEvents[2]) -{ - // Create parent events - char name[MaxPathSize]; - for (int i = 0; i < 2; ++i) - { - TestCreateMutex( - parentEvents[i], - BuildName(testName, name, i == 0 ? ParentEventNamePrefix0 : ParentEventNamePrefix1), - true); - TestAssert(parentEvents[i] != nullptr); - TestAssert(GetLastError() != ERROR_ALREADY_EXISTS); - } - - // Wait for the child to create and acquire locks on its events so that the parent can wait on them - TestAssert(WaitForMutexToBeCreated(testName, childEvents[0], ChildEventNamePrefix0)); - TestAssert(WaitForMutexToBeCreated(testName, childEvents[1], ChildEventNamePrefix1)); - return true; -} - -bool UninitializeParent(const char *testName, AutoCloseMutexHandle parentEvents[2], bool releaseParentEvents = true) -{ - if (releaseParentEvents) - { - TestAssert(parentEvents[0].Release()); - TestAssert(parentEvents[1].Release()); - } - - // Wait for the child to finish its test. Child tests will release and close 'childEvents' before releasing - // 'childRunningEvent', so after this wait, the parent process can freely start another child that will deterministically - // recreate the 'childEvents', which the next parent test will wait on, upon its initialization. - AutoCloseMutexHandle childRunningEvent; - TestAssert(AcquireChildRunningEvent(testName, childRunningEvent)); - TestAssert(childRunningEvent.Release()); - return true; -} - -bool InitializeChild( - const char *testName, - AutoCloseMutexHandle &childRunningEvent, - AutoCloseMutexHandle parentEvents[2], - AutoCloseMutexHandle childEvents[2]) -{ - TestAssert(AcquireChildRunningEvent(testName, childRunningEvent)); - - // Create child events - char name[MaxPathSize]; - for (int i = 0; i < 2; ++i) - { - TestCreateMutex( - childEvents[i], - BuildName(testName, name, i == 0 ? ChildEventNamePrefix0 : ChildEventNamePrefix1), - true); - TestAssert(childEvents[i] != nullptr); - TestAssert(GetLastError() != ERROR_ALREADY_EXISTS); - } - - // Wait for the parent to create and acquire locks on its events so that the child can wait on them - TestAssert(WaitForMutexToBeCreated(testName, parentEvents[0], ParentEventNamePrefix0)); - TestAssert(WaitForMutexToBeCreated(testName, parentEvents[1], ParentEventNamePrefix1)); - - // Parent/child tests start with the parent, so after initialization, wait for the parent to tell the child test to start - TestAssert(WaitForSingleObject(parentEvents[0], FailTimeoutMilliseconds) == WAIT_OBJECT_0); - TestAssert(parentEvents[0].Release()); - return true; -} - -bool UninitializeChild( - AutoCloseMutexHandle &childRunningEvent, - AutoCloseMutexHandle parentEvents[2], - AutoCloseMutexHandle childEvents[2]) -{ - // Release and close 'parentEvents' and 'childEvents' before releasing 'childRunningEvent' to avoid races, see - // UninitializeParent() for more info - TestAssert(childEvents[0].Release()); - TestAssert(childEvents[1].Release()); - childEvents[0].Close(); - childEvents[1].Close(); - parentEvents[0].Close(); - parentEvents[1].Close(); - TestAssert(childRunningEvent.Release()); - return true; -} - -bool YieldToChild(AutoCloseMutexHandle parentEvents[2], AutoCloseMutexHandle childEvents[2], int &ei) -{ - TestAssert(parentEvents[ei].Release()); - TestAssert(WaitForSingleObject(childEvents[ei], FailTimeoutMilliseconds) == WAIT_OBJECT_0); - TestAssert(childEvents[ei].Release()); - TestAssert(WaitForSingleObject(parentEvents[ei], 0) == WAIT_OBJECT_0); - ei = 1 - ei; - return true; -} - -bool YieldToParent(AutoCloseMutexHandle parentEvents[2], AutoCloseMutexHandle childEvents[2], int &ei) -{ - TestAssert(childEvents[ei].Release()); - ei = 1 - ei; - TestAssert(WaitForSingleObject(parentEvents[ei], FailTimeoutMilliseconds) == WAIT_OBJECT_0); - TestAssert(parentEvents[ei].Release()); - TestAssert(WaitForSingleObject(childEvents[1 - ei], 0) == WAIT_OBJECT_0); - return true; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Tests - -bool NameTests() -{ - const char *testName = "NameTests"; - - AutoCloseMutexHandle m; - char name[MaxPathSize]; - - // Empty name - TestCreateMutex(m, ""); - TestAssert(m != nullptr); - - // Normal name - BuildName(testName, name, NamePrefix); - TestCreateMutex(m, name); - TestAssert(m != nullptr); - TestAssert(AutoCloseMutexHandle(TestOpenMutex(name)) != nullptr); - if (g_currentSessionOnly) - { - // When creating or opening a mutex scoped to the current session, the prefix ("Local\") is optional - char nameWithExplicitPrefix[MaxPathSize]; - test_strcpy(nameWithExplicitPrefix, CurrentSessionOnlyPrefix); - BuildName(testName, &nameWithExplicitPrefix[STRING_LENGTH(CurrentSessionOnlyPrefix)], NamePrefix); - TestAssert(AutoCloseMutexHandle(TestOpenMutex(nameWithExplicitPrefix)) != nullptr); - TestCreateMutex(m, nameWithExplicitPrefix); - TestAssert(m != nullptr); - TestAssert(AutoCloseMutexHandle(TestOpenMutex(name)) != nullptr); - } - - // Name too long. The maximum allowed path length depends on the file system, so we're not checking for that. - if(g_currentSessionOnly) - { - char name[257]; - memset(name, 'a', STRING_LENGTH(name)); - name[STRING_LENGTH(name)] = '\0'; - TestCreateMutex(m, name); - TestAssert(m == nullptr); - TestAssert(GetLastError() == ERROR_FILENAME_EXCED_RANGE); - TestAssert(AutoCloseMutexHandle(TestOpenMutex(name)) == nullptr); - TestAssert(GetLastError() == ERROR_FILENAME_EXCED_RANGE); - - name[STRING_LENGTH(name) - 1] = '\0'; - TestCreateMutex(m, name); - TestAssert(m != nullptr); - } - else - { - char name[STRING_LENGTH(AllSessionsPrefix) + 257]; - test_strcpy(name, AllSessionsPrefix); - memset(&name[STRING_LENGTH(AllSessionsPrefix)], 'a', STRING_LENGTH(name) - STRING_LENGTH(AllSessionsPrefix)); - name[STRING_LENGTH(name)] = '\0'; - TestCreateMutex(m, name); - TestAssert(m == nullptr); - TestAssert(GetLastError() == ERROR_FILENAME_EXCED_RANGE); - TestAssert(AutoCloseMutexHandle(TestOpenMutex(name)) == nullptr); - TestAssert(GetLastError() == ERROR_FILENAME_EXCED_RANGE); - - name[STRING_LENGTH(name) - 1] = '\0'; - TestCreateMutex(m, name); - TestAssert(m != nullptr); - } - - // Invalid characters in name - BuildName(testName, name, InvalidNamePrefix0); - TestCreateMutex(m, name); - TestAssert(m == nullptr); - TestAssert(GetLastError() == ERROR_INVALID_NAME); - TestAssert(AutoCloseMutexHandle(TestOpenMutex(name)) == nullptr); - TestAssert(GetLastError() == ERROR_INVALID_NAME); - BuildName(testName, name, InvalidNamePrefix1); - TestCreateMutex(m, name); - TestAssert(m == nullptr); - TestAssert(GetLastError() == ERROR_INVALID_NAME); - TestAssert(AutoCloseMutexHandle(TestOpenMutex(name)) == nullptr); - TestAssert(GetLastError() == ERROR_INVALID_NAME); - - // Creating a second reference to the same named mutex yields an error indicating that it was opened, not created - { - BuildName(testName, name, NamePrefix); - TestCreateMutex(m, name); - TestAssert(m != nullptr); - AutoCloseMutexHandle m2; - TestCreateMutex(m2, name); - TestAssert(m2 != nullptr); - TestAssert(GetLastError() == ERROR_ALREADY_EXISTS); - } - - return true; -} - -bool HeaderMismatchTests() -{ - const char *testName = "HeaderMismatchTests"; - - AutoCloseMutexHandle m, m2; - char name[MaxPathSize], path[MaxPathSize]; - int fd; - - // Create and hold onto a mutex during this test to create the shared memory directory - TestCreateMutex(m2, BuildName(testName, name, TempNamePrefix)); - TestAssert(m2 != nullptr); - - // Init name and path for the remaining tests - BuildName(testName, name, HeaderMismatchTestsNamePrefix); - BuildShmFilePath(testName, path, HeaderMismatchTestsNamePrefix); - - // Unknown shared memory type - TestAssert(WriteHeaderInfo(path, g_currentUserOnly, -1, 1, &fd)); - TestCreateMutex(m, name); - TestAssert(m == nullptr); - TestAssert(GetLastError() == ERROR_INVALID_HANDLE); - TestAssert(test_close(fd) == 0); - TestAssert(test_unlink(path) == 0); - - // Mismatched version - TestAssert(WriteHeaderInfo(path, g_currentUserOnly, 0, -1, &fd)); - TestCreateMutex(m, name); - TestAssert(m == nullptr); - TestAssert(GetLastError() == ERROR_INVALID_HANDLE); - TestAssert(test_close(fd) == 0); - TestAssert(test_unlink(path) == 0); - - return true; -} - -bool MutualExclusionTests_Parent() -{ - const char *testName = "MutualExclusionTests"; - - AutoCloseMutexHandle parentEvents[2], childEvents[2]; - TestAssert(InitializeParent(testName, parentEvents, childEvents)); - int ei = 0; - char name[MaxPathSize]; - AutoCloseMutexHandle m; - - TestCreateMutex(m, BuildName(testName, name, NamePrefix)); - TestAssert(m != nullptr); - - // Recursive locking with various timeouts - TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); - TestAssert(WaitForSingleObject(m, FailTimeoutMilliseconds) == WAIT_OBJECT_0); - TestAssert(WaitForSingleObject(m, static_cast(-1)) == WAIT_OBJECT_0); - TestAssert(m.Release()); - TestAssert(m.Release()); - TestAssert(m.Release()); - TestAssert(!m.Release()); // try to release the lock while nobody owns it, and verify recursive lock counting - TestAssert(GetLastError() == ERROR_NOT_OWNER); - - TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child takes the lock - - TestAssert(WaitForSingleObject(m, 0) == WAIT_TIMEOUT); // try to lock the mutex without waiting - TestAssert(WaitForSingleObject(m, g_expectedTimeoutMilliseconds) == WAIT_TIMEOUT); // try to lock the mutex with a timeout - TestAssert(!m.Release()); // try to release the lock while another thread owns it - TestAssert(GetLastError() == ERROR_NOT_OWNER); - - TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child releases the lock - - TestAssert(WaitForSingleObject(m, static_cast(-1)) == WAIT_OBJECT_0); // lock the mutex with no timeout and release - TestAssert(m.Release()); - - TestAssert(UninitializeParent(testName, parentEvents)); - return true; -} - -DWORD PALAPI MutualExclusionTests_Child(void *arg = nullptr) -{ - const char *testName = "MutualExclusionTests"; - - AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; - TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); - int ei = 0; - - { - char name[MaxPathSize]; - AutoCloseMutexHandle m; - - TestCreateMutex(m, BuildName(testName, name, NamePrefix)); - TestAssert(m != nullptr); - TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); // lock the mutex - YieldToParent(parentEvents, childEvents, ei); // parent attempts to lock/release, and fails - TestAssert(m.Release()); // release the lock - } - - TestAssert(UninitializeChild(childRunningEvent, parentEvents, childEvents)); - return 0; -} - -bool MutualExclusionTests() -{ - const char *testName = "MutualExclusionTests"; - - { - AutoCloseMutexHandle m; - char name[MaxPathSize]; - - // Releasing a lock that is not owned by any thread fails - TestCreateMutex(m, BuildName(testName, name, NamePrefix)); - TestAssert(m != nullptr); - TestAssert(!m.Release()); - TestAssert(GetLastError() == ERROR_NOT_OWNER); - - // Acquire a lock during upon creation, and release - TestCreateMutex(m, BuildName(testName, name, NamePrefix), true); - TestAssert(m != nullptr); - TestAssert(m.Release()); - - // Multi-waits including a named mutex are not supported - AutoCloseMutexHandle m2; - TestCreateMutex(m2, nullptr); - TestAssert(m2 != nullptr); - HANDLE waitHandles[] = {m2.GetHandle(), m.GetHandle()}; - TestAssert( - WaitForMultipleObjects( - ARRAY_SIZE(waitHandles), - waitHandles, - false /* waitAll */, - FailTimeoutMilliseconds) == - WAIT_FAILED); - TestAssert(GetLastError() == ERROR_NOT_SUPPORTED); - TestAssert( - WaitForMultipleObjects( - ARRAY_SIZE(waitHandles), - waitHandles, - true /* waitAll */, - FailTimeoutMilliseconds) == - WAIT_FAILED); - TestAssert(GetLastError() == ERROR_NOT_SUPPORTED); - } - - // When another thread or process owns the lock, this process should not be able to acquire a lock, and the converse - TestAssert(StartThread(MutualExclusionTests_Child)); - TestAssert(MutualExclusionTests_Parent()); - TestAssert(StartProcess("MutualExclusionTests_Child")); - TestAssert(MutualExclusionTests_Parent()); - - return true; -} - -bool LifetimeTests_Parent() -{ - const char *testName = "LifetimeTests"; - - AutoCloseMutexHandle parentEvents[2], childEvents[2]; - TestAssert(InitializeParent(testName, parentEvents, childEvents)); - int ei = 0; - char name[MaxPathSize]; - AutoCloseMutexHandle m; - - TestCreateMutex(m, BuildName(testName, name, NamePrefix)); // create first reference to mutex - TestAssert(m != nullptr); - TestAssert(TestFileExists(BuildShmFilePath(testName, name, NamePrefix))); - TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child creates second reference to mutex using CreateMutex - m.Close(); // close first reference - TestAssert(TestFileExists(BuildShmFilePath(testName, name, NamePrefix))); - TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child closes second reference - TestAssert(!TestFileExists(BuildShmFilePath(testName, name, NamePrefix))); - - TestCreateMutex(m, BuildName(testName, name, NamePrefix)); // create first reference to mutex - TestAssert(m != nullptr); - TestAssert(TestFileExists(BuildShmFilePath(testName, name, NamePrefix))); - TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child creates second reference to mutex using OpenMutex - m.Close(); // close first reference - TestAssert(TestFileExists(BuildShmFilePath(testName, name, NamePrefix))); - TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child closes second reference - TestAssert(!TestFileExists(BuildShmFilePath(testName, name, NamePrefix))); - - TestAssert(UninitializeParent(testName, parentEvents)); - return true; -} - -DWORD PALAPI LifetimeTests_Child(void *arg = nullptr) -{ - const char *testName = "LifetimeTests"; - - AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; - TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); - int ei = 0; - - { - char name[MaxPathSize]; - AutoCloseMutexHandle m; - - // ... parent creates first reference to mutex - TestCreateMutex(m, BuildName(testName, name, NamePrefix)); // create second reference to mutex using CreateMutex - TestAssert(m != nullptr); - TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent closes first reference - m.Close(); // close second reference - - TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent verifies, and creates first reference to mutex again - m = TestOpenMutex(BuildName(testName, name, NamePrefix)); // create second reference to mutex using OpenMutex - TestAssert(m != nullptr); - TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent closes first reference - m.Close(); // close second reference - - TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent verifies - } - - TestAssert(UninitializeChild(childRunningEvent, parentEvents, childEvents)); - return 0; -} - -bool LifetimeTests() -{ - const char *testName = "LifetimeTests"; - - { - AutoCloseMutexHandle m; - char name[MaxPathSize]; - - // Shm file should be created and deleted - TestCreateMutex(m, BuildName(testName, name, NamePrefix)); - TestAssert(m != nullptr); - TestAssert(TestFileExists(BuildShmFilePath(testName, name, NamePrefix))); - m.Close(); - TestAssert(!TestFileExists(BuildShmFilePath(testName, name, NamePrefix))); - } - - // Shm file should not be deleted until last reference is released - TestAssert(StartThread(LifetimeTests_Child)); - TestAssert(LifetimeTests_Parent()); - TestAssert(StartProcess("LifetimeTests_Child")); - TestAssert(LifetimeTests_Parent()); - - return true; -} - -DWORD PALAPI AbandonTests_Child_TryLock(void *arg = nullptr); - -bool AbandonTests_Parent() -{ - const char *testName = "AbandonTests"; - - char name[MaxPathSize]; - AutoCloseMutexHandle m; - { - AutoCloseMutexHandle parentEvents[2], childEvents[2]; - TestAssert(InitializeParent(testName, parentEvents, childEvents)); - int ei = 0; - - TestCreateMutex(m, BuildName(testName, name, NamePrefix)); - TestAssert(m != nullptr); - TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child locks mutex - TestAssert(parentEvents[0].Release()); - TestAssert(parentEvents[1].Release()); // child sleeps for short duration and abandons the mutex - TestAssert(WaitForSingleObject(m, FailTimeoutMilliseconds) == WAIT_ABANDONED_0); // attempt to lock and see abandoned mutex - - TestAssert(UninitializeParent(testName, parentEvents, false /* releaseParentEvents */)); // parent events are released above - } - - // Verify that the mutex lock is owned by this thread, by starting a new thread and trying to lock it - TestAssert(StartThread(AbandonTests_Child_TryLock)); - { - AutoCloseMutexHandle parentEvents[2], childEvents[2]; - TestAssert(InitializeParent(testName, parentEvents, childEvents)); - int ei = 0; - - TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child tries to lock mutex - - TestAssert(UninitializeParent(testName, parentEvents)); - } - - // Verify that the mutex lock is owned by this thread, by starting a new process and trying to lock it - TestAssert(StartProcess("AbandonTests_Child_TryLock")); - AutoCloseMutexHandle parentEvents[2], childEvents[2]; - TestAssert(InitializeParent(testName, parentEvents, childEvents)); - int ei = 0; - - TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child tries to lock mutex - - // Continue verification - TestAssert(m.Release()); - TestAssert(WaitForSingleObject(m, FailTimeoutMilliseconds) == WAIT_OBJECT_0); // lock again to see it's not abandoned anymore - TestAssert(m.Release()); - - TestAssert(UninitializeParent(testName, parentEvents)); - - // Since the child abandons the mutex, and a child process may not release the file lock on the shared memory file before - // indicating completion to the parent, make sure to delete the shared memory file by repeatedly opening/closing the mutex - // until the parent process becomes the last process to reference the mutex and closing it deletes the file. - DWORD startTime = (DWORD)minipal_lowres_ticks(); - while (true) - { - m.Close(); - if (!TestFileExists(BuildShmFilePath(testName, name, NamePrefix))) - { - break; - } - - TestAssert((DWORD)minipal_lowres_ticks() - startTime < FailTimeoutMilliseconds); - m = TestOpenMutex(BuildName(testName, name, NamePrefix)); - } - - return true; -} - -DWORD PALAPI AbandonTests_Child_GracefulExit_Close(void *arg = nullptr) -{ - const char *testName = "AbandonTests"; - - AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; - TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); - int ei = 0; - - { - char name[MaxPathSize]; - AutoCloseMutexHandle m; - - // ... parent waits for child to lock mutex - TestCreateMutex(m, BuildName(testName, name, NamePrefix)); - TestAssert(m != nullptr); - TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); - TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent waits on mutex - Sleep(g_expectedTimeoutMilliseconds); // wait for parent to wait on mutex - m.Close(); // close mutex without releasing lock - } - - TestAssert(UninitializeChild(childRunningEvent, parentEvents, childEvents)); - return 0; -} - -DWORD AbandonTests_Child_GracefulExit_NoClose(void *arg = nullptr) -{ - const char *testName = "AbandonTests"; - - // This test needs to run in a separate process because it does not close the mutex handle. Running it in a separate thread - // causes the mutex object to retain a reference until the process terminates. - TestAssert(test_getpid() != g_parentPid); - - AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; - TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); - int ei = 0; - - { - char name[MaxPathSize]; - AutoCloseMutexHandle m; - - // ... parent waits for child to lock mutex - TestCreateMutex(m, BuildName(testName, name, NamePrefix)); - TestAssert(m != nullptr); - TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); - TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent waits on mutex - Sleep(g_expectedTimeoutMilliseconds); // wait for parent to wait on mutex - m.Abandon(); // don't close the mutex - } - - TestAssert(UninitializeChild(childRunningEvent, parentEvents, childEvents)); - return 0; -} - -DWORD AbandonTests_Child_AbruptExit(void *arg = nullptr) -{ - const char *testName = "AbandonTests"; - - DWORD currentPid = test_getpid(); - TestAssert(currentPid != g_parentPid); // this test needs to run in a separate process - - { - AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; - TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); - int ei = 0; - - { - char name[MaxPathSize]; - AutoCloseMutexHandle m; - - // ... parent waits for child to lock mutex - TestCreateMutex(m, BuildName(testName, name, NamePrefix)); - TestAssert(m != nullptr); - TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); - TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent waits on mutex - Sleep(g_expectedTimeoutMilliseconds); // wait for parent to wait on mutex - m.Abandon(); // don't close the mutex - } - - TestAssert(UninitializeChild(childRunningEvent, parentEvents, childEvents)); - } - - TestAssert(test_kill(currentPid) == 0); // abandon the mutex abruptly - return 0; -} - -// This child process acquires the mutex lock, creates another child process (to ensure that file locks are not inherited), and -// abandons the mutex abruptly. The second child process detects the abandonment and abandons the mutex again for the parent to -// detect. Issue: https://github.com/dotnet/runtime/issues/11636 -DWORD AbandonTests_Child_FileLocksNotInherited_Parent_AbruptExit(void *arg = nullptr) -{ - const char *testName = "AbandonTests"; - - DWORD currentPid = test_getpid(); - TestAssert(currentPid != g_parentPid); // this test needs to run in a separate process - - { - char name[MaxPathSize]; - AutoCloseMutexHandle m; - - // ... root parent waits for child to lock mutex - TestCreateMutex(m, BuildName(testName, name, NamePrefix)); - TestAssert(m != nullptr); - TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); - - // Start a child process while holding the lock on the mutex, to ensure that file locks are not inherited by the - // immediate child, such that the immediate child would be able to detect the mutex being abandoned below. This process - // does not communicate with the root parent, it only communicates to the immediate child by abandoning the mutex. The - // immediate child communicates with the root parent to complete the test. - TestAssert(StartProcess("AbandonTests_Child_FileLocksNotInherited_Child_AbruptExit")); // immediate child waits on mutex - - Sleep(g_expectedTimeoutMilliseconds); // wait for immediate child to wait on mutex - m.Abandon(); // don't close the mutex - } - - TestAssert(test_kill(currentPid) == 0); // abandon the mutex abruptly - return 0; -} - -DWORD AbandonTests_Child_FileLocksNotInherited_Child_AbruptExit(void *arg = nullptr) -{ - const char *testName = "AbandonTests"; - - DWORD currentPid = test_getpid(); - TestAssert(currentPid != g_parentPid); // this test needs to run in a separate process - - AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; - TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); - int ei = 0; - - { - char name[MaxPathSize]; - AutoCloseMutexHandle m; - - // ... immediate parent expects child to wait on mutex - TestCreateMutex(m, BuildName(testName, name, NamePrefix)); - TestAssert(m != nullptr); - TestAssert(WaitForSingleObject(m, FailTimeoutMilliseconds) == WAIT_ABANDONED_0); // attempt to lock and see abandoned mutex - TestAssert(YieldToParent(parentEvents, childEvents, ei)); // root parent waits on mutex - Sleep(g_expectedTimeoutMilliseconds); // wait for root parent to wait on mutex - m.Close(); // close mutex without releasing lock (root parent expects the mutex to be abandoned) - } - - TestAssert(UninitializeChild(childRunningEvent, parentEvents, childEvents)); - return 0; -} - -DWORD PALAPI AbandonTests_Child_TryLock(void *arg) -{ - const char *testName = "AbandonTests"; - - AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; - TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); - int ei = 0; - - { - char name[MaxPathSize]; - AutoCloseMutexHandle m; - - // ... parent waits for child to lock mutex - TestCreateMutex(m, BuildName(testName, name, NamePrefix)); - TestAssert(m != nullptr); - TestAssert(WaitForSingleObject(m, 0) == WAIT_TIMEOUT); // try to lock the mutex while the parent holds the lock - TestAssert(WaitForSingleObject(m, g_expectedTimeoutMilliseconds) == WAIT_TIMEOUT); - } - - TestAssert(UninitializeChild(childRunningEvent, parentEvents, childEvents)); - return 0; -} - -bool AbandonTests() -{ - // Abandon by graceful exit where the lock owner closes the mutex before releasing it, unblocks a waiter - TestAssert(StartThread(AbandonTests_Child_GracefulExit_Close)); - TestAssert(AbandonTests_Parent()); - TestAssert(StartProcess("AbandonTests_Child_GracefulExit_Close")); - TestAssert(AbandonTests_Parent()); - - // Abandon by graceful exit without closing the mutex unblocks a waiter - TestAssert(StartProcess("AbandonTests_Child_GracefulExit_NoClose")); - TestAssert(AbandonTests_Parent()); - - // Abandon by abrupt exit unblocks a waiter - TestAssert(StartProcess("AbandonTests_Child_AbruptExit")); - TestAssert(AbandonTests_Parent()); - - TestAssert(StartProcess("AbandonTests_Child_FileLocksNotInherited_Parent_AbruptExit")); - TestAssert(AbandonTests_Parent()); - - return true; -} - -bool LockAndCloseWithoutThreadExitTests_Parent_CloseOnSameThread() -{ - const char *testName = "LockAndCloseWithoutThreadExitTests"; - - AutoCloseMutexHandle parentEvents[2], childEvents[2]; - TestAssert(InitializeParent(testName, parentEvents, childEvents)); - int ei = 0; - char name[MaxPathSize]; - AutoCloseMutexHandle m; - - TestCreateMutex(m, BuildName(testName, name, NamePrefix)); - TestAssert(m != nullptr); - - TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child locks mutex and closes second reference to mutex on lock-owner thread - TestAssert(WaitForSingleObject(m, 0) == WAIT_TIMEOUT); // attempt to lock and fail - - TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child closes last reference to mutex on lock-owner thread - TestAssert(WaitForSingleObject(m, 0) == WAIT_ABANDONED_0); // attempt to lock and see abandoned mutex - TestAssert(m.Release()); - - TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child exits - TestAssert(TestFileExists(BuildShmFilePath(testName, name, NamePrefix))); - m.Close(); - TestAssert(!TestFileExists(BuildShmFilePath(testName, name, NamePrefix))); - - TestAssert(UninitializeParent(testName, parentEvents)); - return true; -} - -DWORD PALAPI LockAndCloseWithoutThreadExitTests_Child_CloseOnSameThread(void *arg = nullptr) -{ - const char *testName = "LockAndCloseWithoutThreadExitTests"; - - TestAssert(test_getpid() != g_parentPid); // this test needs to run in a separate process - - AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; - TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); - int ei = 0; - char name[MaxPathSize]; - - // ... parent waits for child to lock and close second reference to mutex - AutoCloseMutexHandle m(TestOpenMutex(BuildName(testName, name, NamePrefix))); - TestAssert(m != nullptr); - TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); - TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, NamePrefix))) != nullptr); - TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent waits for child to close last reference to mutex - - m.Close(); // close mutex on lock-owner thread without releasing lock - TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent verifies while this thread is still active - - TestAssert(UninitializeChild(childRunningEvent, parentEvents, childEvents)); - return 0; -} - -DWORD PALAPI LockAndCloseWithoutThreadExitTests_ChildThread_CloseMutex(void *arg); - -bool LockAndCloseWithoutThreadExitTests_Parent_CloseOnDifferentThread() -{ - const char *testName = "LockAndCloseWithoutThreadExitTests"; - - AutoCloseMutexHandle parentEvents[2], childEvents[2]; - TestAssert(InitializeParent(testName, parentEvents, childEvents)); - int ei = 0; - char name[MaxPathSize]; - AutoCloseMutexHandle m; - - TestCreateMutex(m, BuildName(testName, name, NamePrefix)); - TestAssert(m != nullptr); - - TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child locks mutex and closes second reference to mutex on lock-owner thread - TestAssert(WaitForSingleObject(m, 0) == WAIT_TIMEOUT); // attempt to lock and fail - - TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child closes last reference to mutex on non-lock-owner thread - TestAssert(WaitForSingleObject(m, 0) == WAIT_TIMEOUT); // attempt to lock and fail - m.Close(); - m = TestOpenMutex(BuildName(testName, name, NamePrefix)); - TestAssert(m != nullptr); // child has implicit reference to mutex - - TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child closes new reference to mutex on lock-owner thread - TestAssert(WaitForSingleObject(m, 0) == WAIT_ABANDONED_0); // attempt to lock and see abandoned mutex - TestAssert(m.Release()); - - TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child exits - TestAssert(TestFileExists(BuildShmFilePath(testName, name, NamePrefix))); - m.Close(); - TestAssert(!TestFileExists(BuildShmFilePath(testName, name, NamePrefix))); - - TestAssert(UninitializeParent(testName, parentEvents)); - return true; -} - -DWORD PALAPI LockAndCloseWithoutThreadExitTests_Child_CloseOnDifferentThread(void *arg = nullptr) -{ - const char *testName = "LockAndCloseWithoutThreadExitTests"; - - TestAssert(test_getpid() != g_parentPid); // this test needs to run in a separate process - - AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; - TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); - int ei = 0; - char name[MaxPathSize]; - - // ... parent waits for child to lock and close second reference to mutex - AutoCloseMutexHandle m(TestOpenMutex(BuildName(testName, name, NamePrefix))); - TestAssert(m != nullptr); - TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); - TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, NamePrefix))) != nullptr); - TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent waits for child to close last reference to mutex - - // Close the mutex on a thread that is not the lock-owner thread, without releasing the lock - HANDLE closeMutexThread = nullptr; - TestAssert(StartThread(LockAndCloseWithoutThreadExitTests_ChildThread_CloseMutex, (HANDLE)m, &closeMutexThread)); - TestAssert(closeMutexThread != nullptr); - TestAssert(WaitForSingleObject(closeMutexThread, FailTimeoutMilliseconds) == WAIT_OBJECT_0); - TestAssert(CloseHandle(closeMutexThread)); - m.Abandon(); // mutex is already closed, don't close it again - TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent verifies while this lock-owner thread is still active - - m = TestOpenMutex(BuildName(testName, name, NamePrefix)); - TestAssert(m != nullptr); - m.Close(); // close mutex on lock-owner thread without releasing lock - TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent verifies while this thread is still active - - TestAssert(UninitializeChild(childRunningEvent, parentEvents, childEvents)); - return 0; -} - -DWORD PALAPI LockAndCloseWithoutThreadExitTests_ChildThread_CloseMutex(void *arg) -{ - TestAssert(arg != nullptr); - AutoCloseMutexHandle((HANDLE)arg).Close(); - return 0; -} - -bool LockAndCloseWithoutThreadExitTests() -{ - TestAssert(StartProcess("LockAndCloseWithoutThreadExitTests_Child_CloseOnSameThread")); - TestAssert(LockAndCloseWithoutThreadExitTests_Parent_CloseOnSameThread()); - - TestAssert(StartProcess("LockAndCloseWithoutThreadExitTests_Child_CloseOnDifferentThread")); - TestAssert(LockAndCloseWithoutThreadExitTests_Parent_CloseOnDifferentThread()); - - return true; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Test harness - -bool (*const TestList[])() = -{ - NameTests, - HeaderMismatchTests, - MutualExclusionTests, - LifetimeTests, - AbandonTests, - LockAndCloseWithoutThreadExitTests -}; - -bool RunTests() -{ - const bool Bools[] = {false, true}; - bool allPassed = true; - for (int i = 0; i < ARRAY_SIZE(TestList); i++) - { - for (int j = 0; j < ARRAY_SIZE(Bools); j++) - { - g_currentUserOnly = Bools[j]; - for (int k = 0; k < ARRAY_SIZE(Bools); k++) - { - g_currentSessionOnly = Bools[k]; - if (!TestList[i]()) - { - allPassed = false; - } - } - } - } - - return allPassed; -} - -DWORD g_stressDurationMilliseconds = 0; -LONG g_stressTestCounts[ARRAY_SIZE(TestList)] = {0}; -LONG g_stressResult = true; - -DWORD PALAPI StressTest(void *arg) -{ - // Run the specified test continuously for the stress duration - SIZE_T testIndex = reinterpret_cast(arg); - DWORD startTime = (DWORD)minipal_lowres_ticks(); - do - { - ++g_stressTestCounts[testIndex]; - if (!TestList[testIndex]()) - { - InterlockedExchange(&g_stressResult, false); - break; - } - } while ( - InterlockedCompareExchange(&g_stressResult, false, false) == true && - (DWORD)minipal_lowres_ticks() - startTime < g_stressDurationMilliseconds); - return 0; -} - -bool StressTests(DWORD durationMinutes) -{ - g_isStress = true; - g_expectedTimeoutMilliseconds = 1; - g_stressDurationMilliseconds = durationMinutes * (60 * 1000); - - // Start a thread for each test - HANDLE threadHandles[ARRAY_SIZE(TestList)]; - for (SIZE_T i = 0; i < ARRAY_SIZE(threadHandles); ++i) - { - TestAssert(StartThread(StressTest, reinterpret_cast(i), &threadHandles[i])); - } - - while (true) - { - DWORD waitResult = - WaitForMultipleObjects(ARRAY_SIZE(threadHandles), threadHandles, true /* bWaitAll */, 10 * 1000 /* dwMilliseconds */); - TestAssert(waitResult == WAIT_OBJECT_0 || waitResult == WAIT_TIMEOUT); - if (waitResult == WAIT_OBJECT_0) - { - break; - } - - Trace("'paltest_namedmutex_test1' stress test counts: "); - for (SIZE_T i = 0; i < ARRAY_SIZE(g_stressTestCounts); ++i) - { - if (i != 0) - { - Trace(", "); - } - Trace("%u", g_stressTestCounts[i]); - } - Trace("\n"); - fflush(stdout); - } - - for (SIZE_T i = 0; i < ARRAY_SIZE(threadHandles); ++i) - { - CloseHandle(threadHandles[i]); - } - return static_cast(g_stressResult); -} - -PALTEST(threading_NamedMutex_test1_paltest_namedmutex_test1, "threading/NamedMutex/test1/paltest_namedmutex_test1") -{ - if (argc < 1 || argc > 6) - { - return FAIL; - } - - if (PAL_Initialize(argc, argv) != 0) - { - return FAIL; - } - - test_strcpy(g_processPath, argv[0]); - - if (argc == 1) - { - // Unit test arguments: - - g_parentPid = test_getpid(); - int result = RunTests() ? PASS : FAIL; - ExitProcess(result); - return result; - } - - if (test_strcmp(argv[1], "stress") == 0) - { - // Stress test arguments: stress [durationMinutes] - - DWORD durationMinutes = 1; - if (argc >= 3 && test_sscanf(argv[2], "%u", &durationMinutes) != 1) - { - ExitProcess(FAIL); - return FAIL; - } - - g_parentPid = test_getpid(); - int result = StressTests(durationMinutes) ? PASS : FAIL; - ExitProcess(result); - return result; - } - - // Child test process arguments: - // <0|1> /* currentUserOnly */ <0|1> /* currentSessionOnly */ [stress] - - g_isParent = false; - - if (argc < 5) - { - ExitProcess(FAIL); - return FAIL; - } - - // Get parent process' ID from argument - if (test_sscanf(argv[1], "%u", &g_parentPid) != 1) - { - ExitProcess(FAIL); - return FAIL; - } - - // Get the current-user-only and current-session-only args - if ((argv[3][0] != '0' && argv[3][0] != '1') || - argv[3][1] != '\0' || - (argv[4][0] != '0' && argv[4][0] != '1') || - argv[4][1] != '\0') - { - ExitProcess(FAIL); - return FAIL; - } - g_currentUserOnly = argv[3][0] != '0'; - g_currentSessionOnly = argv[4][0] != '0'; - - if (argc >= 6 && test_strcmp(argv[5], "stress") == 0) - { - g_isStress = true; - } - - if (test_strcmp(argv[2], "MutualExclusionTests_Child") == 0) - { - MutualExclusionTests_Child(); - } - else if (test_strcmp(argv[2], "LifetimeTests_Child") == 0) - { - LifetimeTests_Child(); - } - else if (test_strcmp(argv[2], "AbandonTests_Child_GracefulExit_Close") == 0) - { - AbandonTests_Child_GracefulExit_Close(); - } - else if (test_strcmp(argv[2], "AbandonTests_Child_GracefulExit_NoClose") == 0) - { - AbandonTests_Child_GracefulExit_NoClose(); - } - else if (test_strcmp(argv[2], "AbandonTests_Child_AbruptExit") == 0) - { - AbandonTests_Child_AbruptExit(); - } - else if (test_strcmp(argv[2], "AbandonTests_Child_FileLocksNotInherited_Parent_AbruptExit") == 0) - { - AbandonTests_Child_FileLocksNotInherited_Parent_AbruptExit(); - } - else if (test_strcmp(argv[2], "AbandonTests_Child_FileLocksNotInherited_Child_AbruptExit") == 0) - { - AbandonTests_Child_FileLocksNotInherited_Child_AbruptExit(); - } - else if (test_strcmp(argv[2], "AbandonTests_Child_TryLock") == 0) - { - AbandonTests_Child_TryLock(); - } - else if (test_strcmp(argv[2], "LockAndCloseWithoutThreadExitTests_Child_CloseOnSameThread") == 0) - { - LockAndCloseWithoutThreadExitTests_Child_CloseOnSameThread(); - } - else if (test_strcmp(argv[2], "LockAndCloseWithoutThreadExitTests_Child_CloseOnDifferentThread") == 0) - { - LockAndCloseWithoutThreadExitTests_Child_CloseOnDifferentThread(); - } - ExitProcess(PASS); - return PASS; -} diff --git a/src/coreclr/pal/tests/palsuite/threading/NamedMutex/test1/nopal.cpp b/src/coreclr/pal/tests/palsuite/threading/NamedMutex/test1/nopal.cpp deleted file mode 100644 index 435f53108b9300..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/NamedMutex/test1/nopal.cpp +++ /dev/null @@ -1,85 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// Contains wrappers for functions whose required headers conflict with the PAL - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -auto test_strcpy = strcpy; -auto test_strcmp = strcmp; -auto test_strlen = strlen; -auto test_snprintf = snprintf; -auto test_sscanf = sscanf; -auto test_close = close; -auto test_unlink = unlink; - -unsigned int test_getpid() -{ - return getpid(); -} - -unsigned int test_getsid() -{ - return getsid(0); -} - -unsigned int test_geteuid() -{ - return geteuid(); -} - -int test_kill(unsigned int pid) -{ - return kill(pid, SIGKILL); -} - -bool TestFileExists(const char *path) -{ - int fd = open(path, O_RDWR); - if (fd == -1) - return false; - close(fd); - return true; -} - -bool WriteHeaderInfo(const char *path, bool currentUserOnly, char sharedMemoryType, char version, int *fdRef) -{ - int fd = open(path, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); - if (fd == -1) - return false; - - if (currentUserOnly) - { - int chmodResult; - do - { - chmodResult = chmod(path, S_IRUSR | S_IWUSR); - } while (chmodResult != 0 && errno == EINTR); - - if (chmodResult != 0) - return false; - } - - *fdRef = fd; - if (ftruncate(fd, getpagesize()) != 0) - return false; - if (lseek(fd, 0, SEEK_SET) != 0) - return false; - - // See SharedMemorySharedDataHeader for format - char buffer[] = {sharedMemoryType, version}; - if (write(fd, buffer, ARRAY_SIZE(buffer)) != ARRAY_SIZE(buffer)) - return false; - - return flock(fd, LOCK_SH | LOCK_NB) == 0; -} diff --git a/src/coreclr/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test6/child6.cpp b/src/coreclr/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test6/child6.cpp deleted file mode 100644 index 618c5edeca5555..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test6/child6.cpp +++ /dev/null @@ -1,210 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** Source: child6.c -** -** Purpose: Test for WaitForMultipleObjectsEx in multiple -** scenarios - child process -** -** -**=========================================================*/ - -#include - -PALTEST(threading_WaitForMultipleObjectsEx_test6_paltest_waitformultipleobjectsex_test6_child, "threading/WaitForMultipleObjectsEx/test6/paltest_waitformultipleobjectsex_test6_child") -{ - int i, iRet; - BOOL bRet; - BOOL bNamedEvent = 0; - BOOL bMutex = 0; - BOOL bMutexAndNamedEvent = 0; - BOOL bSemaphore = 0; - DWORD dwRet; - HANDLE hNamedEvent; - HANDLE hMutex; - char szTestName[256]; - WCHAR wszTestName[256] = { 0 }; - char szEventName[128] = { 0 }; - char szMutexName[128] = { 0 }; - char szSemName[128] = { 0 }; - WCHAR wszEventName[128]; - WCHAR wszMutexName[128]; - WCHAR wszSemName[128]; - DWORD iExitCode = 0; - HANDLE hSemaphore; - - if(0 != (PAL_Initialize(argc, argv))) - { - return ( FAIL ); - } - - Trace("[child] Starting\n"); - - for (i=1; i -#include - -#define MAX_COUNT 10000 -#define MAX_THREADS 256 - -BOOL g_bMutex = 0; -BOOL g_bEvent = 0; -BOOL g_bNamedEvent = 0; -BOOL g_bSemaphore = 0; -BOOL g_bProcess = 0; -BOOL g_bLocalWaitAll = 0; -BOOL g_bRemoteWaitAll = 0; -BOOL g_bRandom = 0; - -int iCount = 1; -int iThreads = 1; -HANDLE hThreads[MAX_THREADS]; - -#ifndef MIN -#define MIN(a,b) (((a)<(b)) ? (a) : (b)) -#endif - -DWORD PALAPI EventTestThread(PVOID pArg) -{ - BOOL bRet; - DWORD dwRet; - HANDLE hEvent[2]; - HANDLE (*prgHandles)[] = (HANDLE (*)[])pArg; - - Trace("[EventTestThread] Starting\n"); - - bRet = DuplicateHandle(GetCurrentProcess(), (*prgHandles)[0], GetCurrentProcess(), - &hEvent[0], 0, FALSE, DUPLICATE_SAME_ACCESS); - bRet &= DuplicateHandle(GetCurrentProcess(), (*prgHandles)[1], GetCurrentProcess(), - &hEvent[1], 0, FALSE, DUPLICATE_SAME_ACCESS); - if (FALSE == bRet) - { - Fail("[EventTestThread] Failed to duplicate handles\n"); - } - - Sleep(1000); - bRet = SetEvent(hEvent[1]); - if (FALSE == bRet) - { - Fail("SetEvent failed\n"); - Fail("[EventTestThread] SetEvent failed [GetLastError()=%u]\n", - GetLastError()); - } - - dwRet = WaitForSingleObject(hEvent[1], INFINITE); - if (WAIT_FAILED == dwRet) - { - Fail("[EventTestThread] WaitForMultipleObjects failed [GetLastError()=%u]\n", - GetLastError()); - } - - Sleep(1000); - bRet = SetEvent(hEvent[0]); - if (FALSE == bRet) - { - Fail("[EventTestThread] SetEvent failed [GetLastError()=%u]\n", - GetLastError()); - } - - Sleep(1000); - bRet = SetEvent(hEvent[1]); - if (FALSE == bRet) - { - Fail("[EventTestThread] SetEvent failed [GetLastError()=%u]\n", - GetLastError()); - } - - CloseHandle(hEvent[0]); - CloseHandle(hEvent[1]); - - Trace("[EventTestThread] Done\n"); - return 0; -} - -DWORD PALAPI MutexTestThread(PVOID pArg) -{ - BOOL bRet; - DWORD dwRet; - HANDLE hMutex; - - Trace("[MutexTestThread] Starting\n"); - - bRet = DuplicateHandle(GetCurrentProcess(), (HANDLE)pArg, GetCurrentProcess(), &hMutex, - 0, FALSE, DUPLICATE_SAME_ACCESS); - if (FALSE == bRet) - { - Fail("[EventTestThread] DuplicateHandle failed [GetLastError()=%u]\n", - GetLastError()); - } - - dwRet = WaitForSingleObject(hMutex, INFINITE); - if (WAIT_FAILED == dwRet) - { - Fail("[EventTestThread] WaitForMultipleObjects failed [GetLastError()=%u]\n", - GetLastError()); - } - - Sleep(1000); - CloseHandle(hMutex); - - Trace("[MutexTestThread] Done\n"); - - return 0; -} - -DWORD PALAPI TestThread(PVOID pArg) -{ - BOOL bRet; - DWORD dwRet; - PROCESS_INFORMATION pi; - STARTUPINFO si; - HANDLE hNamedEvent; - HANDLE hEvent[2] = { 0, 0 }; - HANDLE hMutex = 0; - HANDLE hSemaphore = 0; - HANDLE hObjs[2]; - DWORD dwThreadNum; - DWORD dwSlaveThreadTid = 0; - HANDLE hThread; - int i, iCnt, iRet; - char szTestName[128]; - char szCmd[128]; - char szEventName[128] = { 0 }; - char szMutexName[128] = { 0 }; - char szSemName[128] = { 0 }; - WCHAR wszEventName[128] = { 0 }; - WCHAR wszMutexName[128] = { 0 }; - WCHAR wszSemName[128] = { 0 }; - BOOL bMutex = g_bMutex; - BOOL bEvent = g_bEvent; - BOOL bNamedEvent = g_bNamedEvent; - BOOL bSemaphore = g_bSemaphore; - BOOL bProcess = g_bProcess; - BOOL bLocalWaitAll = g_bLocalWaitAll; - BOOL bRemoteWaitAll = g_bRemoteWaitAll; - int iDesiredExitCode; - - dwThreadNum = (DWORD)(SIZE_T)pArg; - - sprintf_s (szTestName, 128, "Test6_%u", dwThreadNum); - szTestName[127] = 0; - - sprintf_s(szEventName, 128, "%s_Event", szTestName); - szEventName[127] = 0; - sprintf_s(szMutexName, 128, "%s_Mutex", szTestName); - szMutexName[127] = 0; - sprintf_s(szSemName, 128, "%s_Semaphore", szTestName); - szSemName[127] = 0; - - iRet = MultiByteToWideChar(CP_ACP, 0, szEventName, strlen(szEventName)+1, wszEventName, 128); - iRet &= MultiByteToWideChar(CP_ACP, 0, szMutexName, strlen(szMutexName)+1, wszMutexName, 128); - iRet &= MultiByteToWideChar(CP_ACP, 0, szSemName, strlen(szSemName)+1, wszSemName, 128); - - if (0 == iRet) - { - Fail("[TestThread] Failed to convert strings\n"); - } - - Trace("[TestThread] TestName=%s Event: %S, Mutex: %S, Semaphore = %S\n", - szTestName, wszEventName, wszMutexName, wszSemName); - - hEvent[0] = CreateEvent(NULL, FALSE, FALSE, NULL); - hEvent[1] = CreateEvent(NULL, FALSE, FALSE, NULL); - - hNamedEvent = CreateEventW(NULL, FALSE, FALSE, wszEventName); - hMutex = CreateMutexW(NULL, FALSE, wszMutexName); - hSemaphore = CreateSemaphoreExW(NULL, 0, 256, wszSemName, 0, 0); - - if (NULL == hEvent[0] || NULL == hEvent[1] || NULL == hMutex || - NULL == hNamedEvent || NULL == hSemaphore) - { - Fail("[TestThread] Failed to create objects " - "[hNamedEvent=%p hMutex=%p hSemaphore=%p]\n", - (VOID*)hNamedEvent, (VOID*)hMutex, (VOID*)hSemaphore); - } - - for (iCnt=0; iCnt i+1)) - { - i++; - iCnt = atoi(argv[i]); - if (iCnt > 0 && iCnt < MAX_COUNT) - { - iCount = iCnt; - } - } - else if ((0 == strcmp(argv[i], "-threads")) && (argc > i+1)) - { - i++; - iCnt = atoi(argv[i]); - if (iCnt > 0 && iCnt <= MAX_THREADS) - { - iThreads = iCnt; - } - } - else - { - Trace("Unknown option %s ignored\n", argv[i]); - } - } - } - - - iCnt = 0; - for (i=0;i Date: Mon, 21 Jul 2025 21:51:00 +0000 Subject: [PATCH 41/47] Fix qcalls and managed/native sizes --- .../src/System/Threading/Thread.CoreCLR.cs | 8 ++++---- src/coreclr/vm/object.h | 3 +++ src/coreclr/vm/qcallentrypoints.cpp | 1 + 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index b3a58b2b282cf7..fbaabab7053ac9 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -33,6 +33,10 @@ public sealed partial class Thread private string? _name; private StartHelper? _startHelper; +#if TARGET_UNIX || TARGET_BROWSER || TARGET_WASI + internal WaitSubsystem.ThreadWaitInfo? _waitInfo; +#endif + /*========================================================================= ** The base implementation of Thread is all native. The following fields ** should never be used in the C# code. They are here to define the proper @@ -62,10 +66,6 @@ public sealed partial class Thread private bool _isDead; private bool _isThreadPool; -#if TARGET_UNIX || TARGET_BROWSER || TARGET_WASI - internal WaitSubsystem.ThreadWaitInfo? _waitInfo; -#endif - private Thread() { } public int ManagedThreadId diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index 0b97c65e53df4a..7ba028c03e19d5 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -1309,6 +1309,9 @@ class ThreadBaseObject : public Object OBJECTREF m_SynchronizationContext; STRINGREF m_Name; OBJECTREF m_StartHelper; +#ifdef TARGET_UNIX + OBJECTREF m_WaitInfo; +#endif // TARGET_UNIX // The next field (m_InternalThread) is declared as IntPtr in the managed // definition of Thread. The loader will sort it next. diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 433cb61fca5597..7f5836d99d25b6 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -290,6 +290,7 @@ static const Entry s_QCall[] = DllImportEntry(ThreadNative_Initialize) DllImportEntry(ThreadNative_GetThreadState) DllImportEntry(ThreadNative_SetWaitSleepJoinState) + DllImportEntry(ThreadNative_ClearWaitSleepJoinState) #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT DllImportEntry(ThreadNative_GetApartmentState) DllImportEntry(ThreadNative_SetApartmentState) From f0be4c2c8c3bcfc918c12c83b3209ee516df1d52 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 21 Jul 2025 22:05:38 +0000 Subject: [PATCH 42/47] Unify LowLevelLifoSemaphore implementations for Unix to use the wait subsystem --- .../System.Private.CoreLib.csproj | 1 - .../Threading/LowLevelLifoSemaphore.Unix.cs | 51 ---------- .../src/System.Private.CoreLib.csproj | 1 - src/coreclr/pal/inc/pal.h | 7 -- src/coreclr/pal/src/synchmgr/wait.cpp | 30 ------ src/coreclr/vm/comwaithandle.cpp | 19 ---- src/coreclr/vm/comwaithandle.h | 4 - src/coreclr/vm/qcallentrypoints.cpp | 3 - .../System.Private.CoreLib.Shared.projitems | 1 + .../Threading/LowLevelLifoSemaphore.Unix.cs | 8 +- .../System/Threading/LowLevelLifoSemaphore.cs | 4 - .../System.Private.CoreLib.csproj | 3 - .../LowLevelLifoSemaphore.Unix.Mono.cs | 47 --------- src/mono/mono/metadata/icall-decl.h | 5 - src/mono/mono/metadata/icall-def.h | 7 -- src/mono/mono/metadata/threads.c | 28 ------ src/mono/mono/utils/CMakeLists.txt | 2 - src/mono/mono/utils/lifo-semaphore.c | 96 ------------------- src/mono/mono/utils/lifo-semaphore.h | 34 ------- 19 files changed, 6 insertions(+), 345 deletions(-) delete mode 100644 src/coreclr/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs rename src/{coreclr/nativeaot => libraries}/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs (65%) delete mode 100644 src/mono/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.Mono.cs delete mode 100644 src/mono/mono/utils/lifo-semaphore.c delete mode 100644 src/mono/mono/utils/lifo-semaphore.h diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index a64294355f6e44..88992bc63e56ae 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -289,7 +289,6 @@ - diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs deleted file mode 100644 index 25fc6ff09ad2a1..00000000000000 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using Microsoft.Win32.SafeHandles; - -namespace System.Threading -{ - /// - /// A LIFO semaphore implemented using the PAL's semaphore with uninterruptible waits. - /// - internal sealed partial class LowLevelLifoSemaphore : IDisposable - { - private Semaphore? _semaphore; - - private void Create(int maximumSignalCount) - { - Debug.Assert(maximumSignalCount > 0); - _semaphore = new Semaphore(0, maximumSignalCount); - } - - public bool WaitCore(int timeoutMs) - { - Debug.Assert(_semaphore != null); - Debug.Assert(timeoutMs >= -1); - - int waitResult = WaitNative(_semaphore!.SafeWaitHandle, timeoutMs); - Debug.Assert(waitResult == WaitHandle.WaitSuccess || waitResult == WaitHandle.WaitTimeout); - return waitResult == WaitHandle.WaitSuccess; - } - - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "WaitHandle_WaitOnePrioritized")] - private static partial int WaitNative(SafeWaitHandle handle, int timeoutMs); - - private void ReleaseCore(int count) - { - Debug.Assert(_semaphore != null); - Debug.Assert(count > 0); - - _semaphore!.Release(count); - } - - public void Dispose() - { - Debug.Assert(_semaphore != null); - _semaphore!.Dispose(); - } - } -} diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index bf88594f9cdb5f..24aa48b6506fc0 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -294,7 +294,6 @@ - Interop\Unix\System.Native\Interop.Abort.cs diff --git a/src/coreclr/pal/inc/pal.h b/src/coreclr/pal/inc/pal.h index 4105eaf082944e..fc725cc0111f4d 100644 --- a/src/coreclr/pal/inc/pal.h +++ b/src/coreclr/pal/inc/pal.h @@ -889,13 +889,6 @@ WaitForSingleObject( IN HANDLE hHandle, IN DWORD dwMilliseconds); -PALIMPORT -DWORD -PALAPI -PAL_WaitForSingleObjectPrioritized( - IN HANDLE hHandle, - IN DWORD dwMilliseconds); - PALIMPORT DWORD PALAPI diff --git a/src/coreclr/pal/src/synchmgr/wait.cpp b/src/coreclr/pal/src/synchmgr/wait.cpp index 1a3aa8c4df18df..4fa283394639ae 100644 --- a/src/coreclr/pal/src/synchmgr/wait.cpp +++ b/src/coreclr/pal/src/synchmgr/wait.cpp @@ -83,36 +83,6 @@ WaitForSingleObject(IN HANDLE hHandle, return dwRet; } - -/*++ -Function: - WaitForSingleObjectPrioritized - -Similar to WaitForSingleObject, except uses a LIFO release policy for waiting threads by prioritizing new waiters (registering -them at the beginning of the wait queue rather than at the end). ---*/ -DWORD -PALAPI -PAL_WaitForSingleObjectPrioritized(IN HANDLE hHandle, - IN DWORD dwMilliseconds) -{ - DWORD dwRet; - - PERF_ENTRY(PAL_WaitForSingleObjectPrioritized); - ENTRY("PAL_WaitForSingleObjectPrioritized(hHandle=%p, dwMilliseconds=%u)\n", - hHandle, dwMilliseconds); - - CPalThread * pThread = InternalGetCurrentThread(); - - dwRet = InternalWaitForMultipleObjectsEx(pThread, 1, &hHandle, FALSE, - dwMilliseconds, FALSE, TRUE /* bPrioritize */); - - LOGEXIT("PAL_WaitForSingleObjectPrioritized returns DWORD %u\n", dwRet); - PERF_EXIT(PAL_WaitForSingleObjectPrioritized); - return dwRet; -} - - /*++ Function: WaitForSingleObjectEx diff --git a/src/coreclr/vm/comwaithandle.cpp b/src/coreclr/vm/comwaithandle.cpp index e80675cc03febf..322cf20704e831 100644 --- a/src/coreclr/vm/comwaithandle.cpp +++ b/src/coreclr/vm/comwaithandle.cpp @@ -82,22 +82,3 @@ extern "C" INT32 QCALLTYPE WaitHandle_SignalAndWait(HANDLE waitHandleSignal, HAN END_QCALL; return retVal; } - -#ifdef TARGET_UNIX -extern "C" INT32 QCALLTYPE WaitHandle_WaitOnePrioritized(HANDLE handle, INT32 timeoutMs) -{ - QCALL_CONTRACT; - - DWORD result = WAIT_FAILED; - - BEGIN_QCALL; - - _ASSERTE(handle != NULL); - _ASSERTE(handle != INVALID_HANDLE_VALUE); - - result = PAL_WaitForSingleObjectPrioritized(handle, timeoutMs); - - END_QCALL; - return (INT32)result; -} -#endif // TARGET_UNIX diff --git a/src/coreclr/vm/comwaithandle.h b/src/coreclr/vm/comwaithandle.h index ac605389129138..7fb890e7554ce2 100644 --- a/src/coreclr/vm/comwaithandle.h +++ b/src/coreclr/vm/comwaithandle.h @@ -18,8 +18,4 @@ extern "C" INT32 QCALLTYPE WaitHandle_WaitOneCore(HANDLE handle, INT32 timeout, extern "C" INT32 QCALLTYPE WaitHandle_WaitMultipleIgnoringSyncContext(HANDLE *handleArray, INT32 numHandles, BOOL waitForAll, INT32 timeout); extern "C" INT32 QCALLTYPE WaitHandle_SignalAndWait(HANDLE waitHandleSignal, HANDLE waitHandleWait, INT32 timeout); -#ifdef TARGET_UNIX -extern "C" INT32 QCALLTYPE WaitHandle_WaitOnePrioritized(HANDLE handle, INT32 timeoutMs); -#endif // TARGET_UNIX - #endif // _COM_WAITABLE_HANDLE_H diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 7f5836d99d25b6..bb05305b66997a 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -309,9 +309,6 @@ static const Entry s_QCall[] = DllImportEntry(WaitHandle_WaitOneCore) DllImportEntry(WaitHandle_WaitMultipleIgnoringSyncContext) DllImportEntry(WaitHandle_SignalAndWait) -#ifdef TARGET_UNIX - DllImportEntry(WaitHandle_WaitOnePrioritized) -#endif // TARGET_UNIX DllImportEntry(ClrConfig_GetConfigBoolValue) DllImportEntry(Buffer_Clear) DllImportEntry(Buffer_MemMove) diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index d5068c6af1510d..75537a3e864aee 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2807,6 +2807,7 @@ + diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs similarity index 65% rename from src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs rename to src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs index a0873fc273ff32..41319b669c6e79 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.cs @@ -11,7 +11,9 @@ namespace System.Threading /// internal sealed partial class LowLevelLifoSemaphore : IDisposable { - private WaitSubsystem.WaitableObject _semaphore; + // Declared nullable even though it is initialized in Create + // as Roslyn doesn't see that it's set in Create and Create is called from all constructors. + private WaitSubsystem.WaitableObject? _semaphore; private void Create(int maximumSignalCount) { @@ -24,12 +26,12 @@ public void Dispose() private bool WaitCore(int timeoutMs) { - return WaitSubsystem.Wait(_semaphore, timeoutMs, false, true) == WaitHandle.WaitSuccess; + return WaitSubsystem.Wait(_semaphore!, timeoutMs, false, true) == WaitHandle.WaitSuccess; } private void ReleaseCore(int count) { - WaitSubsystem.ReleaseSemaphore(_semaphore, count); + WaitSubsystem.ReleaseSemaphore(_semaphore!, count); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs index 39233c87c15c96..0f789de54ee214 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.cs @@ -94,10 +94,6 @@ public bool Wait(int timeoutMs, bool spinWait) counts = countsBeforeUpdate; } -#if CORECLR && TARGET_UNIX - // The PAL's wait subsystem is slower, spin more to compensate for the more expensive wait - spinCount *= 2; -#endif bool isSingleProcessor = Environment.IsSingleProcessor; int spinIndex = isSingleProcessor ? SpinSleep0Threshold : 0; while (spinIndex < spinCount) diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index 1ef2c0a4aa22da..5d5c71bdb5db54 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -261,9 +261,6 @@ CommonSystem\Experimentals.cs - - - diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.Mono.cs deleted file mode 100644 index 477ee0f08c9c5c..00000000000000 --- a/src/mono/System.Private.CoreLib/src/System/Threading/LowLevelLifoSemaphore.Unix.Mono.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.CompilerServices; - -namespace System.Threading -{ - internal sealed unsafe partial class LowLevelLifoSemaphore : IDisposable - { - private IntPtr lifo_semaphore; - - [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern IntPtr InitInternal(); - -#pragma warning disable IDE0060 - private void Create(int maximumSignalCount) -#pragma warning restore IDE0060 - { - lifo_semaphore = InitInternal(); - } - - [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern void DeleteInternal(IntPtr semaphore); - - public void Dispose() - { - DeleteInternal(lifo_semaphore); - lifo_semaphore = IntPtr.Zero; - } - - [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern int TimedWaitInternal(IntPtr semaphore, int timeoutMs); - - private bool WaitCore(int timeoutMs) - { - return TimedWaitInternal(lifo_semaphore, timeoutMs) != 0; - } - - [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern void ReleaseInternal(IntPtr semaphore, int count); - - private void ReleaseCore(int count) - { - ReleaseInternal(lifo_semaphore, count); - } - } -} diff --git a/src/mono/mono/metadata/icall-decl.h b/src/mono/mono/metadata/icall-decl.h index ea8336b4f7bc0f..387282931aa5a1 100644 --- a/src/mono/mono/metadata/icall-decl.h +++ b/src/mono/mono/metadata/icall-decl.h @@ -181,11 +181,6 @@ ICALL_EXPORT void ves_icall_Mono_SafeStringMarshal_GFree (void *c_str); ICALL_EXPORT char* ves_icall_Mono_SafeStringMarshal_StringToUtf8 (MonoString *volatile* s); ICALL_EXPORT MonoType* ves_icall_Mono_RuntimeClassHandle_GetTypeFromClass (MonoClass *klass); -ICALL_EXPORT gpointer ves_icall_System_Threading_LowLevelLifoSemaphore_InitInternal (void); -ICALL_EXPORT void ves_icall_System_Threading_LowLevelLifoSemaphore_DeleteInternal (gpointer sem_ptr); -ICALL_EXPORT gint32 ves_icall_System_Threading_LowLevelLifoSemaphore_TimedWaitInternal (gpointer sem_ptr, gint32 timeout_ms); -ICALL_EXPORT void ves_icall_System_Threading_LowLevelLifoSemaphore_ReleaseInternal (gpointer sem_ptr, gint32 count); - #ifdef TARGET_AMD64 ICALL_EXPORT void ves_icall_System_Runtime_Intrinsics_X86_X86Base___cpuidex (int abcd[4], int function_id, int subfunction_id); #endif diff --git a/src/mono/mono/metadata/icall-def.h b/src/mono/mono/metadata/icall-def.h index 3ab137aa253788..fd135f5046fcf6 100644 --- a/src/mono/mono/metadata/icall-def.h +++ b/src/mono/mono/metadata/icall-def.h @@ -568,13 +568,6 @@ NOHANDLES(ICALL(ILOCK_21, "Increment(long&)", ves_icall_System_Threading_Interlo NOHANDLES(ICALL(ILOCK_22, "MemoryBarrierProcessWide", ves_icall_System_Threading_Interlocked_MemoryBarrierProcessWide)) NOHANDLES(ICALL(ILOCK_23, "Read(long&)", ves_icall_System_Threading_Interlocked_Read_Long)) -ICALL_TYPE(LIFOSEM, "System.Threading.LowLevelLifoSemaphore", LIFOSEM_1) -NOHANDLES(ICALL(LIFOSEM_1, "DeleteInternal", ves_icall_System_Threading_LowLevelLifoSemaphore_DeleteInternal)) -NOHANDLES(ICALL(LIFOSEM_2, "InitInternal", ves_icall_System_Threading_LowLevelLifoSemaphore_InitInternal)) -NOHANDLES(ICALL(LIFOSEM_3, "ReleaseInternal", ves_icall_System_Threading_LowLevelLifoSemaphore_ReleaseInternal)) -NOHANDLES(ICALL(LIFOSEM_4, "TimedWaitInternal", ves_icall_System_Threading_LowLevelLifoSemaphore_TimedWaitInternal)) - - ICALL_TYPE(MONIT, "System.Threading.Monitor", MONIT_0) HANDLES(MONIT_0, "Enter", ves_icall_System_Threading_Monitor_Monitor_Enter, void, 1, (MonoObject)) HANDLES(MONIT_1, "InternalExit", mono_monitor_exit_icall, void, 1, (MonoObject)) diff --git a/src/mono/mono/metadata/threads.c b/src/mono/mono/metadata/threads.c index 9100051a13185b..78473d8b3681ca 100644 --- a/src/mono/mono/metadata/threads.c +++ b/src/mono/mono/metadata/threads.c @@ -60,7 +60,6 @@ #include #include #include -#include #include #ifdef HAVE_SYS_WAIT_H @@ -4883,30 +4882,3 @@ ves_icall_System_Threading_Thread_GetCurrentOSThreadId (MonoError *error) { return mono_native_thread_os_id_get (); } - -gpointer -ves_icall_System_Threading_LowLevelLifoSemaphore_InitInternal (void) -{ - return (gpointer)mono_lifo_semaphore_init (); -} - -void -ves_icall_System_Threading_LowLevelLifoSemaphore_DeleteInternal (gpointer sem_ptr) -{ - LifoSemaphore *sem = (LifoSemaphore *)sem_ptr; - mono_lifo_semaphore_delete (sem); -} - -gint32 -ves_icall_System_Threading_LowLevelLifoSemaphore_TimedWaitInternal (gpointer sem_ptr, gint32 timeout_ms) -{ - LifoSemaphore *sem = (LifoSemaphore *)sem_ptr; - return mono_lifo_semaphore_timed_wait (sem, timeout_ms); -} - -void -ves_icall_System_Threading_LowLevelLifoSemaphore_ReleaseInternal (gpointer sem_ptr, gint32 count) -{ - LifoSemaphore *sem = (LifoSemaphore *)sem_ptr; - mono_lifo_semaphore_release (sem, count); -} diff --git a/src/mono/mono/utils/CMakeLists.txt b/src/mono/mono/utils/CMakeLists.txt index 8c124a1aaa952f..4940aa4732114e 100644 --- a/src/mono/mono/utils/CMakeLists.txt +++ b/src/mono/mono/utils/CMakeLists.txt @@ -104,8 +104,6 @@ set(utils_common_sources mono-stack-unwinding.h hazard-pointer.c hazard-pointer.h - lifo-semaphore.c - lifo-semaphore.h lock-free-queue.c lock-free-queue.h lock-free-alloc.c diff --git a/src/mono/mono/utils/lifo-semaphore.c b/src/mono/mono/utils/lifo-semaphore.c deleted file mode 100644 index 1f3f6c4410b9f2..00000000000000 --- a/src/mono/mono/utils/lifo-semaphore.c +++ /dev/null @@ -1,96 +0,0 @@ -#include -#include -#include - -#if defined(HOST_BROWSER) && !defined(DISABLE_THREADS) -#include -#include -#endif - -LifoSemaphore * -mono_lifo_semaphore_init (void) -{ - LifoSemaphore *semaphore = g_new0 (LifoSemaphore, 1); - if (semaphore == NULL) - return NULL; - - mono_coop_mutex_init (&semaphore->mutex); - - return semaphore; -} - -void -mono_lifo_semaphore_delete (LifoSemaphore *semaphore) -{ - g_assert (semaphore->head == NULL); - mono_coop_mutex_destroy (&semaphore->mutex); - g_free (semaphore); -} - -int32_t -mono_lifo_semaphore_timed_wait (LifoSemaphore *semaphore, int32_t timeout_ms) -{ - LifoSemaphoreWaitEntry wait_entry = {0}; - - mono_coop_cond_init (&wait_entry.condition); - mono_coop_mutex_lock (&semaphore->mutex); - - if (semaphore->pending_signals > 0) { - --semaphore->pending_signals; - mono_coop_cond_destroy (&wait_entry.condition); - mono_coop_mutex_unlock (&semaphore->mutex); - return 1; - } - - // Enqueue out entry into the LIFO wait list - wait_entry.previous = NULL; - wait_entry.next = semaphore->head; - if (semaphore->head != NULL) - semaphore->head->previous = &wait_entry; - semaphore->head = &wait_entry; - - // Wait for a signal or timeout - int wait_error = 0; - do { - wait_error = mono_coop_cond_timedwait (&wait_entry.condition, &semaphore->mutex, timeout_ms); - } while (wait_error == 0 && !wait_entry.signaled); - - if (wait_error == -1) { - if (semaphore->head == &wait_entry) - semaphore->head = wait_entry.next; - if (wait_entry.next != NULL) - wait_entry.next->previous = wait_entry.previous; - if (wait_entry.previous != NULL) - wait_entry.previous->next = wait_entry.next; - } - - mono_coop_cond_destroy (&wait_entry.condition); - mono_coop_mutex_unlock (&semaphore->mutex); - - return wait_entry.signaled; -} - -void -mono_lifo_semaphore_release (LifoSemaphore *semaphore, uint32_t count) -{ - mono_coop_mutex_lock (&semaphore->mutex); - - while (count > 0) { - LifoSemaphoreWaitEntry *wait_entry = semaphore->head; - if (wait_entry != NULL) { - semaphore->head = wait_entry->next; - if (semaphore->head != NULL) - semaphore->head->previous = NULL; - wait_entry->previous = NULL; - wait_entry->next = NULL; - wait_entry->signaled = 1; - mono_coop_cond_signal (&wait_entry->condition); - --count; - } else { - semaphore->pending_signals += count; - count = 0; - } - } - - mono_coop_mutex_unlock (&semaphore->mutex); -} diff --git a/src/mono/mono/utils/lifo-semaphore.h b/src/mono/mono/utils/lifo-semaphore.h deleted file mode 100644 index ad0492c6defb30..00000000000000 --- a/src/mono/mono/utils/lifo-semaphore.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef __MONO_LIFO_SEMAPHORE_H__ -#define __MONO_LIFO_SEMAPHORE_H__ - -#include - -typedef struct _LifoSemaphore LifoSemaphore; -typedef struct _LifoSemaphoreWaitEntry LifoSemaphoreWaitEntry; - -struct _LifoSemaphoreWaitEntry { - LifoSemaphoreWaitEntry *previous; - LifoSemaphoreWaitEntry *next; - MonoCoopCond condition; - int signaled; -}; - -struct _LifoSemaphore { - MonoCoopMutex mutex; - uint32_t pending_signals; - LifoSemaphoreWaitEntry *head; -}; - -LifoSemaphore * -mono_lifo_semaphore_init (void); - -void -mono_lifo_semaphore_delete (LifoSemaphore *semaphore); - -int32_t -mono_lifo_semaphore_timed_wait (LifoSemaphore *semaphore, int32_t timeout_ms); - -void -mono_lifo_semaphore_release (LifoSemaphore *semaphore, uint32_t count); - -#endif // __MONO_LIFO_SEMAPHORE_H__ From 14c8a8c263757e42ddd311d2912491fee341cc32 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 22 Jul 2025 15:41:18 +0000 Subject: [PATCH 43/47] Hook up the "thread exiting" callback --- .../src/System/Threading/Thread.CoreCLR.cs | 9 +++++++++ src/coreclr/vm/corelib.h | 1 + src/coreclr/vm/threads.cpp | 9 +++++++++ 3 files changed, 19 insertions(+) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index fbaabab7053ac9..9e7994e66b8539 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -557,6 +557,15 @@ WaitSubsystem.ThreadWaitInfo AllocateWaitInfo() } #endif + private void OnThreadExiting() + { +#if TARGET_UNIX || TARGET_BROWSER || TARGET_WASI + // Inform the wait subsystem that the thread is exiting. For instance, this would abandon any mutexes locked by + // the thread. + _waitInfo?.OnThreadExiting(); +#endif + } + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_CurrentThreadIsFinalizerThread")] [return: MarshalAs(UnmanagedType.Bool)] internal static partial bool CurrentThreadIsFinalizerThread(); diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 94dd6f4f505951..ad56bd0b2c82b5 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -969,6 +969,7 @@ DEFINE_CLASS(DIRECTONTHREADLOCALDATA, Threading, Thread+DirectOnThreadLocalData) DEFINE_CLASS(THREAD, Threading, Thread) DEFINE_METHOD(THREAD, START_CALLBACK, StartCallback, IM_RetVoid) DEFINE_METHOD(THREAD, POLLGC, PollGC, NoSig) +DEFINE_METHOD(THREAD, ON_THREAD_EXITING, OnThreadExiting, IM_RetVoid) #ifdef FEATURE_OBJCMARSHAL DEFINE_CLASS(AUTORELEASEPOOL, Threading, AutoreleasePool) diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index 72a14b5a81b84d..80cbf8b2e3136d 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -2727,6 +2727,15 @@ void Thread::CooperativeCleanup() GCX_COOP(); + if (!IsGCSpecial()) + { + // Allow managed subsystems to clean up on thread exit. + PREPARE_NONVIRTUAL_CALLSITE(METHOD__THREAD__ON_THREAD_EXITING); + DECLARE_ARGHOLDER_ARRAY(args, 1); + args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(GetExposedObject()); + CALL_MANAGED_METHOD_NORET(args); + } + // Clear any outstanding stale EH state that maybe still active on the thread. #ifdef FEATURE_EH_FUNCLETS ExInfo::PopTrackers((void*)-1); From d1d8344550780edfa683d6ed9e6fae2952022981 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 29 Jul 2025 21:07:49 +0000 Subject: [PATCH 44/47] Delete Mutex support from the PAL --- .../dlls/mscordac/mscordac_unixexports.src | 2 - src/coreclr/pal/inc/pal.h | 28 -- src/coreclr/pal/src/CMakeLists.txt | 1 - src/coreclr/pal/src/include/pal/corunix.hpp | 1 - src/coreclr/pal/src/include/pal/mutex.hpp | 54 --- .../pal/src/include/pal/synchobjects.hpp | 1 - .../pal/src/include/pal/threadsusp.hpp | 1 - .../pal/src/synchmgr/synchcontrollers.cpp | 4 - src/coreclr/pal/src/synchmgr/wait.cpp | 7 - src/coreclr/pal/src/synchobj/mutex.cpp | 406 ------------------ src/coreclr/pal/src/thread/thread.cpp | 1 - src/coreclr/pal/src/thread/threadsusp.cpp | 49 ++- src/coreclr/pal/tests/palsuite/CMakeLists.txt | 20 - .../pal/tests/palsuite/compilableTests.txt | 11 - .../mutex/nonshared/main.cpp | 229 ---------- .../mutex/nonshared/mutex.cpp | 326 -------------- .../object_management/mutex/shared/main.cpp | 264 ------------ .../object_management/mutex/shared/mutex.cpp | 342 --------------- .../composite/object_management/readme.txt | 8 +- .../tests/palsuite/composite/wfmo/main.cpp | 238 ---------- .../tests/palsuite/composite/wfmo/mutex.cpp | 350 --------------- .../tests/palsuite/composite/wfmo/readme.txt | 22 - .../pal/tests/palsuite/paltestlist.txt | 7 - .../palsuite/paltestlist_to_be_reviewed.txt | 4 - .../threading/CreateEventW/test1/test1.cpp | 92 ---- .../threading/CreateEventW/test2/test2.cpp | 84 ---- .../threading/CreateEventW/test3/test3.cpp | 231 ---------- .../test1/CreateMutexW.cpp | 343 --------------- .../test2/CreateMutexW.cpp | 165 ------- .../DuplicateHandle/test11/childprocess.cpp | 73 ---- .../DuplicateHandle/test11/myexitcode.h | 12 - .../DuplicateHandle/test11/test11.cpp | 321 -------------- .../threading/DuplicateHandle/test4/test4.cpp | 238 ---------- .../threading/OpenEventW/test4/test4.cpp | 111 ----- .../OpenProcess/test1/childProcess.cpp | 30 -- .../threading/OpenProcess/test1/test1.cpp | 41 -- .../ReleaseMutex/test3/ReleaseMutex.cpp | 102 ----- .../SignalObjectAndWaitTest.cpp | 55 +-- .../WaitForMultipleObjectsEx/test3/test3.cpp | 105 ----- .../WaitForMultipleObjectsEx/test4/test4.cpp | 100 ----- .../WFSOExMutexTest/WFSOExMutexTest.cpp | 213 --------- .../WFSOMutexTest/WFSOMutexTest.cpp | 183 -------- 42 files changed, 52 insertions(+), 4823 deletions(-) delete mode 100644 src/coreclr/pal/src/include/pal/mutex.hpp delete mode 100644 src/coreclr/pal/src/synchobj/mutex.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/composite/object_management/mutex/nonshared/main.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/composite/object_management/mutex/nonshared/mutex.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/composite/object_management/mutex/shared/main.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/composite/object_management/mutex/shared/mutex.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/composite/wfmo/main.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/composite/wfmo/mutex.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/composite/wfmo/readme.txt delete mode 100644 src/coreclr/pal/tests/palsuite/threading/CreateEventW/test1/test1.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/threading/CreateEventW/test2/test2.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/threading/CreateEventW/test3/test3.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test1/CreateMutexW.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test2/CreateMutexW.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/threading/DuplicateHandle/test11/childprocess.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/threading/DuplicateHandle/test11/myexitcode.h delete mode 100644 src/coreclr/pal/tests/palsuite/threading/DuplicateHandle/test11/test11.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/threading/DuplicateHandle/test4/test4.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/threading/OpenEventW/test4/test4.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/threading/ReleaseMutex/test3/ReleaseMutex.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test3/test3.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test4/test4.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExMutexTest/WFSOExMutexTest.cpp delete mode 100644 src/coreclr/pal/tests/palsuite/threading/WaitForSingleObject/WFSOMutexTest/WFSOMutexTest.cpp diff --git a/src/coreclr/dlls/mscordac/mscordac_unixexports.src b/src/coreclr/dlls/mscordac/mscordac_unixexports.src index 0b53a46fbb72a6..2d23a21d23a6fc 100644 --- a/src/coreclr/dlls/mscordac/mscordac_unixexports.src +++ b/src/coreclr/dlls/mscordac/mscordac_unixexports.src @@ -64,8 +64,6 @@ nativeStringResourceTable_mscorrc #CreateFileMappingW #CreateFileA #CreateFileW -#CreateMutexW -#CreateMutexExW #CreateEventW #CreateEventExW #CreateProcessW diff --git a/src/coreclr/pal/inc/pal.h b/src/coreclr/pal/inc/pal.h index fc725cc0111f4d..7a06fe1f079693 100644 --- a/src/coreclr/pal/inc/pal.h +++ b/src/coreclr/pal/inc/pal.h @@ -742,34 +742,6 @@ OpenEventW( #define OpenEvent OpenEventW #endif -PALIMPORT -HANDLE -PALAPI -CreateMutexW( - IN LPSECURITY_ATTRIBUTES lpMutexAttributes, - IN BOOL bInitialOwner, - IN LPCWSTR lpName); - -PALIMPORT -HANDLE -PALAPI -CreateMutexExW( - IN LPSECURITY_ATTRIBUTES lpMutexAttributes, - IN LPCWSTR lpName, - IN DWORD dwFlags, - IN DWORD dwDesiredAccess); - -// CreateMutexExW: dwFlags -#define CREATE_MUTEX_INITIAL_OWNER ((DWORD)0x1) - -#define CreateMutex CreateMutexW - -PALIMPORT -BOOL -PALAPI -ReleaseMutex( - IN HANDLE hMutex); - PALIMPORT DWORD PALAPI diff --git a/src/coreclr/pal/src/CMakeLists.txt b/src/coreclr/pal/src/CMakeLists.txt index 1123af90893cbe..691ef061e78ff3 100644 --- a/src/coreclr/pal/src/CMakeLists.txt +++ b/src/coreclr/pal/src/CMakeLists.txt @@ -202,7 +202,6 @@ set(SOURCES safecrt/wmakepath_s.cpp synchobj/event.cpp synchobj/semaphore.cpp - synchobj/mutex.cpp synchmgr/synchcontrollers.cpp synchmgr/synchmanager.cpp synchmgr/wait.cpp diff --git a/src/coreclr/pal/src/include/pal/corunix.hpp b/src/coreclr/pal/src/include/pal/corunix.hpp index 3bb843cdfa374f..69f4071752757f 100644 --- a/src/coreclr/pal/src/include/pal/corunix.hpp +++ b/src/coreclr/pal/src/include/pal/corunix.hpp @@ -159,7 +159,6 @@ namespace CorUnix { otiAutoResetEvent = 0, otiManualResetEvent, - otiMutex, otiSemaphore, otiFile, otiFileMapping, diff --git a/src/coreclr/pal/src/include/pal/mutex.hpp b/src/coreclr/pal/src/include/pal/mutex.hpp deleted file mode 100644 index c1f9aab7b08cb9..00000000000000 --- a/src/coreclr/pal/src/include/pal/mutex.hpp +++ /dev/null @@ -1,54 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*++ - - - -Module Name: - - mutex.hpp - -Abstract: - - Mutex object structure definition. - - - ---*/ - -#ifndef _PAL_MUTEX_H_ -#define _PAL_MUTEX_H_ - -#include "corunix.hpp" - -#include - -namespace CorUnix -{ - extern CObjectType otMutex; - - PAL_ERROR - InternalCreateMutex( - CPalThread *pThread, - LPSECURITY_ATTRIBUTES lpMutexAttributes, - BOOL bInitialOwner, - HANDLE *phMutex - ); - - PAL_ERROR - InternalReleaseMutex( - CPalThread *pThread, - HANDLE hMutex - ); -} - -#define SYNCSPINLOCK_F_ASYMMETRIC 1 - -#define SPINLOCKInit(lock) (*(lock) = 0) -#define SPINLOCKDestroy SPINLOCKInit - -void SPINLOCKAcquire (LONG * lock, unsigned int flags); -void SPINLOCKRelease (LONG * lock); -DWORD SPINLOCKTryAcquire (LONG * lock); -#endif //_PAL_MUTEX_H_ diff --git a/src/coreclr/pal/src/include/pal/synchobjects.hpp b/src/coreclr/pal/src/include/pal/synchobjects.hpp index 5adb4ca34d7e23..e984e600ad03db 100644 --- a/src/coreclr/pal/src/include/pal/synchobjects.hpp +++ b/src/coreclr/pal/src/include/pal/synchobjects.hpp @@ -21,7 +21,6 @@ Module Name: #include "corunix.hpp" #include "threadinfo.hpp" -#include "mutex.hpp" #include "list.h" #include diff --git a/src/coreclr/pal/src/include/pal/threadsusp.hpp b/src/coreclr/pal/src/include/pal/threadsusp.hpp index 4608ea372c2f4b..f182a9fd675052 100644 --- a/src/coreclr/pal/src/include/pal/threadsusp.hpp +++ b/src/coreclr/pal/src/include/pal/threadsusp.hpp @@ -24,7 +24,6 @@ Module Name: #include "pal/threadinfo.hpp" #include "pal/thread.hpp" -#include "pal/mutex.hpp" #include "pal/init.h" #if !HAVE_MACH_EXCEPTIONS #include diff --git a/src/coreclr/pal/src/synchmgr/synchcontrollers.cpp b/src/coreclr/pal/src/synchmgr/synchcontrollers.cpp index 614944198d46bd..52f38913d69dcd 100644 --- a/src/coreclr/pal/src/synchmgr/synchcontrollers.cpp +++ b/src/coreclr/pal/src/synchmgr/synchcontrollers.cpp @@ -964,10 +964,6 @@ namespace CorUnix "Conflicting values for SignalCount [%d] and " "OwnershipCount [%d]\n", GetOwnershipCount(), GetSignalCount()); - - _ASSERT_MSG(otiMutex != m_otiObjectTypeId || m_lSignalCount <= 1, - "Mutex with invalid singal count\n"); - return; } diff --git a/src/coreclr/pal/src/synchmgr/wait.cpp b/src/coreclr/pal/src/synchmgr/wait.cpp index 4fa283394639ae..781cf1bce3a34e 100644 --- a/src/coreclr/pal/src/synchmgr/wait.cpp +++ b/src/coreclr/pal/src/synchmgr/wait.cpp @@ -24,7 +24,6 @@ Revision History: #include "pal/synchobjects.hpp" #include "pal/handlemgr.hpp" #include "pal/event.hpp" -#include "pal/mutex.hpp" #include "pal/semaphore.hpp" #include "pal/dbgmsg.h" #include @@ -39,7 +38,6 @@ static PalObjectTypeId sg_rgWaitObjectsIds[] = { otiAutoResetEvent, otiManualResetEvent, - otiMutex, otiSemaphore, otiProcess, otiThread @@ -51,7 +49,6 @@ static PalObjectTypeId sg_rgSignalableObjectIds[] = { otiAutoResetEvent, otiManualResetEvent, - otiMutex, otiSemaphore }; static CAllowedObjectTypes sg_aotSignalableObject(sg_rgSignalableObjectIds, ARRAY_SIZE(sg_rgSignalableObjectIds)); @@ -679,10 +676,6 @@ DWORD CorUnix::InternalSignalObjectAndWait( palError = InternalSetEvent(thread, hObjectToSignal, true /* fSetEvent */); break; - case otiMutex: - palError = InternalReleaseMutex(thread, hObjectToSignal); - break; - case otiSemaphore: palError = InternalReleaseSemaphore(thread, hObjectToSignal, 1 /* lReleaseCount */, nullptr /* lpPreviousCount */); break; diff --git a/src/coreclr/pal/src/synchobj/mutex.cpp b/src/coreclr/pal/src/synchobj/mutex.cpp deleted file mode 100644 index b189f7269d6879..00000000000000 --- a/src/coreclr/pal/src/synchobj/mutex.cpp +++ /dev/null @@ -1,406 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*++ - -Module Name: - - mutex.ccpp - -Abstract: - - Implementation of mutex synchroniztion object as described in - the WIN32 API - -Revision History: - ---*/ - -#include "pal/dbgmsg.h" - -SET_DEFAULT_DEBUG_CHANNEL(SYNC); // some headers have code with asserts, so do this first - -#include "pal/mutex.hpp" -#include "pal/file.hpp" -#include "pal/thread.hpp" -#include "pal/utils.h" - -#include "../synchmgr/synchmanager.hpp" - -#include -#include - -#include -#include -#include -#include "minipal/time.h" - -using namespace CorUnix; - -/* ------------------- Definitions ------------------------------*/ - -CObjectType CorUnix::otMutex( - otiMutex, - NULL, // No cleanup routine - 0, // No immutable data - NULL, // No immutable data copy routine - NULL, // No immutable data cleanup routine - 0, // No process local data - NULL, // No process local data cleanup routine - CObjectType::WaitableObject, - CObjectType::ObjectCanBeUnsignaled, - CObjectType::ThreadReleaseAltersSignalCount, - CObjectType::OwnershipTracked - ); - -static CAllowedObjectTypes aotMutex(otiMutex); - -/*++ -Function: - CreateMutexW - -Note: - lpMutexAttributes currently ignored: - -- Win32 object security not supported - -- handles to mutex objects are not inheritable - - See MSDN docs on CreateMutexW for all other parameters. ---*/ - -HANDLE -PALAPI -CreateMutexW( - IN LPSECURITY_ATTRIBUTES lpMutexAttributes, - IN BOOL bInitialOwner, - IN LPCWSTR lpName) -{ - _ASSERTE(lpName == nullptr); - HANDLE hMutex = NULL; - PAL_ERROR palError; - CPalThread *pthr = NULL; - - PERF_ENTRY(CreateMutexW); - ENTRY("CreateMutexW(lpMutexAttributes=%p, bInitialOwner=%d\n", - lpMutexAttributes, - bInitialOwner - ); - - pthr = InternalGetCurrentThread(); - - { - palError = InternalCreateMutex( - pthr, - nullptr, // lpMutexAttributes currently ignored - bInitialOwner, - &hMutex - ); - } - - // - // We always need to set last error, even on success: - // we need to protect ourselves from the situation - // where last error is set to ERROR_ALREADY_EXISTS on - // entry to the function - // - - pthr->SetLastError(palError); - - LOGEXIT("CreateMutexW returns HANDLE %p\n", hMutex); - PERF_EXIT(CreateMutexW); - return hMutex; -} - -/*++ -Function: -CreateMutexExW - -Note: -lpMutexAttributes currently ignored: --- Win32 object security not supported --- handles to mutex objects are not inheritable - -Parameters: -See MSDN doc. ---*/ - -HANDLE -PALAPI -CreateMutexExW( - IN LPSECURITY_ATTRIBUTES lpMutexAttributes, - IN LPCWSTR lpName, - IN DWORD dwFlags, - IN DWORD dwDesiredAccess) -{ - return CreateMutexW(lpMutexAttributes, (dwFlags & CREATE_MUTEX_INITIAL_OWNER) != 0, lpName); -} - -/*++ -Function: - InternalCreateMutex - -Note: - lpMutexAttributes currently ignored: - -- Win32 object security not supported - -- handles to mutex objects are not inheritable - -Parameters: - errors -- An optional wrapper for system call errors, for more detailed error information. - pthr -- thread data for calling thread - phEvent -- on success, receives the allocated mutex handle - - See MSDN docs on CreateMutex for all other parameters. ---*/ - -PAL_ERROR -CorUnix::InternalCreateMutex( - CPalThread *pthr, - LPSECURITY_ATTRIBUTES lpMutexAttributes, - BOOL bInitialOwner, - HANDLE *phMutex - ) -{ - CObjectAttributes oa(nullptr, lpMutexAttributes); - PAL_ERROR palError = NO_ERROR; - IPalObject *pobjMutex = NULL; - IPalObject *pobjRegisteredMutex = NULL; - ISynchStateController *pssc = NULL; - HANDLE hMutex = nullptr; - - _ASSERTE(NULL != pthr); - _ASSERTE(NULL != phMutex); - - ENTRY("InternalCreateMutex(pthr=%p, lpMutexAttributes=%p, bInitialOwner=%d" - ", phMutex=%p)\n", - pthr, - lpMutexAttributes, - bInitialOwner, - phMutex - ); - - palError = g_pObjectManager->AllocateObject( - pthr, - &otMutex, - &oa, - &pobjMutex - ); - - if (NO_ERROR != palError) - { - goto InternalCreateMutexExit; - } - - palError = pobjMutex->GetSynchStateController( - pthr, - &pssc - ); - - if (NO_ERROR != palError) - { - ASSERT("Unable to create state controller (%d)\n", palError); - goto InternalCreateMutexExit; - } - - if (bInitialOwner) - { - palError = pssc->SetOwner(pthr); - } - else - { - palError = pssc->SetSignalCount(1); - } - - pssc->ReleaseController(); - - if (NO_ERROR != palError) - { - ASSERT("Unable to set initial mutex state (%d)\n", palError); - goto InternalCreateMutexExit; - } - - palError = g_pObjectManager->RegisterObject( - pthr, - pobjMutex, - &aotMutex, - &hMutex, - &pobjRegisteredMutex - ); - _ASSERTE(palError != ERROR_ALREADY_EXISTS); // Mutexes can't have names - _ASSERTE(palError != NO_ERROR || pobjRegisteredMutex == pobjMutex); - _ASSERTE((palError == NO_ERROR) == (hMutex != nullptr)); - - // When RegisterObject succeeds, the object would have an additional reference from the handle, and one reference is - // released below through pobjRegisteredMutex. When RegisterObject fails, it releases the initial reference to the object. - // Either way, pobjMutex is invalidated by the above call to RegisterObject. - pobjMutex = nullptr; - - if (palError != NO_ERROR) - { - goto InternalCreateMutexExit; - } - - pobjRegisteredMutex->ReleaseReference(pthr); - pobjRegisteredMutex = nullptr; - - *phMutex = hMutex; - hMutex = nullptr; - -InternalCreateMutexExit: - - _ASSERTE(pobjRegisteredMutex == nullptr); - _ASSERTE(hMutex == nullptr); - - if (pobjMutex != nullptr) - { - pobjMutex->ReleaseReference(pthr); - } - - LOGEXIT("InternalCreateMutex returns %i\n", palError); - - return palError; -} - -/*++ -Function: - ReleaseMutex - -Parameters: - See MSDN doc. ---*/ - -BOOL -PALAPI -ReleaseMutex( IN HANDLE hMutex ) -{ - PAL_ERROR palError = NO_ERROR; - CPalThread *pthr = NULL; - - PERF_ENTRY(ReleaseMutex); - ENTRY("ReleaseMutex(hMutex=%p)\n", hMutex); - - pthr = InternalGetCurrentThread(); - - palError = InternalReleaseMutex(pthr, hMutex); - - if (NO_ERROR != palError) - { - pthr->SetLastError(palError); - } - - LOGEXIT("ReleaseMutex returns BOOL %d\n", (NO_ERROR == palError)); - PERF_EXIT(ReleaseMutex); - return (NO_ERROR == palError); -} - -/*++ -Function: - InternalReleaseMutex - -Parameters: - pthr -- thread data for calling thread - - See MSDN docs on ReleaseMutex for all other parameters ---*/ - -PAL_ERROR -CorUnix::InternalReleaseMutex( - CPalThread *pthr, - HANDLE hMutex - ) -{ - PAL_ERROR palError = NO_ERROR; - IPalObject *pobjMutex = NULL; - ISynchStateController *pssc = NULL; - PalObjectTypeId objectTypeId; - - _ASSERTE(NULL != pthr); - - ENTRY("InternalReleaseMutex(pthr=%p, hMutex=%p)\n", - pthr, - hMutex - ); - - palError = g_pObjectManager->ReferenceObjectByHandle( - pthr, - hMutex, - &aotMutex, - &pobjMutex - ); - - if (NO_ERROR != palError) - { - ERROR("Unable to obtain object for handle %p (error %d)!\n", hMutex, palError); - goto InternalReleaseMutexExit; - } - - palError = pobjMutex->GetSynchStateController( - pthr, - &pssc - ); - - if (NO_ERROR != palError) - { - ASSERT("Error %d obtaining synch state controller\n", palError); - goto InternalReleaseMutexExit; - } - - palError = pssc->DecrementOwnershipCount(); - - if (NO_ERROR != palError) - { - ERROR("Error %d decrementing mutex ownership count\n", palError); - goto InternalReleaseMutexExit; - } - -InternalReleaseMutexExit: - - if (NULL != pssc) - { - pssc->ReleaseController(); - } - - if (NULL != pobjMutex) - { - pobjMutex->ReleaseReference(pthr); - } - - LOGEXIT("InternalReleaseMutex returns %i\n", palError); - - return palError; -} - -/* Basic spinlock implementation */ -void SPINLOCKAcquire (LONG * lock, unsigned int flags) -{ - size_t loop_seed = 1, loop_count = 0; - - if (flags & SYNCSPINLOCK_F_ASYMMETRIC) - { - loop_seed = ((size_t)pthread_self() % 10) + 1; - } - while (InterlockedCompareExchange(lock, 1, 0)) - { - if (!(flags & SYNCSPINLOCK_F_ASYMMETRIC) || (++loop_count % loop_seed)) - { -#if PAL_IGNORE_NORMAL_THREAD_PRIORITY - struct timespec tsSleepTime; - tsSleepTime.tv_sec = 0; - tsSleepTime.tv_nsec = 1; - nanosleep(&tsSleepTime, NULL); -#else - sched_yield(); -#endif - } - } - -} - -void SPINLOCKRelease (LONG * lock) -{ - VolatileStore(lock, 0); -} - -DWORD SPINLOCKTryAcquire (LONG * lock) -{ - return InterlockedCompareExchange(lock, 1, 0); - // only returns 0 or 1. -} diff --git a/src/coreclr/pal/src/thread/thread.cpp b/src/coreclr/pal/src/thread/thread.cpp index ec6920f922cade..fbe9d8501d03f1 100644 --- a/src/coreclr/pal/src/thread/thread.cpp +++ b/src/coreclr/pal/src/thread/thread.cpp @@ -15,7 +15,6 @@ SET_DEFAULT_DEBUG_CHANNEL(THREAD); // some headers have code with asserts, so do #include "pal/corunix.hpp" #include "pal/context.h" #include "pal/thread.hpp" -#include "pal/mutex.hpp" #include "pal/handlemgr.hpp" #include "pal/seh.hpp" #include "pal/signal.hpp" diff --git a/src/coreclr/pal/src/thread/threadsusp.cpp b/src/coreclr/pal/src/thread/threadsusp.cpp index 867f46b3fa38f2..74462ce4fc8d9f 100644 --- a/src/coreclr/pal/src/thread/threadsusp.cpp +++ b/src/coreclr/pal/src/thread/threadsusp.cpp @@ -22,7 +22,6 @@ Revision History: #include "pal/corunix.hpp" #include "pal/thread.hpp" -#include "pal/mutex.hpp" #include "pal/seh.hpp" #include "pal/init.h" #include "pal/dbgmsg.h" @@ -50,7 +49,53 @@ CONST BYTE WAKEUPCODE=0x2A; suspension mutex or spinlock. The downside is that it restricts us to only performing one suspension or resumption in the PAL at a time. */ #ifdef USE_GLOBAL_LOCK_FOR_SUSPENSION -static LONG g_ssSuspensionLock = 0; + +#define SYNCSPINLOCK_F_ASYMMETRIC 1 + +#define SPINLOCKInit(lock) (*(lock) = 0) + +namespace +{ + LONG g_ssSuspensionLock = 0; + + /* Basic spinlock implementation */ + void SPINLOCKAcquire (LONG * lock, unsigned int flags) + { + size_t loop_seed = 1, loop_count = 0; + + if (flags & SYNCSPINLOCK_F_ASYMMETRIC) + { + loop_seed = ((size_t)pthread_self() % 10) + 1; + } + while (InterlockedCompareExchange(lock, 1, 0)) + { + if (!(flags & SYNCSPINLOCK_F_ASYMMETRIC) || (++loop_count % loop_seed)) + { +#if PAL_IGNORE_NORMAL_THREAD_PRIORITY + struct timespec tsSleepTime; + tsSleepTime.tv_sec = 0; + tsSleepTime.tv_nsec = 1; + nanosleep(&tsSleepTime, NULL); +#else + sched_yield(); +#endif + } + } + + } + + void SPINLOCKRelease (LONG * lock) + { + VolatileStore(lock, 0); + } + + DWORD SPINLOCKTryAcquire (LONG * lock) + { + return InterlockedCompareExchange(lock, 1, 0); + // only returns 0 or 1. + } + +} #endif /*++ diff --git a/src/coreclr/pal/tests/palsuite/CMakeLists.txt b/src/coreclr/pal/tests/palsuite/CMakeLists.txt index 5b96ab5fa81568..3a8ca5394f78dc 100644 --- a/src/coreclr/pal/tests/palsuite/CMakeLists.txt +++ b/src/coreclr/pal/tests/palsuite/CMakeLists.txt @@ -40,16 +40,10 @@ add_executable_clr(paltests #composite/object_management/event/nonshared/main.cpp #composite/object_management/event/shared/event.cpp #composite/object_management/event/shared/main.cpp - #composite/object_management/mutex/nonshared/main.cpp - #composite/object_management/mutex/nonshared/mutex.cpp - #composite/object_management/mutex/shared/main.cpp - #composite/object_management/mutex/shared/mutex.cpp #composite/object_management/semaphore/nonshared/main.cpp #composite/object_management/semaphore/nonshared/semaphore.cpp #composite/object_management/semaphore/shared/main.cpp #composite/object_management/semaphore/shared/semaphore.cpp - #composite/wfmo/main.cpp - #composite/wfmo/mutex.cpp c_runtime/atof/test1/test1.cpp c_runtime/atoi/test1/test1.cpp c_runtime/isalnum/test1/test1.cpp @@ -376,11 +370,6 @@ add_executable_clr(paltests # pal_specific/PAL_RegisterLibraryW_UnregisterLibraryW/test2_neg/reg_unreg_libraryw_neg.cpp samples/test1/test.cpp samples/test2/test.cpp - threading/CreateEventW/test1/test1.cpp - threading/CreateEventW/test2/test2.cpp - threading/CreateEventW/test3/test3.cpp - threading/CreateMutexW_ReleaseMutex/test1/CreateMutexW.cpp - threading/CreateMutexW_ReleaseMutex/test2/CreateMutexW.cpp threading/CreateProcessW/test1/childProcess.cpp threading/CreateProcessW/test1/parentProcess.cpp threading/CreateProcessW/test2/childprocess.cpp @@ -393,12 +382,9 @@ add_executable_clr(paltests threading/CreateThread/test3/test3.cpp threading/DuplicateHandle/test1/test1.cpp threading/DuplicateHandle/test10/test10.cpp - threading/DuplicateHandle/test11/childprocess.cpp - threading/DuplicateHandle/test11/test11.cpp threading/DuplicateHandle/test12/test12.cpp threading/DuplicateHandle/test2/test2.cpp threading/DuplicateHandle/test3/test3.cpp - threading/DuplicateHandle/test4/test4.cpp threading/DuplicateHandle/test7/test7.cpp threading/DuplicateHandle/test8/test8.cpp # threading/DuplicateHandle/test9/test9.cpp @@ -419,7 +405,6 @@ add_executable_clr(paltests threading/OpenEventW/test2/test2.cpp threading/OpenEventW/test3/childprocess.cpp threading/OpenEventW/test3/test3.cpp - threading/OpenEventW/test4/test4.cpp threading/OpenEventW/test5/test5.cpp threading/OpenProcess/test1/childProcess.cpp threading/OpenProcess/test1/test1.cpp @@ -431,7 +416,6 @@ add_executable_clr(paltests threading/QueueUserAPC/test5/test5.cpp threading/QueueUserAPC/test6/test6.cpp threading/QueueUserAPC/test7/test7.cpp - threading/ReleaseMutex/test3/ReleaseMutex.cpp threading/releasesemaphore/test1/test.cpp threading/ResetEvent/test1/test1.cpp threading/ResetEvent/test2/test2.cpp @@ -453,15 +437,11 @@ add_executable_clr(paltests threading/WaitForMultipleObjects/test1/test1.cpp threading/WaitForMultipleObjectsEx/test1/test1.cpp threading/WaitForMultipleObjectsEx/test2/test2.cpp - threading/WaitForMultipleObjectsEx/test3/test3.cpp - threading/WaitForMultipleObjectsEx/test4/test4.cpp threading/WaitForMultipleObjectsEx/test5/helper.cpp threading/WaitForMultipleObjectsEx/test5/test5.cpp threading/WaitForSingleObject/test1/test1.cpp - threading/WaitForSingleObject/WFSOExMutexTest/WFSOExMutexTest.cpp threading/WaitForSingleObject/WFSOExSemaphoreTest/WFSOExSemaphoreTest.cpp threading/WaitForSingleObject/WFSOExThreadTest/WFSOExThreadTest.cpp - threading/WaitForSingleObject/WFSOMutexTest/WFSOMutexTest.cpp threading/WaitForSingleObject/WFSOProcessTest/ChildProcess.cpp threading/WaitForSingleObject/WFSOProcessTest/WFSOProcessTest.cpp threading/WaitForSingleObject/WFSOSemaphoreTest/WFSOSemaphoreTest.cpp diff --git a/src/coreclr/pal/tests/palsuite/compilableTests.txt b/src/coreclr/pal/tests/palsuite/compilableTests.txt index 930d5979fa0318..d3f96b642ca5e2 100644 --- a/src/coreclr/pal/tests/palsuite/compilableTests.txt +++ b/src/coreclr/pal/tests/palsuite/compilableTests.txt @@ -278,11 +278,6 @@ pal_specific/PAL_RegisterLibraryW_UnregisterLibraryW/test1/paltest_pal_registerl pal_specific/PAL_RegisterLibraryW_UnregisterLibraryW/test2_neg/paltest_reg_unreg_libraryw_neg samples/test1/paltest_samples_test1 samples/test2/paltest_samples_test2 -threading/CreateEventW/test1/paltest_createeventw_test1 -threading/CreateEventW/test2/paltest_createeventw_test2 -threading/CreateEventW/test3/paltest_createeventw_test3 -threading/CreateMutexW_ReleaseMutex/test1/paltest_createmutexw_releasemutex_test1 -threading/CreateMutexW_ReleaseMutex/test2/paltest_createmutexw_releasemutex_test2 threading/CreateProcessW/test1/paltest_createprocessw_test1 threading/CreateProcessW/test2/paltest_createprocessw_test2 threading/CreateSemaphoreW_ReleaseSemaphore/test1/paltest_createsemaphorew_releasesemaphore_test1 @@ -293,11 +288,9 @@ threading/CreateThread/test2/paltest_createthread_test2 threading/CreateThread/test3/paltest_createthread_test3 threading/DuplicateHandle/test1/paltest_duplicatehandle_test1 threading/DuplicateHandle/test10/paltest_duplicatehandle_test10 -threading/DuplicateHandle/test11/paltest_duplicatehandle_test11 threading/DuplicateHandle/test12/paltest_duplicatehandle_test12 threading/DuplicateHandle/test2/paltest_duplicatehandle_test2 threading/DuplicateHandle/test3/paltest_duplicatehandle_test3 -threading/DuplicateHandle/test4/paltest_duplicatehandle_test4 threading/DuplicateHandle/test7/paltest_duplicatehandle_test7 threading/DuplicateHandle/test8/paltest_duplicatehandle_test8 threading/DuplicateHandle/test9/paltest_duplicatehandle_test9 @@ -315,7 +308,6 @@ threading/GetExitCodeProcess/test1/paltest_getexitcodeprocess_test1 threading/OpenEventW/test1/paltest_openeventw_test1 threading/OpenEventW/test2/paltest_openeventw_test2 threading/OpenEventW/test3/paltest_openeventw_test3 -threading/OpenEventW/test4/paltest_openeventw_test4 threading/OpenEventW/test5/paltest_openeventw_test5 threading/OpenProcess/test1/paltest_openprocess_test1 threading/QueryThreadCycleTime/test1/paltest_querythreadcycletime_test1 @@ -326,7 +318,6 @@ threading/QueueUserAPC/test4/paltest_queueuserapc_test4 threading/QueueUserAPC/test5/paltest_queueuserapc_test5 threading/QueueUserAPC/test6/paltest_queueuserapc_test6 threading/QueueUserAPC/test7/paltest_queueuserapc_test7 -threading/ReleaseMutex/test3/paltest_releasemutex_test3 threading/releasesemaphore/test1/paltest_releasesemaphore_test1 threading/ResetEvent/test1/paltest_resetevent_test1 threading/ResetEvent/test2/paltest_resetevent_test2 @@ -349,11 +340,9 @@ threading/WaitForMultipleObjects/test1/paltest_waitformultipleobjects_test1 threading/WaitForMultipleObjectsEx/test1/paltest_waitformultipleobjectsex_test1 threading/WaitForMultipleObjectsEx/test2/paltest_waitformultipleobjectsex_test2 threading/WaitForMultipleObjectsEx/test3/paltest_waitformultipleobjectsex_test3 -threading/WaitForMultipleObjectsEx/test4/paltest_waitformultipleobjectsex_test4 threading/WaitForMultipleObjectsEx/test5/paltest_waitformultipleobjectsex_test5 threading/WaitForMultipleObjectsEx/test6/paltest_waitformultipleobjectsex_test6 threading/WaitForSingleObject/test1/paltest_waitforsingleobject_test1 -threading/WaitForSingleObject/WFSOExMutexTest/paltest_waitforsingleobject_wfsoexmutextest threading/WaitForSingleObject/WFSOExSemaphoreTest/paltest_waitforsingleobject_wfsoexsemaphoretest threading/WaitForSingleObject/WFSOExThreadTest/paltest_waitforsingleobject_wfsoexthreadtest threading/WaitForSingleObject/WFSOMutexTest/paltest_waitforsingleobject_wfsomutextest diff --git a/src/coreclr/pal/tests/palsuite/composite/object_management/mutex/nonshared/main.cpp b/src/coreclr/pal/tests/palsuite/composite/object_management/mutex/nonshared/main.cpp deleted file mode 100644 index 3878df1fe9ef7b..00000000000000 --- a/src/coreclr/pal/tests/palsuite/composite/object_management/mutex/nonshared/main.cpp +++ /dev/null @@ -1,229 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** Source Code: main.c and mutex.c -** main.c creates process and waits for all processes to get over -** mutex.c creates a mutex and then calls threads which will contend for the mutex -** -** This test is for Object Management Test case for Mutex where Object type is not shareable. -** Algorithm -** o Create PROCESS_COUNT processes. -** o Main Thread of each process creates OBJECT_TYPE Object -** -** Author: ShamitP -**============================================================ -*/ - -#include -#include "resulttime.h" - -/* Test Input Variables */ -unsigned int PROCESS_COUNT = 2; -unsigned int THREAD_COUNT = 20; -unsigned int REPEAT_COUNT = 4000; -unsigned int RELATION_ID = 1001; - - -struct TestStats{ - DWORD operationTime; - unsigned int relationId; - unsigned int processCount; - unsigned int threadCount; - unsigned int repeatCount; - char* buildNumber; - -}; - -int GetParameters( int argc, char **argv) -{ - if( (argc != 5) || ((argc == 1) && !strcmp(argv[1],"/?")) - || !strcmp(argv[1],"/h") || !strcmp(argv[1],"/H")) - { - printf("PAL -Composite Object Management Mutex Test\n"); - printf("Usage:\n"); - printf("main\n\t[PROCESS_COUNT [greater than 1] \n"); - printf("\t[THREAD_COUNT [greater than 1] \n"); - printf("\t[REPEAT_COUNT [greater than 1]\n"); - printf("\t[RELATION_ID [greater than 1]\n"); - - - return -1; - } - - PROCESS_COUNT = atoi(argv[1]); - if( (PROCESS_COUNT < 1) || (PROCESS_COUNT > MAXIMUM_WAIT_OBJECTS) ) - { - printf("\nMain Process:Invalid PROCESS_COUNT number, Pass greater than 1 and less than PROCESS_COUNT %d\n", MAXIMUM_WAIT_OBJECTS); - return -1; - } - - THREAD_COUNT = atoi(argv[2]); - if( (THREAD_COUNT < 1) || (THREAD_COUNT > MAXIMUM_WAIT_OBJECTS) ) - { - printf("\nInvalid THREAD_COUNT number, Pass greater than 1 and less than %d\n", MAXIMUM_WAIT_OBJECTS); - return -1; - } - - REPEAT_COUNT = atoi(argv[3]); - if( REPEAT_COUNT < 1) - { - printf("\nMain Process:Invalid REPEAT_COUNT number, Pass greater than 1\n"); - return -1; - } - - RELATION_ID = atoi(argv[4]); - if( RELATION_ID < 1) - { - printf("\nMain Process:Invalid RELATION_ID number, Pass greater than 1\n"); - return -1; - } - - return 0; -} - -PALTEST(composite_object_management_mutex_nonshared_paltest_mutex_nonshared, "composite/object_management/mutex/nonshared/paltest_mutex_nonshared") -{ - unsigned int i = 0; - HANDLE hProcess[MAXIMUM_WAIT_OBJECTS]; - HANDLE hMutexHandle[MAXIMUM_WAIT_OBJECTS]; - - STARTUPINFO si[MAXIMUM_WAIT_OBJECTS]; - PROCESS_INFORMATION pi[MAXIMUM_WAIT_OBJECTS]; - - const char *ObjName = "Mutex"; - char lpCommandLine[MAX_PATH] = ""; - - int returnCode = 0; - DWORD processReturnCode = 0; - int testReturnCode = PASS; - - char fileName[MAX_PATH]; - FILE *pFile = NULL; - DWORD dwStartTime; - struct TestStats testStats; - - if(0 != (PAL_Initialize(argc, argv))) - { - return ( FAIL ); - } - - if(GetParameters(argc, argv)) - { - Fail("Error in obtaining the parameters\n"); - } - - /* Register the start time */ - dwStartTime = (DWORD)minipal_lowres_ticks(); - testStats.relationId = RELATION_ID; - testStats.processCount = PROCESS_COUNT; - testStats.threadCount = THREAD_COUNT; - testStats.repeatCount = REPEAT_COUNT; - testStats.buildNumber = getBuildNumber(); - - - _snprintf(fileName, MAX_PATH, "main_mutex_%d_.txt", RELATION_ID); - pFile = fopen(fileName, "w+"); - if(pFile == NULL) - { - Fail("Error in opening main file for write\n"); - } - - for( i = 0; i < PROCESS_COUNT; i++ ) - { - ZeroMemory( lpCommandLine, MAX_PATH ); - if ( _snprintf( lpCommandLine, MAX_PATH-1, "mutex %d %d %d %d", i, THREAD_COUNT, REPEAT_COUNT, RELATION_ID) < 0 ) - { - Fail("Error Insufficient mutex name string length for %s for iteration [%d]\n", ObjName, i); - } - - /* Zero the data structure space */ - ZeroMemory ( &pi[i], sizeof(pi[i]) ); - ZeroMemory ( &si[i], sizeof(si[i]) ); - - /* Set the process flags and standard io handles */ - si[i].cb = sizeof(si[i]); - - if(!CreateProcess( NULL, /* lpApplicationName*/ - lpCommandLine, /* lpCommandLine */ - NULL, /* lpProcessAttributes */ - NULL, /* lpThreadAttributes */ - TRUE, /* bInheritHandles */ - 0, /* dwCreationFlags, */ - NULL, /* lpEnvironment */ - NULL, /* pCurrentDirectory */ - &si[i], /* lpStartupInfo */ - &pi[i] /* lpProcessInformation */ - )) - { - Fail("Process Not created for [%d], the error code is [%d]\n", i, GetLastError()); - } - else - { - hProcess[i] = pi[i].hProcess; -// Trace("Process created for [%d]\n", i); - - } - - //Create Process - - } - - returnCode = WaitForMultipleObjects( PROCESS_COUNT, hProcess, TRUE, INFINITE); - if( WAIT_OBJECT_0 != returnCode ) - { - Trace("Wait for Object(s) @ Main thread for %d processes returned %d, and GetLastError value is %d\n", PROCESS_COUNT, returnCode, GetLastError()); - testReturnCode = FAIL; - } - - for( i = 0; i < PROCESS_COUNT; i++ ) - { - /* check the exit code from the process */ - if( ! GetExitCodeProcess( pi[i].hProcess, &processReturnCode ) ) - { - Trace( "GetExitCodeProcess call failed for iteration %d with error code %u\n", - i, GetLastError() ); - - testReturnCode = FAIL; - } - - if(processReturnCode == FAIL) - { - Trace( "Process [%d] failed and returned FAIL\n", i); - testReturnCode = FAIL; - } - - if(!CloseHandle(pi[i].hThread)) - { - Trace("Error:%d: CloseHandle failed for Process [%d] hThread\n", GetLastError(), i); - testReturnCode = FAIL; - } - - if(!CloseHandle(pi[i].hProcess) ) - { - Trace("Error:%d: CloseHandle failed for Process [%d] hProcess\n", GetLastError(), i); - testReturnCode = FAIL; - } - } - - testStats.operationTime = GetTimeDiff(dwStartTime); - fprintf(pFile, "%d,%d,%d,%d,%d,%s\n", testStats.operationTime, testStats.relationId, testStats.processCount, testStats.threadCount, testStats.repeatCount, testStats.buildNumber); - if(fclose(pFile)) - { - Trace("Error: fclose failed for pFile\n"); - testReturnCode = FAIL; - } - - if( testReturnCode == PASS) - { - Trace("Test Passed\n"); - } - else - { - Trace("Test Failed\n"); - } - - PAL_Terminate(); - return testReturnCode; -} diff --git a/src/coreclr/pal/tests/palsuite/composite/object_management/mutex/nonshared/mutex.cpp b/src/coreclr/pal/tests/palsuite/composite/object_management/mutex/nonshared/mutex.cpp deleted file mode 100644 index d22381c288313d..00000000000000 --- a/src/coreclr/pal/tests/palsuite/composite/object_management/mutex/nonshared/mutex.cpp +++ /dev/null @@ -1,326 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** Source Code: main.c and mutex.c -** main.c creates process and waits for all processes to get over -** mutex.c creates a mutex and then calls threads which will contend for the mutex -** -** This test is for Object Management Test case for Mutex where Object type is not shareable. -** Algorithm -** o Create PROCESS_COUNT processes. -** o Main Thread of each process creates OBJECT_TYPE Object -** -** Author: ShamitP -**============================================================ -*/ - -#include -#include "resultbuffer.h" -#include "resulttime.h" - -#define TIMEOUT 5000 -/* Test Input Variables */ -unsigned int USE_PROCESS_COUNT = 0; -unsigned int THREAD_COUNT = 0; -unsigned int REPEAT_COUNT = 0; -unsigned int RELATION_ID = 1001; - -/* Capture statistics at per thread basis */ -struct statistics{ - unsigned int processId; - unsigned int operationsFailed; - unsigned int operationsPassed; - unsigned int operationsTotal; - DWORD operationTime; - unsigned int relationId; -}; - -struct ProcessStats{ - unsigned int processId; - DWORD operationTime; - unsigned int relationId; -}; - -HANDLE StartTestsEvHandle = NULL; -HANDLE hMutexHandle = NULL; - -/* Results Buffer */ -ResultBuffer *resultBuffer = NULL; - -int testStatus; - -void PALAPI Run_Thread_mutex_nonshared(LPVOID lpParam); - -int GetParameters( int argc, char **argv) -{ - if( (argc != 5) || ((argc == 1) && !strcmp(argv[1],"/?")) - || !strcmp(argv[1],"/h") || !strcmp(argv[1],"/H")) - { - printf("PAL -Composite Object Management Mutex Test\n"); - printf("Usage:\n"); - printf("mutex\n\t[USE_PROCESS_COUNT ( greater than 1] \n"); - printf("\t[THREAD_COUNT ( greater than 1] \n"); - printf("\t[REPEAT_COUNT ( greater than 1]\n"); - printf("\t[RELATION_ID [greater than 1]\n"); - return -1; - } - - USE_PROCESS_COUNT = atoi(argv[1]); - if( USE_PROCESS_COUNT < 0) - { - printf("\nInvalid USE_PROCESS_COUNT number, Pass greater than 1\n"); - return -1; - } - - THREAD_COUNT = atoi(argv[2]); - if( (THREAD_COUNT < 1) || (THREAD_COUNT > MAXIMUM_WAIT_OBJECTS) ) - { - printf("\nInvalid THREAD_COUNT number, Pass greater than 1 and less than %d\n", MAXIMUM_WAIT_OBJECTS); - return -1; - } - - REPEAT_COUNT = atoi(argv[3]); - if( REPEAT_COUNT < 1) - { - printf("\nInvalid REPEAT_COUNT number, Pass greater than 1\n"); - return -1; - } - - RELATION_ID = atoi(argv[4]); - if( RELATION_ID < 1) - { - printf("\nMain Process:Invalid RELATION_ID number, Pass greater than 1\n"); - return -1; - } - - return 0; -} - -PALTEST(composite_object_management_mutex_nonshared_paltest_mutex_nonshared, "composite/object_management/mutex/nonshared/paltest_mutex_nonshared") -{ - unsigned int i = 0; - HANDLE hThread[MAXIMUM_WAIT_OBJECTS]; - DWORD threadId[MAXIMUM_WAIT_OBJECTS]; - - const char sTmpEventName[MAX_PATH] = "StartTestEvent"; - - DWORD dwParam = 0; - - int returnCode = 0; - - /* Variables to capture the file name and the file pointer at thread level*/ - char fileName[MAX_PATH]; - FILE *pFile = NULL; - struct statistics* buffer = NULL; - int statisticsSize = 0; - - /* Variables to capture the file name and the file pointer at process level*/ - char processFileName[MAX_PATH]; - FILE *pProcessFile = NULL; - struct ProcessStats processStats; - DWORD dwStartTime; - - testStatus = PASS; - - if(0 != (PAL_Initialize(argc, argv))) - { - return ( FAIL ); - } - - if(GetParameters(argc, argv)) - { - Fail("Error in obtaining the parameters\n"); - } - - /* Register the start time */ - dwStartTime = (DWORD)minipal_lowres_ticks(); - processStats.relationId = RELATION_ID; - processStats.processId = USE_PROCESS_COUNT; - - _snprintf(processFileName, MAX_PATH, "%d_process_mutex_%d_.txt", USE_PROCESS_COUNT,RELATION_ID); - pProcessFile = fopen(processFileName, "w+"); - if(pProcessFile == NULL) - { - Fail("Error in opening process File file for write for process [%d]\n", USE_PROCESS_COUNT); - } - - statisticsSize = sizeof(struct statistics); - - _snprintf(fileName, MAX_PATH, "%d_thread_mutex_%d_.txt", USE_PROCESS_COUNT, RELATION_ID); - pFile = fopen(fileName, "w+"); - if(pFile == NULL) - { - Fail("Error in opening file for write for process [%d]\n", USE_PROCESS_COUNT); - } - // For each thread we will log operations failed (int), passed (int), total (int) - // and number of ticks (DWORD) for the operations - resultBuffer = new ResultBuffer( THREAD_COUNT, statisticsSize); - - StartTestsEvHandle = CreateEvent( NULL, /* lpEventAttributes*/ - TRUE, /* bManualReset */ - FALSE, /* bInitialState */ - NULL - ); /* name of Event */ - - if( StartTestsEvHandle == NULL ) - { - Fail("Error:%d: Unexpected failure " - "to create %s Event for process count %d\n", GetLastError(), sTmpEventName, USE_PROCESS_COUNT ); - - } - - /* Create StartTest Event */ - - hMutexHandle = CreateMutex( - NULL, - FALSE, /* bInitialOwner, owns initially */ - NULL - ); - - if( hMutexHandle == NULL) - { - Fail("Unable to create Mutex handle for process id [%d], returned error [%d]\n", i, GetLastError()); - } - /* We already assume that the mutex was created previously*/ - - for( i = 0; i < THREAD_COUNT; i++ ) - { - dwParam = (int) i; - //Create thread - hThread[i] = CreateThread( - NULL, /* no security attributes */ - 0, /* use default stack size */ - (LPTHREAD_START_ROUTINE)Run_Thread_mutex_nonshared,/* thread function */ - (LPVOID)dwParam, /* argument to thread function */ - 0, /* use default creation flags */ - &threadId[i] /* returns the thread identifier*/ - ); - - if(hThread[i] == NULL) - { - Fail("Create Thread failed for %d process, and GetLastError value is %d\n", USE_PROCESS_COUNT, GetLastError()); - } - - } - - if (!SetEvent(StartTestsEvHandle)) - { - Fail("Set Event for Start Tests failed for %d process, and GetLastError value is %d\n", USE_PROCESS_COUNT, GetLastError()); - } - - /* Test running */ - returnCode = WaitForMultipleObjects( THREAD_COUNT, hThread, TRUE, INFINITE); - - if( WAIT_OBJECT_0 != returnCode ) - { - Trace("Wait for Object(s) for %d process returned %d, and GetLastError value is %d\n", USE_PROCESS_COUNT, returnCode, GetLastError()); - testStatus = FAIL; - } - - processStats.operationTime = GetTimeDiff(dwStartTime); - - /* Write to a file*/ - if(pFile!= NULL) - { - for( i = 0; i < THREAD_COUNT; i++ ) - { - buffer = (struct statistics *)resultBuffer->getResultBuffer(i); - returnCode = fprintf(pFile, "%d,%d,%d,%d,%lu,%d\n", buffer->processId, buffer->operationsFailed, buffer->operationsPassed, buffer->operationsTotal, buffer->operationTime, buffer->relationId ); - } - } - fclose(pFile); - - fprintf(pProcessFile, "%d,%d,%d\n", USE_PROCESS_COUNT, processStats.operationTime, processStats.relationId ); - fclose(pProcessFile); - - /* Logging for the test case over, clean up the handles */ - - for( i = 0; i < THREAD_COUNT; i++ ) - { - if(!CloseHandle(hThread[i]) ) - { - Trace("Error:%d: CloseHandle failed for Process [%d] hThread[%d]\n", GetLastError(), USE_PROCESS_COUNT, i); - testStatus = FAIL; - } - } - - if(!CloseHandle(StartTestsEvHandle)) - { - Trace("Error:%d: CloseHandle failed for Process [%d] StartTestsEvHandle\n", GetLastError(), USE_PROCESS_COUNT); - testStatus = FAIL; - } - - if(!CloseHandle(hMutexHandle)) - { - Trace("Error:%d: CloseHandle failed for Process [%d] hMutexHandle\n", GetLastError(), USE_PROCESS_COUNT); - testStatus = FAIL; - } - - PAL_Terminate(); - return testStatus; -} - -void PALAPI Run_Thread_mutex_nonshared (LPVOID lpParam) -{ - unsigned int i = 0; - DWORD dwWaitResult; - - struct statistics stats; - DWORD dwStartTime; - - stats.relationId = RELATION_ID; - stats.processId = USE_PROCESS_COUNT; - stats.operationsFailed = 0; - stats.operationsPassed = 0; - stats.operationsTotal = 0; - stats.operationTime = 0; - - int Id=(int)lpParam; - - dwWaitResult = WaitForSingleObject( - StartTestsEvHandle, // handle to mutex - TIMEOUT); - - if(dwWaitResult != WAIT_OBJECT_0) - { - Trace("Error while waiting for StartTest Event@ thread %d\n", Id); - testStatus = FAIL; - } - - dwStartTime = (DWORD)minipal_lowres_ticks(); - - for( i = 0; i < REPEAT_COUNT; i++ ) - { - dwWaitResult = WaitForSingleObject( - hMutexHandle, // handle to mutex - TIMEOUT); - - if(dwWaitResult != WAIT_OBJECT_0) - { - stats.operationsFailed += 1; - stats.operationsTotal += 1; - testStatus = FAIL; - continue; - } - if (! ReleaseMutex(hMutexHandle)) - { - // Deal with error. - stats.operationsFailed += 1; - stats.operationsTotal += 1; - // Probably need to have while true loop to attempt to release mutex... - testStatus = FAIL; - continue; - } - - stats.operationsTotal += 1; - stats.operationsPassed += 1; - } - - stats.operationTime = GetTimeDiff(dwStartTime); - if(resultBuffer->LogResult(Id, (char *)&stats)) - { - Fail("Error:%d: while writing to shared memory, Thread Id is[%d] and Process id is [%d]\n", GetLastError(), Id, USE_PROCESS_COUNT); - } -} diff --git a/src/coreclr/pal/tests/palsuite/composite/object_management/mutex/shared/main.cpp b/src/coreclr/pal/tests/palsuite/composite/object_management/mutex/shared/main.cpp deleted file mode 100644 index c42b95d9f5b381..00000000000000 --- a/src/coreclr/pal/tests/palsuite/composite/object_management/mutex/shared/main.cpp +++ /dev/null @@ -1,264 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** This test is for Object Management Test case for Mutex where Object type is shareable. -** -** Source Code: main.c and mutex.c -** main.c creates a mutex, creates processes and waits for all processes to get over -** mutex.c create threads which will contend for the mutex -** -** This test is for Object Management Test case for Mutex where Object type is not shareable. -** Algorithm -** o Main Process Creates OBJECT_TYPE Object -** o Create PROCESS_COUNT processes aware of the Shared Object -** -** Author: ShamitP -** -** -**============================================================ -*/ - -#include -#include "resulttime.h" - -/* Test Input Variables */ -unsigned int PROCESS_COUNT = 2; -unsigned int THREAD_COUNT = 2; -unsigned int REPEAT_COUNT = 40000; -unsigned int RELATION_ID = 1001; - - -char objectSuffix[MAX_PATH]; - -struct TestStats{ - DWORD operationTime; - unsigned int relationId; - unsigned int processCount; - unsigned int threadCount; - unsigned int repeatCount; - char* buildNumber; - -}; - -int GetParameters( int argc, char **argv) -{ - if( (!((argc == 5) || (argc == 6) ) )|| ((argc == 1) && !strcmp(argv[1],"/?")) - || !strcmp(argv[1],"/h") || !strcmp(argv[1],"/H")) - { - printf("PAL -Composite Object Management event Test\n"); - printf("Usage:\n"); - printf("main\n\t[PROCESS_COUNT (greater than 1)] \n"); - printf("\t[THREAD_COUNT (greater than 1)] \n"); - printf("\t[REPEAT_COUNT (greater than 1)]\n"); - printf("\t[RELATION_ID [greater than 1]\n"); - printf("\t[Object Name Suffix]\n"); - return -1; - } - - PROCESS_COUNT = atoi(argv[1]); - if( (PROCESS_COUNT < 1) || (PROCESS_COUNT > MAXIMUM_WAIT_OBJECTS) ) - { - printf("\nMain Process:Invalid PROCESS_COUNT number, Pass greater than 1 and less than PROCESS_COUNT %d\n", MAXIMUM_WAIT_OBJECTS); - return -1; - } - - THREAD_COUNT = atoi(argv[2]); - if( (THREAD_COUNT < 1) || (THREAD_COUNT > MAXIMUM_WAIT_OBJECTS) ) - { - printf("\nInvalid THREAD_COUNT number, Pass greater than 1 and less than %d\n", MAXIMUM_WAIT_OBJECTS); - return -1; - } - - REPEAT_COUNT = atoi(argv[3]); - if( REPEAT_COUNT < 1) - { - printf("\nMain Process:Invalid REPEAT_COUNT number, Pass greater than 1\n"); - return -1; - } - - RELATION_ID = atoi(argv[4]); - if( RELATION_ID < 1) - { - printf("\nMain Process:Invalid RELATION_ID number, Pass greater than 1\n"); - return -1; - } - - if(argc == 6) - { - strncpy(objectSuffix, argv[5], MAX_PATH-1); - } - - return 0; -} - -PALTEST(composite_object_management_mutex_shared_paltest_mutex_shared, "composite/object_management/mutex/shared/paltest_mutex_shared") -{ - unsigned int i = 0; - HANDLE hProcess[MAXIMUM_WAIT_OBJECTS]; - HANDLE hMutexHandle; - - STARTUPINFO si[MAXIMUM_WAIT_OBJECTS]; - PROCESS_INFORMATION pi[MAXIMUM_WAIT_OBJECTS]; - - char ObjName[MAX_PATH] = "SHARED_MUTEX"; - char lpCommandLine[MAX_PATH] = ""; - - int returnCode = 0; - DWORD processReturnCode = 0; - int testReturnCode = PASS; - - char fileName[MAX_PATH]; - FILE *pFile = NULL; - DWORD dwStartTime; - struct TestStats testStats; - - if(0 != (PAL_Initialize(argc, argv))) - { - return ( FAIL ); - } - - ZeroMemory( objectSuffix, MAX_PATH ); - - if(GetParameters(argc, argv)) - { - Fail("Error in obtaining the parameters\n"); - } - - if(argc == 5) - { - strncat(ObjName, objectSuffix, MAX_PATH - (sizeof(ObjName) + 1) ); - } - - /* Register the start time */ - dwStartTime = (DWORD)minipal_lowres_ticks(); - testStats.relationId = RELATION_ID; - testStats.processCount = PROCESS_COUNT; - testStats.threadCount = THREAD_COUNT; - testStats.repeatCount = REPEAT_COUNT; - testStats.buildNumber = getBuildNumber(); - - - _snprintf(fileName, MAX_PATH, "main_mutex_%d_.txt", RELATION_ID); - pFile = fopen(fileName, "w+"); - if(pFile == NULL) - { - Fail("Error in opening main file for write\n"); - } - - hMutexHandle = CreateMutex( - NULL, - FALSE, /* bInitialOwner, owns initially */ - ObjName - ); - - if( hMutexHandle == NULL) - { - Fail("Unable to create Mutex handle for Main thread returned error [%d]\n", GetLastError()); - } - - for( i = 0; i < PROCESS_COUNT; i++ ) - { - ZeroMemory( lpCommandLine, MAX_PATH ); - if ( _snprintf( lpCommandLine, MAX_PATH-1, "mutex %d %d %d %d %s", i, THREAD_COUNT, REPEAT_COUNT, RELATION_ID, objectSuffix) < 0 ) - { - Fail ("Error Insufficient mutex name string length for %s for iteration [%d]\n", ObjName, i); - } - - - /* Zero the data structure space */ - ZeroMemory ( &pi[i], sizeof(pi[i]) ); - ZeroMemory ( &si[i], sizeof(si[i]) ); - - /* Set the process flags and standard io handles */ - si[i].cb = sizeof(si[i]); - - //Create Process - if(!CreateProcess( NULL, /* lpApplicationName*/ - lpCommandLine, /* lpCommandLine */ - NULL, /* lpProcessAttributes */ - NULL, /* lpThreadAttributes */ - TRUE, /* bInheritHandles */ - 0, /* dwCreationFlags, */ - NULL, /* lpEnvironment */ - NULL, /* pCurrentDirectory */ - &si[i], /* lpStartupInfo */ - &pi[i] /* lpProcessInformation */ - )) - { - Fail("Process Not created for [%d], the error code is [%d]\n", i, GetLastError()); - } - else - { - hProcess[i] = pi[i].hProcess; -// Trace("Process created for [%d]\n", i); - - } - } - - returnCode = WaitForMultipleObjects( PROCESS_COUNT, hProcess, TRUE, INFINITE); - if( WAIT_OBJECT_0 != returnCode ) - { - Trace("Wait for Object(s) @ Main thread for %d processes returned %d, and GetLastError value is %d\n", PROCESS_COUNT, returnCode, GetLastError()); - testReturnCode = FAIL; - } - - for( i = 0; i < PROCESS_COUNT; i++ ) - { - /* check the exit code from the process */ - if( ! GetExitCodeProcess( pi[i].hProcess, &processReturnCode ) ) - { - Trace( "GetExitCodeProcess call failed for iteration %d with error code %u\n", - i, GetLastError() ); - - testReturnCode = FAIL; - } - - if(processReturnCode == FAIL) - { - Trace( "Process [%d] failed and returned FAIL\n", i); - testReturnCode = FAIL; - } - - if(!CloseHandle(pi[i].hThread)) - { - Trace("Error:%d: CloseHandle failed for Process [%d] hThread\n", GetLastError(), i); - testReturnCode = FAIL; - } - - if(!CloseHandle(pi[i].hProcess) ) - { - Trace("Error:%d: CloseHandle failed for Process [%d] hProcess\n", GetLastError(), i); - testReturnCode = FAIL; - } - } - - testStats.operationTime = GetTimeDiff(dwStartTime); - fprintf(pFile, "%d,%d,%d,%d,%d,%s\n", testStats.operationTime, testStats.relationId, testStats.processCount, testStats.threadCount, testStats.repeatCount, testStats.buildNumber ); - if(fclose(pFile)) - { - Trace("Error: fclose failed for pFile\n"); - testReturnCode = FAIL; - } - - if(!CloseHandle(hMutexHandle)) - { - Trace("Error:%d: CloseHandle failed for hMutexHandle\n", GetLastError()); - testReturnCode = FAIL; - - } - - if( testReturnCode == PASS) - { - Trace("Test Passed\n"); - } - else - { - Trace("Test Failed\n"); - } - - PAL_Terminate(); - return testReturnCode; -} - diff --git a/src/coreclr/pal/tests/palsuite/composite/object_management/mutex/shared/mutex.cpp b/src/coreclr/pal/tests/palsuite/composite/object_management/mutex/shared/mutex.cpp deleted file mode 100644 index 8bc6645c04f4f1..00000000000000 --- a/src/coreclr/pal/tests/palsuite/composite/object_management/mutex/shared/mutex.cpp +++ /dev/null @@ -1,342 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** This test is for Object Management Test case for Mutex where Object type is shareable. -** -** Source Code: main.c and mutex.c -** main.c creates a mutex, creates processes and waits for all processes to get over -** mutex.c create threads which will contend for the mutex -** -** This test is for Object Management Test case for Mutex where Object type is not shareable. -** Algorithm -** o Main Process Creates OBJECT_TYPE Object -** o Create PROCESS_COUNT processes aware of the Shared Object -** -** Author: ShamitP -** -** -**============================================================ -*/ - -#include -#include "resultbuffer.h" -#include "resulttime.h" - -#define TIMEOUT 5000 -/* Test Input Variables */ -unsigned int USE_PROCESS_COUNT = 0; -unsigned int THREAD_COUNT = 0; -unsigned int REPEAT_COUNT = 0; -unsigned int RELATION_ID = 0; - - -/* Capture statistics at per thread basis */ -struct statistics{ - unsigned int processId; - unsigned int operationsFailed; - unsigned int operationsPassed; - unsigned int operationsTotal; - DWORD operationTime; - unsigned int relationId; -}; - -struct ProcessStats{ - unsigned int processId; - DWORD operationTime; - unsigned int relationId; -}; - -HANDLE StartTestsEvHandle = NULL; -HANDLE hMutexHandle = NULL; - -/* Results Buffer */ -ResultBuffer *resultBuffer = NULL; - -int testStatus; - -const char sTmpEventName[MAX_PATH] = "StartTestEvent"; -char objectSuffix[MAX_PATH]; - -void PALAPI Run_Thread_mutex_shared(LPVOID lpParam); - -int GetParameters( int argc, char **argv) -{ - if( (!((argc == 5) || (argc == 6) ) )|| ((argc == 1) && !strcmp(argv[1],"/?")) - || !strcmp(argv[1],"/h") || !strcmp(argv[1],"/H")) - { - printf("PAL -Composite Object Management event Test\n"); - printf("Usage:\n"); - printf("main\n\t[USE_PROCESS_COUNT (greater than 1)] \n"); - printf("\t[THREAD_COUNT (greater than 1)] \n"); - printf("\t[REPEAT_COUNT (greater than 1)]\n"); - printf("\t[RELATION_ID [greater than 1]\n"); - printf("\t[Object Name Suffix]\n"); - - return -1; - } - - USE_PROCESS_COUNT = atoi(argv[1]); - if( USE_PROCESS_COUNT < 0) - { - printf("\nInvalid USE_PROCESS_COUNT number, Pass greater than 1\n"); - return -1; - } - - THREAD_COUNT = atoi(argv[2]); - if( (THREAD_COUNT < 1) || (THREAD_COUNT > MAXIMUM_WAIT_OBJECTS) ) - { - printf("\nInvalid THREAD_COUNT number, Pass greater than 1 and less than %d\n", MAXIMUM_WAIT_OBJECTS); - return -1; - } - - REPEAT_COUNT = atoi(argv[3]); - if( REPEAT_COUNT < 1) - { - printf("\nInvalid REPEAT_COUNT number, Pass greater than 1\n"); - return -1; - } - - RELATION_ID = atoi(argv[4]); - if( RELATION_ID < 1) - { - printf("\nMain Process:Invalid RELATION_ID number, Pass greater than 1\n"); - return -1; - } - - if(argc == 6) - { - strncpy(objectSuffix, argv[5], MAX_PATH-1); - } - - return 0; -} - -PALTEST(composite_object_management_mutex_shared_paltest_mutex_shared, "composite/object_management/mutex/shared/paltest_mutex_shared") -{ - unsigned int i = 0; - HANDLE hThread[MAXIMUM_WAIT_OBJECTS]; - DWORD threadId[MAXIMUM_WAIT_OBJECTS]; - - char ObjName[MAX_PATH] = "SHARED_MUTEX"; - DWORD dwParam = 0; - - int returnCode = 0; - - /* Variables to capture the file name and the file pointer*/ - char fileName[MAX_PATH]; - FILE *pFile = NULL; - struct statistics* buffer = NULL; - int statisticsSize = 0; - - /* Variables to capture the file name and the file pointer at process level*/ - char processFileName[MAX_PATH]; - FILE *pProcessFile = NULL; - struct ProcessStats processStats; - DWORD dwStartTime; - - testStatus = PASS; - - if(0 != (PAL_Initialize(argc, argv))) - { - return ( FAIL ); - } - - ZeroMemory( objectSuffix, MAX_PATH ); - - if(GetParameters(argc, argv)) - { - Fail("Error in obtaining the parameters\n"); - } - - if(argc == 5) - { - strncat(ObjName, objectSuffix, MAX_PATH - (sizeof(ObjName) + 1) ); - } - - /* Register the start time */ - dwStartTime = (DWORD)minipal_lowres_ticks(); - processStats.relationId = RELATION_ID; - processStats.processId = USE_PROCESS_COUNT; - - _snprintf(processFileName, MAX_PATH, "%d_process_mutex_%d_.txt", USE_PROCESS_COUNT, RELATION_ID); - pProcessFile = fopen(processFileName, "w+"); - if(pProcessFile == NULL) - { - Fail("Error in opening process File file for write for process [%d]\n", USE_PROCESS_COUNT); - } statisticsSize = sizeof(struct statistics); - - _snprintf(fileName, MAX_PATH, "%d_thread_mutex_%d_.txt", USE_PROCESS_COUNT, RELATION_ID); - pFile = fopen(fileName, "w+"); - if(pFile == NULL) - { - Fail("Error in opening file for write for process [%d]\n", USE_PROCESS_COUNT); - } - // For each thread we will log operations failed (int), passed (int), total (int) - // and number of ticks (DWORD) for the operations - resultBuffer = new ResultBuffer( THREAD_COUNT, statisticsSize); - - /* Create StartTest Event */ - StartTestsEvHandle = CreateEvent( NULL, /* lpEventAttributes*/ - TRUE, /* bManualReset */ - FALSE, /* bInitialState */ - NULL); /* name of Event */ - - if( StartTestsEvHandle == NULL ) - { - Fail("Error:%d: Unexpected failure " - "to create %s Event for process count %d\n", GetLastError(), sTmpEventName, USE_PROCESS_COUNT ); - - } - - hMutexHandle = CreateMutex( - NULL, - FALSE, /* bInitialOwner, owns initially */ - ObjName - ); - - if( (hMutexHandle == NULL)|| (GetLastError() != ERROR_ALREADY_EXISTS)) - { - Fail("Unable to create Mutex handle for process id [%d], returned error [%d], expected ERROR_ALREADY_EXISTS\n", i, GetLastError()); - } - /* We already assume that the mutex was created previously*/ - - for( i = 0; i < THREAD_COUNT; i++ ) - { - dwParam = (int) i; - //Create thread - hThread[i] = CreateThread( - NULL, /* no security attributes */ - 0, /* use default stack size */ - (LPTHREAD_START_ROUTINE)Run_Thread_mutex_shared,/* thread function */ - (LPVOID)dwParam, /* argument to thread function */ - 0, /* use default creation flags */ - &threadId[i] /* returns the thread identifier*/ - ); - - if(hThread[i] == NULL) - { - Fail("Create Thread failed for %d process, and GetLastError value is %d\n", USE_PROCESS_COUNT, GetLastError()); - } - - } - - if (!SetEvent(StartTestsEvHandle)) - { - Fail("Set Event for Start Tests failed for %d process, and GetLastError value is %d\n", USE_PROCESS_COUNT, GetLastError()); - } - - /* Test running */ - returnCode = WaitForMultipleObjects( THREAD_COUNT, hThread, TRUE, INFINITE); - - if( WAIT_OBJECT_0 != returnCode ) - { - Trace("Wait for Object(s) for %d process returned %d, and GetLastError value is %d\n", USE_PROCESS_COUNT, returnCode, GetLastError()); - testStatus = FAIL; - } - - processStats.operationTime = GetTimeDiff(dwStartTime); - - /* Write to a file*/ - if(pFile!= NULL) - { - for( i = 0; i < THREAD_COUNT; i++ ) - { - buffer = (struct statistics *)resultBuffer->getResultBuffer(i); - returnCode = fprintf(pFile, "%d,%d,%d,%d,%lu,%d\n", buffer->processId, buffer->operationsFailed, buffer->operationsPassed, buffer->operationsTotal, buffer->operationTime, buffer->relationId ); - } - } - fclose(pFile); - - fprintf(pProcessFile, "%d,%d,%d\n", USE_PROCESS_COUNT, processStats.operationTime, processStats.relationId ); - fclose(pProcessFile); - - /* Logging for the test case over, clean up the handles */ - - for( i = 0; i < THREAD_COUNT; i++ ) - { - if(!CloseHandle(hThread[i]) ) - { - Trace("Error:%d: CloseHandle failed for Process [%d] hThread[%d]\n", GetLastError(), USE_PROCESS_COUNT, i); - testStatus = FAIL; - } - } - - if(!CloseHandle(StartTestsEvHandle)) - { - Trace("Error:%d: CloseHandle failed for Process [%d] StartTestsEvHandle\n", GetLastError(), USE_PROCESS_COUNT); - testStatus = FAIL; - } - - if(!CloseHandle(hMutexHandle)) - { - Trace("Error:%d: CloseHandle failed for Process [%d] hMutexHandle\n", GetLastError(), USE_PROCESS_COUNT); - testStatus = FAIL; - } - - PAL_Terminate(); - return testStatus; -} - -void PALAPI Run_Thread_mutex_shared (LPVOID lpParam) -{ - unsigned int i = 0; - DWORD dwWaitResult; - - struct statistics stats; - DWORD dwStartTime; - - stats.relationId = RELATION_ID; - stats.processId = USE_PROCESS_COUNT; - stats.operationsFailed = 0; - stats.operationsPassed = 0; - stats.operationsTotal = 0; - stats.operationTime = 0; - - int Id=(int)lpParam; - - dwWaitResult = WaitForSingleObject( - StartTestsEvHandle, // handle to mutex - TIMEOUT); - - if(dwWaitResult != WAIT_OBJECT_0) - { - Trace("Error while waiting for StartTest Event@ thread %d\n", Id); - testStatus = FAIL; - } - - dwStartTime = (DWORD)minipal_lowres_ticks(); - - for( i = 0; i < REPEAT_COUNT; i++ ) - { - dwWaitResult = WaitForSingleObject( - hMutexHandle, // handle to mutex - TIMEOUT); - - if(dwWaitResult != WAIT_OBJECT_0) - { - stats.operationsFailed += 1; - stats.operationsTotal += 1; - testStatus = FAIL; - continue; - } - if (! ReleaseMutex(hMutexHandle)) - { - // Deal with error. - stats.operationsFailed += 1; - stats.operationsTotal += 1; - // Probably need to have while true loop to attempt to release mutex... - testStatus = FAIL; - continue; - } - - stats.operationsTotal += 1; - stats.operationsPassed += 1; - } - stats.operationTime = GetTimeDiff(dwStartTime); - if(resultBuffer->LogResult(Id, (char *)&stats)) - { - Fail("Error:%d: while writing to shared memory, Thread Id is[%d] and Process id is [%d]\n", GetLastError(), Id, USE_PROCESS_COUNT); - } -} diff --git a/src/coreclr/pal/tests/palsuite/composite/object_management/readme.txt b/src/coreclr/pal/tests/palsuite/composite/object_management/readme.txt index 6bae5f105d634e..669b61f79fe3d7 100644 --- a/src/coreclr/pal/tests/palsuite/composite/object_management/readme.txt +++ b/src/coreclr/pal/tests/palsuite/composite/object_management/readme.txt @@ -2,8 +2,6 @@ To compile: 1) create a dat file (say object_management.dat) with contents: -PAL,Composite,palsuite\composite\object_management\mutex\nonshared,mutex=main.c mutex.c,,, -PAL,Composite,palsuite\composite\object_management\mutex\shared,mutex=main.c mutex.c,,, PAL,Composite,palsuite\composite\object_management\semaphore\nonshared,semaphore=main.c semaphore.c,,, PAL,Composite,palsuite\composite\object_management\semaphore\shared,semaphore=main.c semaphore.c,,, PAL,Composite,palsuite\composite\object_management\event\nonshared,event=main.c event.c,,, @@ -19,10 +17,10 @@ main [PROCESS_COUNT] [THREAD_COUNT] [REPEAT_COUNT] Output: -The performance numbers will be in _[event|semaphore|mutex].txt -(will be at palsuite\composite\object_management\[mutex|event|semaphore]\[shared|nonshared]\obj[r|c|d] directory if u use rrunmod.pl) +The performance numbers will be in _[event|semaphore].txt +(will be at palsuite\composite\object_management\[event|semaphore]\[shared|nonshared]\obj[r|c|d] directory if u use rrunmod.pl) -So if process_count is 3, you will have files 0_mutex.txt, 1_mutex.txt and so on… +So if process_count is 3, you will have files 0_event.txt, 1_event.txt and so on� For each process txt file created, each row represents a thread data (process id, number of failures, number of pass, total number of repeated operations and an integer that will be used to identify a run diff --git a/src/coreclr/pal/tests/palsuite/composite/wfmo/main.cpp b/src/coreclr/pal/tests/palsuite/composite/wfmo/main.cpp deleted file mode 100644 index e12cb5d596b88e..00000000000000 --- a/src/coreclr/pal/tests/palsuite/composite/wfmo/main.cpp +++ /dev/null @@ -1,238 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** Source Code: main.c and mutex.c -** main.c creates process and waits for all processes to get over -** mutex.c creates a mutex and then calls threads which will contend for the mutex -** -** This test is for WFMO Test case for Mutex -** Algorithm -** o Create PROCESS_COUNT processes. -** o Main Thread of each process creates OBJECT_TYPE Object -** -** Author: ShamitP -** -** -**============================================================ -*/ - -#include -#include "resulttime.h" - -/* Test Input Variables */ -unsigned int PROCESS_COUNT = 3; -unsigned int THREAD_COUNT = 30; -unsigned int REPEAT_COUNT = 40; -unsigned int SLEEP_LENGTH = 4; -unsigned int RELATION_ID = 1001; - - - -struct TestStats{ - DWORD operationTime; - unsigned int relationId; - unsigned int processCount; - unsigned int threadCount; - unsigned int repeatCount; - char* buildNumber; - -}; - -int GetParameters( int argc, char **argv) -{ - if( (argc != 6) || ((argc == 1) && !strcmp(argv[1],"/?")) - || !strcmp(argv[1],"/h") || !strcmp(argv[1],"/H")) - { - printf("PAL -Composite WFMO Test\n"); - printf("Usage:\n"); - printf("main\n\t[PROCESS_COUNT [greater than 0] \n"); - printf("\t[THREAD_COUNT [greater than 0] \n"); - printf("\t[REPEAT_COUNT [greater than 0]\n"); - printf("\t[SLEEP_LENGTH [greater than 0]\n"); - printf("\t[RELATION_ID [greater than 0]\n"); - - - - return -1; - } - - PROCESS_COUNT = atoi(argv[1]); - if( (PROCESS_COUNT < 1) || (PROCESS_COUNT > MAXIMUM_WAIT_OBJECTS) ) - { - printf("\nMain Process:Invalid PROCESS_COUNT number, Pass greater than 1 and less than PROCESS_COUNT %d\n", MAXIMUM_WAIT_OBJECTS); - return -1; - } - - THREAD_COUNT = atoi(argv[2]); - if( (THREAD_COUNT < 1) || (THREAD_COUNT > MAXIMUM_WAIT_OBJECTS) ) - { - printf("\nInvalid THREAD_COUNT number, Pass greater than 1 and less than %d\n", MAXIMUM_WAIT_OBJECTS); - return -1; - } - - REPEAT_COUNT = atoi(argv[3]); - if( REPEAT_COUNT < 1) - { - printf("\nMain Process:Invalid REPEAT_COUNT number, Pass greater than 1\n"); - return -1; - } - - SLEEP_LENGTH = atoi(argv[4]); - if( SLEEP_LENGTH < 1) - { - printf("\nMain Process:Invalid SLEEP_LENGTH number, Pass greater than 1\n"); - return -1; - } - - RELATION_ID = atoi(argv[5]); - if( RELATION_ID < 1) - { - printf("\nMain Process:Invalid RELATION_ID number, Pass greater than 1\n"); - return -1; - } - - - - return 0; -} - -PALTEST(composite_wfmo_paltest_composite_wfmo, "composite/wfmo/paltest_composite_wfmo") -{ - unsigned int i = 0; - HANDLE hProcess[MAXIMUM_WAIT_OBJECTS]; - - STARTUPINFO si[MAXIMUM_WAIT_OBJECTS]; - PROCESS_INFORMATION pi[MAXIMUM_WAIT_OBJECTS]; - - char lpCommandLine[MAX_PATH] = ""; - - int returnCode = 0; - DWORD processReturnCode = 0; - int testReturnCode = PASS; - - char fileName[MAX_PATH]; - FILE *pFile = NULL; - DWORD dwStartTime; - struct TestStats testStats; - - if(0 != (PAL_Initialize(argc, argv))) - { - return ( FAIL ); - } - - if(GetParameters(argc, argv)) - { - Fail("Error in obtaining the parameters\n"); - } - - /* Register the start time */ - dwStartTime = (DWORD)minipal_lowres_ticks(); - testStats.relationId = 0; - testStats.relationId = RELATION_ID; - testStats.processCount = PROCESS_COUNT; - testStats.threadCount = THREAD_COUNT; - testStats.repeatCount = REPEAT_COUNT; - testStats.buildNumber = getBuildNumber(); - - - - _snprintf(fileName, MAX_PATH, "main_wfmo_%d_.txt",testStats.relationId); - pFile = fopen(fileName, "w+"); - if(pFile == NULL) - { - Fail("Error in opening main file for write\n"); - } - - for( i = 0; i < PROCESS_COUNT; i++ ) - { - - ZeroMemory( lpCommandLine, MAX_PATH ); - if ( _snprintf( lpCommandLine, MAX_PATH-1, "mutex %d %d %d %d %d", i, THREAD_COUNT, REPEAT_COUNT, SLEEP_LENGTH, RELATION_ID) < 0 ) - { - Trace ("Error: Insufficient commandline string length for iteration [%d]\n", i); - } - - /* Zero the data structure space */ - ZeroMemory ( &pi[i], sizeof(pi[i]) ); - ZeroMemory ( &si[i], sizeof(si[i]) ); - - /* Set the process flags and standard io handles */ - si[i].cb = sizeof(si[i]); - - //Create Process - if(!CreateProcess( NULL, /* lpApplicationName*/ - lpCommandLine, /* lpCommandLine */ - NULL, /* lpProcessAttributes */ - NULL, /* lpThreadAttributes */ - TRUE, /* bInheritHandles */ - 0, /* dwCreationFlags, */ - NULL, /* lpEnvironment */ - NULL, /* pCurrentDirectory */ - &si[i], /* lpStartupInfo */ - &pi[i] /* lpProcessInformation */ - )) - { - Fail("Process Not created for [%d], the error code is [%d]\n", i, GetLastError()); - } - else - { - hProcess[i] = pi[i].hProcess; - // Trace("Process created for [%d]\n", i); - } - - } - - returnCode = WaitForMultipleObjects( PROCESS_COUNT, hProcess, TRUE, INFINITE); - if( WAIT_OBJECT_0 != returnCode ) - { - Trace("Wait for Object(s) @ Main thread for %d processes returned %d, and GetLastError value is %d\n", PROCESS_COUNT, returnCode, GetLastError()); - } - - for( i = 0; i < PROCESS_COUNT; i++ ) - { - /* check the exit code from the process */ - if( ! GetExitCodeProcess( pi[i].hProcess, &processReturnCode ) ) - { - Trace( "GetExitCodeProcess call failed for iteration %d with error code %u\n", - i, GetLastError() ); - - testReturnCode = FAIL; - } - - if(processReturnCode == FAIL) - { - Trace( "Process [%d] failed and returned FAIL\n", i); - testReturnCode = FAIL; - } - - if(!CloseHandle(pi[i].hThread)) - { - Trace("Error:%d: CloseHandle failed for Process [%d] hThread\n", GetLastError(), i); - } - - if(!CloseHandle(pi[i].hProcess) ) - { - Trace("Error:%d: CloseHandle failed for Process [%d] hProcess\n", GetLastError(), i); - } - } - - testStats.operationTime = GetTimeDiff(dwStartTime); - fprintf(pFile, "%d,%d,%d,%d,%d,%s\n", testStats.operationTime, testStats.relationId, testStats.processCount, testStats.threadCount, testStats.repeatCount, testStats.buildNumber); - if(fclose(pFile)) - { - Trace("Error: fclose failed for pFile\n"); - testReturnCode = FAIL; - } - - if( testReturnCode == PASS) - { - Trace("Test Passed\n"); - } - else - { - Trace("Test Failed\n"); - } - PAL_Terminate(); - return testReturnCode; -} diff --git a/src/coreclr/pal/tests/palsuite/composite/wfmo/mutex.cpp b/src/coreclr/pal/tests/palsuite/composite/wfmo/mutex.cpp deleted file mode 100644 index ed5de0fafd16ea..00000000000000 --- a/src/coreclr/pal/tests/palsuite/composite/wfmo/mutex.cpp +++ /dev/null @@ -1,350 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -**Source Code: main.c and mutex.c -** main.c creates process and waits for all processes to get over -** mutex.c creates a mutex and then calls threads which will -** contend for the mutex -** -** This test is for WFMO Test case for Mutex -** Algorithm -** o Create PROCESS_COUNT processes. -** o Main Thread of each process creates OBJECT_TYPE Object -** -** Author: ShamitP -** -** -**============================================================ -*/ - -#include -#include "resultbuffer.h" -#include "resulttime.h" - -/* Test Input Variables */ -unsigned int USE_PROCESS_COUNT = 0; -unsigned int THREAD_COUNT = 0; -unsigned int REPEAT_COUNT = 0; -unsigned int SLEEP_LENGTH = 0; -unsigned int RELATION_ID = 1001; - - -/* Capture statistics at per thread basis */ -struct statistics{ - unsigned int processId; - unsigned int operationsFailed; - unsigned int operationsPassed; - unsigned int operationsTotal; - DWORD operationTime; - unsigned int relationId; - -}; - -struct ProcessStats{ - unsigned int processId; - DWORD operationTime; - unsigned int relationId; -}; - -/* Handle to signal threads to start the tests */ -HANDLE StartTestsEvHandle = NULL; -/* Handle to mutex which will be contended by threads */ -HANDLE hMutexHandle = NULL; -/* Results Buffer */ -ResultBuffer *resultBuffer = NULL; - -int testStatus; - -void PALAPI Run_Thread_composite_wfmo(LPVOID lpParam); - -int GetParameters( int argc, char **argv) -{ - if( (argc != 6) || ((argc == 1) && !strcmp(argv[1],"/?")) - || !strcmp(argv[1],"/h") || !strcmp(argv[1],"/H")) - { - printf("PAL -Composite WFMO Test\n"); - printf("Usage:\n"); - printf("mutex\n\t[USE_PROCESS_COUNT [greater than 0] \n"); - printf("\t[THREAD_COUNT [greater than 0] \n"); - printf("\t[REPEAT_COUNT [greater than 0]\n"); - printf("\t[SLEEP_LENGTH [greater than 0]\n"); - printf("\t[RELATION_ID [greater than 0]\n"); - - return -1; - } - - USE_PROCESS_COUNT = atoi(argv[1]); - if( USE_PROCESS_COUNT < 0) - { - printf("\nInvalid USE_PROCESS_COUNT number, Pass greater than 1\n"); - return -1; - } - - THREAD_COUNT = atoi(argv[2]); - if( (THREAD_COUNT < 1) || (THREAD_COUNT > MAXIMUM_WAIT_OBJECTS) ) - { - printf("\nInvalid THREAD_COUNT number, Pass greater than 1 and less than %d\n", MAXIMUM_WAIT_OBJECTS); - return -1; - } - - REPEAT_COUNT = atoi(argv[3]); - if( REPEAT_COUNT < 1) - { - printf("\nInvalid REPEAT_COUNT number, Pass greater than 1\n"); - return -1; - } - - SLEEP_LENGTH = atoi(argv[4]); - if( SLEEP_LENGTH < 1) - { - printf("\nMain Process:Invalid SLEEP_LENGTH number, Pass greater than 1\n"); - return -1; - } - - RELATION_ID = atoi(argv[5]); - if( RELATION_ID < 1) - { - printf("\nMain Process:Invalid RELATION_ID number, Pass greater than 1\n"); - return -1; - } - - return 0; -} - -PALTEST(composite_wfmo_paltest_composite_wfmo, "composite/wfmo/paltest_composite_wfmo") -{ - unsigned int i = 0; - HANDLE hThread[MAXIMUM_WAIT_OBJECTS]; - DWORD threadId[MAXIMUM_WAIT_OBJECTS]; - int returnCode = 0; - - DWORD dwParam = 0; - - /* Variables to capture the file name and the file pointer at thread level*/ - char fileName[MAX_PATH]; - FILE *pFile = NULL; - struct statistics* buffer = NULL; - int statisticsSize = 0; - - /* Variables to capture the file name and the file pointer at process level*/ - char processFileName[MAX_PATH]; - FILE *pProcessFile = NULL; - struct ProcessStats processStats; - DWORD dwStartTime; - - testStatus = PASS; - - if(0 != (PAL_Initialize(argc, argv))) - { - return ( FAIL ); - } - - if(GetParameters(argc, argv)) - { - Fail("Error in obtaining the parameters\n"); - } - - /* Register the start time */ - dwStartTime = (DWORD)minipal_lowres_ticks(); - processStats.relationId = RELATION_ID; - processStats.processId = USE_PROCESS_COUNT; - - _snprintf(processFileName, MAX_PATH, "%d_process_wfmo_%d_.txt", USE_PROCESS_COUNT, RELATION_ID); - pProcessFile = fopen(processFileName, "w+"); - if(pProcessFile == NULL) - { - Fail("Error:%d: in opening Process File for write for process [%d]\n", GetLastError(), USE_PROCESS_COUNT); - } - - statisticsSize = sizeof(struct statistics); - - _snprintf(fileName, MAX_PATH, "%d_thread_wfmo_%d_.txt", USE_PROCESS_COUNT, RELATION_ID); - pFile = fopen(fileName, "w+"); - if(pFile == NULL) - { - Fail("Error in opening file for write for process [%d], error [%d]\n", USE_PROCESS_COUNT, GetLastError()); - } - // For each thread we will log operations failed (int), passed (int), total (int) - // and number of ticks (DWORD) for the operations - resultBuffer = new ResultBuffer( THREAD_COUNT, statisticsSize); - - StartTestsEvHandle = CreateEvent( NULL, /* lpEventAttributes*/ - TRUE, /* bManualReset */ - FALSE, /* bInitialState */ - NULL); /* name of Event */ - - if( StartTestsEvHandle == NULL ) - { - Fail("Error:%d: Unexpected failure " - "to create start tests Event for process count %d\n", GetLastError(), USE_PROCESS_COUNT ); - - } - - /* Create StartTest Event */ - hMutexHandle = CreateMutex( - NULL, - FALSE, /* bInitialOwner, owns initially */ - NULL - ); - - if( hMutexHandle == NULL) - { - Fail("Unable to create Mutex handle for process id [%d], returned error [%d]\n", i, GetLastError()); - } - - /* We already assume that the mutex was created previously*/ - for( i = 0; i < THREAD_COUNT; i++ ) - { - dwParam = (int) i; - //Create thread - hThread[i] = CreateThread( - NULL, /* no security attributes */ - 0, /* use default stack size */ - (LPTHREAD_START_ROUTINE)Run_Thread_composite_wfmo,/* thread function */ - (LPVOID)dwParam, /* argument to thread function */ - 0, /* use default creation flags */ - &threadId[i] /* returns the thread identifier*/ - ); - - if(hThread[i] == NULL) - { - Fail("Create Thread failed for %d process, and GetLastError value is %d\n", USE_PROCESS_COUNT, GetLastError()); - } - } - - if (!SetEvent(StartTestsEvHandle)) - { - Fail("Set Event for Start Tests failed for %d process, and GetLastError value is %d\n", USE_PROCESS_COUNT, GetLastError()); - } - /* Test running */ - - if( THREAD_COUNT != 1 ) - { - returnCode = WaitForMultipleObjects(THREAD_COUNT, hThread, TRUE, INFINITE); - } - else - { - returnCode = WaitForSingleObject(hThread[0], INFINITE); - } - - if( WAIT_OBJECT_0 != returnCode ) - { - Trace("Wait for Object(s) for %d process returned %d, and GetLastError value is %d\n", USE_PROCESS_COUNT, returnCode, GetLastError()); - testStatus = FAIL; - } - - processStats.operationTime = GetTimeDiff(dwStartTime); - - /* Write to a file*/ - if(pFile!= NULL) - { - for( i = 0; i < THREAD_COUNT; i++ ) - { - buffer = (struct statistics *)resultBuffer->getResultBuffer(i); - returnCode = fprintf(pFile, "%d,%d,%d,%d,%lu,%d\n", buffer->processId, buffer->operationsFailed, buffer->operationsPassed, buffer->operationsTotal, buffer->operationTime, buffer->relationId ); - } - } - if(fclose(pFile)) - { - Trace("Error: fclose failed for pFile at Process %d\n", USE_PROCESS_COUNT); - testStatus = FAIL; - } - - fprintf(pProcessFile, "%d,%d,%d\n", USE_PROCESS_COUNT, processStats.operationTime, processStats.relationId ); - if(fclose(pProcessFile)) - { - Trace("Error: fclose failed for pProcessFile at Process %d\n", USE_PROCESS_COUNT); - testStatus = FAIL; - } - - /* Logging for the test case over, clean up the handles */ - for( i = 0; i < THREAD_COUNT; i++ ) - { - if(!CloseHandle(hThread[i]) ) - { - Trace("Error:%d: CloseHandle failed for Process [%d] hThread[%d]\n", GetLastError(), USE_PROCESS_COUNT, i); - testStatus = FAIL; - } - } - - if(!CloseHandle(StartTestsEvHandle)) - { - Trace("Error:%d: CloseHandle failed for Process [%d] StartTestsEvHandle\n", GetLastError(), USE_PROCESS_COUNT); - testStatus = FAIL; - } - - PAL_Terminate(); - return testStatus; -} - -void PALAPI Run_Thread_composite_wfmo (LPVOID lpParam) -{ - unsigned int i = 0; - struct statistics stats; - - DWORD dwWaitResult; - DWORD dwStartTime; - - stats.relationId = RELATION_ID; - stats.processId = USE_PROCESS_COUNT; - stats.operationsFailed = 0; - stats.operationsPassed = 0; - stats.operationsTotal = 0; - stats.operationTime = 0; - - int Id=(int)lpParam; - - dwWaitResult = WaitForSingleObject( - StartTestsEvHandle, // handle to mutex - INFINITE); - - if(dwWaitResult != WAIT_OBJECT_0) - { - Trace("Error:%d: while waiting for StartTest Event@ thread %d\n", GetLastError(), Id); - testStatus = FAIL; - } - - /* Register the start time */ - dwStartTime = (DWORD)minipal_lowres_ticks(); - - /* Run the tests repeat count times */ - for( i = 0; i < REPEAT_COUNT; i++ ) - { - dwWaitResult = WaitForSingleObject( - hMutexHandle, // handle to mutex - INFINITE); - - if(dwWaitResult != WAIT_OBJECT_0) - { - Trace("Error:%d: while waiting for onject @ thread %d, # iter %d\n", GetLastError(), Id, i); - stats.operationsFailed += 1; - stats.operationsTotal += 1; - testStatus = FAIL; - continue; - } - - Sleep(SLEEP_LENGTH); - - if (!ReleaseMutex(hMutexHandle)) - { - // Deal with error. - Trace("Error:%d: while releasing mutex @ thread %d # iter %d\n", GetLastError(), Id, i); - stats.operationsFailed += 1; - stats.operationsTotal += 1; - // do we need to have while true loop to attempt to release mutex...? - testStatus = FAIL; - continue; - } - - stats.operationsTotal += 1; - stats.operationsPassed += 1; - } - - stats.operationTime = GetTimeDiff(dwStartTime); - - if(resultBuffer->LogResult(Id, (char *)&stats)) - { - Fail("Error:%d: while writing to shared memory, Thread Id is[%d] and Process id is [%d]\n", GetLastError(), Id, USE_PROCESS_COUNT); - } -} diff --git a/src/coreclr/pal/tests/palsuite/composite/wfmo/readme.txt b/src/coreclr/pal/tests/palsuite/composite/wfmo/readme.txt deleted file mode 100644 index 6be55d8ff42c08..00000000000000 --- a/src/coreclr/pal/tests/palsuite/composite/wfmo/readme.txt +++ /dev/null @@ -1,22 +0,0 @@ -To compile: - -1) create a dat file (say wfmo.dat) with contents: -PAL,Composite,palsuite\composite\wfmo,wfmo=main.c mutex.c,,, - -2) perl rrunmod.pl -r wfmo.dat - - -To execute: -main [PROCESS_COUNT] [THREAD_COUNT] [REPEAT_COUNT] [SLEEP_LENGTH] - -Output: -The performance numbers will be in _wfmo.txt -(will be at palsuite\composite\wfmo\obj[r|c|d] directory if u use rrunmod.pl) - -So if process_count is 3, you will have files 0_wfmo.txt, 1_wfmo.txt and so on… - -For each process txt file created, -each row represents a thread data (process id, number of failures, number of pass, total number of repeated operations and an integer that will be used to identify a run -(currently zero)). - - diff --git a/src/coreclr/pal/tests/palsuite/paltestlist.txt b/src/coreclr/pal/tests/palsuite/paltestlist.txt index 62a943e4726b7c..50a4d063420d8b 100644 --- a/src/coreclr/pal/tests/palsuite/paltestlist.txt +++ b/src/coreclr/pal/tests/palsuite/paltestlist.txt @@ -248,9 +248,6 @@ miscellaneous/SetLastError/test1/paltest_setlasterror_test1 pal_specific/PAL_Initialize_Terminate/test1/paltest_pal_initialize_terminate_test1 pal_specific/PAL_Initialize_Terminate/test2/paltest_pal_initialize_terminate_test2 samples/test1/paltest_samples_test1 -threading/CreateEventW/test1/paltest_createeventw_test1 -threading/CreateEventW/test2/paltest_createeventw_test2 -threading/CreateMutexW_ReleaseMutex/test1/paltest_createmutexw_releasemutex_test1 threading/CreateProcessW/test1/paltest_createprocessw_test1 threading/CreateProcessW/test2/paltest_createprocessw_test2 threading/CreateSemaphoreW_ReleaseSemaphore/test1/paltest_createsemaphorew_releasesemaphore_test1 @@ -259,7 +256,6 @@ threading/CreateThread/test1/paltest_createthread_test1 threading/CreateThread/test3/paltest_createthread_test3 threading/DuplicateHandle/test10/paltest_duplicatehandle_test10 threading/DuplicateHandle/test2/paltest_duplicatehandle_test2 -threading/DuplicateHandle/test4/paltest_duplicatehandle_test4 threading/DuplicateHandle/test7/paltest_duplicatehandle_test7 threading/DuplicateHandle/test8/paltest_duplicatehandle_test8 threading/ExitProcess/test1/paltest_exitprocess_test1 @@ -276,7 +272,6 @@ threading/QueueUserAPC/test4/paltest_queueuserapc_test4 threading/QueueUserAPC/test5/paltest_queueuserapc_test5 threading/QueueUserAPC/test6/paltest_queueuserapc_test6 threading/QueueUserAPC/test7/paltest_queueuserapc_test7 -threading/ReleaseMutex/test3/paltest_releasemutex_test3 threading/releasesemaphore/test1/paltest_releasesemaphore_test1 threading/ResetEvent/test1/paltest_resetevent_test1 threading/ResetEvent/test2/paltest_resetevent_test2 @@ -293,9 +288,7 @@ threading/WaitForMultipleObjects/test1/paltest_waitformultipleobjects_test1 threading/WaitForMultipleObjectsEx/test1/paltest_waitformultipleobjectsex_test1 threading/WaitForMultipleObjectsEx/test2/paltest_waitformultipleobjectsex_test2 threading/WaitForMultipleObjectsEx/test3/paltest_waitformultipleobjectsex_test3 -threading/WaitForMultipleObjectsEx/test4/paltest_waitformultipleobjectsex_test4 threading/WaitForSingleObject/test1/paltest_waitforsingleobject_test1 -threading/WaitForSingleObject/WFSOExMutexTest/paltest_waitforsingleobject_wfsoexmutextest threading/WaitForSingleObject/WFSOExSemaphoreTest/paltest_waitforsingleobject_wfsoexsemaphoretest threading/WaitForSingleObject/WFSOExThreadTest/paltest_waitforsingleobject_wfsoexthreadtest threading/WaitForSingleObject/WFSOMutexTest/paltest_waitforsingleobject_wfsomutextest diff --git a/src/coreclr/pal/tests/palsuite/paltestlist_to_be_reviewed.txt b/src/coreclr/pal/tests/palsuite/paltestlist_to_be_reviewed.txt index d861a469b37070..0c512626040870 100644 --- a/src/coreclr/pal/tests/palsuite/paltestlist_to_be_reviewed.txt +++ b/src/coreclr/pal/tests/palsuite/paltestlist_to_be_reviewed.txt @@ -69,12 +69,9 @@ miscellaneous/IsBadWritePtr/test3/paltest_isbadwriteptr_test3 pal_specific/PAL_get_stdout/test1/paltest_pal_get_stdout_test1 pal_specific/PAL_RegisterLibraryW_UnregisterLibraryW/test1/paltest_pal_registerlibraryw_unregisterlibraryw_test1 samples/test2/paltest_samples_test2 -threading/CreateEventW/test3/paltest_createeventw_test3 -threading/CreateMutexW_ReleaseMutex/test2/paltest_createmutexw_releasemutex_test2 threading/CreateSemaphoreW_ReleaseSemaphore/test3/paltest_createsemaphorew_releasesemaphore_test3 threading/CreateThread/test2/paltest_createthread_test2 threading/DuplicateHandle/test1/paltest_duplicatehandle_test1 -threading/DuplicateHandle/test11/paltest_duplicatehandle_test11 threading/DuplicateHandle/test12/paltest_duplicatehandle_test12 threading/DuplicateHandle/test3/paltest_duplicatehandle_test3 threading/DuplicateHandle/test9/paltest_duplicatehandle_test9 @@ -85,7 +82,6 @@ threading/GetExitCodeProcess/test1/paltest_getexitcodeprocess_test1 threading/OpenEventW/test1/paltest_openeventw_test1 threading/OpenEventW/test2/paltest_openeventw_test2 threading/OpenEventW/test3/paltest_openeventw_test3 -threading/OpenEventW/test4/paltest_openeventw_test4 threading/OpenEventW/test5/paltest_openeventw_test5 threading/OpenProcess/test1/paltest_openprocess_test1 threading/QueueUserAPC/test1/paltest_queueuserapc_test1 diff --git a/src/coreclr/pal/tests/palsuite/threading/CreateEventW/test1/test1.cpp b/src/coreclr/pal/tests/palsuite/threading/CreateEventW/test1/test1.cpp deleted file mode 100644 index 361c58d1ad1db1..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/CreateEventW/test1/test1.cpp +++ /dev/null @@ -1,92 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** Source: test1.c -** -** Purpose: Test for CreateEventW -** -** -**=========================================================*/ - -/* - * Note: From the rotor_pal documentation: lpEventAttributes will - * always be NULL, bManualReset can be either TRUE or FALSE, - * bInitialState can be either TRUE or FALSE, the lpName may be - * non-NULL. -*/ -#define UNICODE -#include - -BOOL CreateEventTest_CreateEvent_test1() -{ - BOOL bRet = FALSE; - DWORD dwRet = 0; - - LPSECURITY_ATTRIBUTES lpEventAttributes = NULL; - BOOL bManualReset = TRUE; - BOOL bInitialState = TRUE; - - /* - * Call CreateEvent, and check to ensure the returned HANDLE is a - * valid event HANDLE - */ - - HANDLE hEvent = CreateEventW(lpEventAttributes, - bManualReset, - bInitialState, - NULL); - - if (hEvent != NULL) - { - /* - * Wait for the Object (for 0 time) and ensure that it returns - * the value indicating that the event is signaled. - */ - dwRet = WaitForSingleObject(hEvent,0); - - if (dwRet != WAIT_OBJECT_0) - { - Trace("CreateEventTest:WaitForSingleObject failed (%x)\n", GetLastError()); - } - else - { - /* - * If we make it here, and CloseHandle succeeds, then the - * entire test has passed. Otherwise bRet will still show - * failure - */ - bRet = CloseHandle(hEvent); - - if (!bRet) - { - Trace("CreateEventTest:CloseHandle failed (%x)\n", GetLastError()); - } - } - } - else - { - Trace("CreateEventTest:CreateEvent failed (%x)\n", GetLastError()); - } - - return bRet; -} - -PALTEST(threading_CreateEventW_test1_paltest_createeventw_test1, "threading/CreateEventW/test1/paltest_createeventw_test1") -{ - - if(0 != (PAL_Initialize(argc, argv))) - { - return ( FAIL ); - } - - if(!CreateEventTest_CreateEvent_test1()) - { - Fail ("Test failed\n"); - } - - PAL_Terminate(); - return ( PASS ); - -} diff --git a/src/coreclr/pal/tests/palsuite/threading/CreateEventW/test2/test2.cpp b/src/coreclr/pal/tests/palsuite/threading/CreateEventW/test2/test2.cpp deleted file mode 100644 index c40581718fd569..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/CreateEventW/test2/test2.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** Source: test2.c -** -** Purpose: Test for CreateEventW. Create the event with the -** initial state being not signaled. Check to ensure that it -** times out when the event is triggered. -** -** -**=========================================================*/ -#define UNICODE -#include - -BOOL CreateEventTest_CreateEvent_test2() -{ - BOOL bRet = FALSE; - DWORD dwRet = 0; - - LPSECURITY_ATTRIBUTES lpEventAttributes = 0; - BOOL bManualReset = TRUE; - BOOL bInitialState = FALSE; - - - /* Create an event with the Initial State set to FALSE */ - - HANDLE hEvent = CreateEventW(lpEventAttributes, - bManualReset, - bInitialState, - NULL); - - if (hEvent != NULL) - { - /* This should ensure that the object is reset, or - non-signaled. - */ - - dwRet = WaitForSingleObject(hEvent,0); - - if (dwRet != WAIT_TIMEOUT) - { - Trace("CloseEventTest:WaitForSingleObject failed (%x)\n", GetLastError()); - } - else - { - /* At this point, we've tested the function with success. - So long as the HANDLE can be closed, this test should - pass. - */ - - bRet = CloseHandle(hEvent); - - if (!bRet) - { - Trace("CloseEventTest:CloseHandle failed (%x)\n", GetLastError()); - } - } - } - else - { - Trace("CloseEventTest:CreateEvent failed (%x)\n", GetLastError()); - } - - return bRet; -} - -PALTEST(threading_CreateEventW_test2_paltest_createeventw_test2, "threading/CreateEventW/test2/paltest_createeventw_test2") -{ - if(0 != (PAL_Initialize(argc, argv))) - { - return ( FAIL ); - } - - if(!CreateEventTest_CreateEvent_test2()) - { - Fail ("Test failed\n"); - } - - PAL_Terminate(); - return ( PASS ); - -} diff --git a/src/coreclr/pal/tests/palsuite/threading/CreateEventW/test3/test3.cpp b/src/coreclr/pal/tests/palsuite/threading/CreateEventW/test3/test3.cpp deleted file mode 100644 index 559348727b723e..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/CreateEventW/test3/test3.cpp +++ /dev/null @@ -1,231 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** Source: test3.c -** -** Purpose: Tests for CreateEvent. Create an unnamed event, create -** an event with an empty name, create an event with a name longer than -** MAX_PATH, MAX_PATH + 1, create an event with a name already taken -** by a non-event object, create an event with a name already taken -** by an event object. -** -** -**=========================================================*/ -#include - -#define SWAPPTR ((VOID *) (-1)) - -struct testCase -{ - LPSECURITY_ATTRIBUTES lpEventAttributes; - BOOL bManualReset; - BOOL bInitialState; - WCHAR lpName[MAX_PATH + 2]; - DWORD dwNameLen; - DWORD lastError; - BOOL bResult; -}; - -PALTEST(threading_CreateEventW_test3_paltest_createeventw_test3, "threading/CreateEventW/test3/paltest_createeventw_test3") -{ - struct testCase testCases[]= - { - {0, TRUE, FALSE, {'\0'}, 0, ERROR_SUCCESS, PASS}, - {0, TRUE, FALSE, {'\0'}, 5, ERROR_SUCCESS, PASS}, - {0, TRUE, FALSE, {'\0'}, 5, ERROR_ALREADY_EXISTS, PASS}, - {0, TRUE, FALSE, {'\0'}, 6, ERROR_INVALID_HANDLE, PASS}, - {0, TRUE, FALSE, {'\0'}, MAX_PATH - 1 - 60, ERROR_SUCCESS, PASS}, - {0, TRUE, FALSE, {'\0'}, MAX_PATH - 60, ERROR_SUCCESS, PASS}, - }; - - HANDLE hEvent[sizeof(testCases)/sizeof(struct testCase)]; - - DWORD result[sizeof(testCases)/sizeof(struct testCase)]; - - BOOL bRet = TRUE; - WCHAR nonEventName[] = {'a','a','a','a','a','a','\0'}; - char name[MAX_PATH + 2]; - WCHAR *wName; - HANDLE hFMap = NULL; - HANDLE hUnnamedEvent; - DWORD dwRet; - int i; - - if(0 != (PAL_Initialize(argc, argv))) - { - return ( FAIL ); - } - - hUnnamedEvent = CreateEventW(0, TRUE, FALSE, NULL); - - if ( NULL == hUnnamedEvent ) - { - bRet = FALSE; - Trace ( "PALSUITE ERROR: CreateEventW (%d, %d, %d, NULL) call " - "returned NULL.\nGetLastError returned %u.\n", 0, TRUE, FALSE, - GetLastError()); - goto done; - } - - if (!CloseHandle(hUnnamedEvent)) - { - bRet = FALSE; - Trace("PALSUITE ERROR: CreateEventW: CloseHandle(%lp); call " - "failed\nGetLastError returned '%u'.\n", hUnnamedEvent, - GetLastError()); - } - - /* Create non-event with the same name as one of the testCases */ - hFMap = CreateFileMappingW( SWAPPTR, NULL, PAGE_READONLY, 0, 1, - nonEventName ); - - if ( NULL == hFMap ) - { - bRet = FALSE; - Trace ( "PALSUITE ERROR: CreateFileMapping (%p, %p, %d, %d, %d, %S)" - " call returned NULL.\nGetLastError returned %u\n", - SWAPPTR, NULL, PAGE_READONLY, 0, 0, nonEventName, - GetLastError()); - } - - /* Create Events */ - for (i = 0; i < sizeof(testCases)/sizeof(struct testCase); i++) - { - /* create name */ - memset (name, '\0', MAX_PATH + 2); - memset (name, 'a', testCases[i].dwNameLen ); - - wName = convert(name); - - wcsncpy(testCases[i].lpName, wName, - testCases[i].dwNameLen); - - free(wName); - - SetLastError(ERROR_SUCCESS); - - hEvent[i] = CreateEventW( testCases[i].lpEventAttributes, - testCases[i].bManualReset, - testCases[i].bInitialState, - testCases[i].lpName); - - if (hEvent[i] != INVALID_HANDLE_VALUE) - { - DWORD dwError = GetLastError(); - - if (dwError != testCases[i].lastError) - { - bRet = FALSE; - Trace ("PALSUITE ERROR:\nCreateEvent(%lp, %d, %d, %S)" - "\nGetLastError returned '%u', it should have returned" - "'%d' at index '%d'.\n", testCases[i].lpEventAttributes, - testCases[i].bManualReset, testCases[i].bInitialState, - testCases[i].lpName, dwError, - testCases[i].lastError, i); - } - if ( ERROR_FILENAME_EXCED_RANGE == testCases[i].lastError ) - { - result [i] = 1; - } - if ( ERROR_INVALID_HANDLE == testCases[i].lastError ) - { - result [i] = 1; - } - /* - * If we expected the testcase to FAIL and it passed, - * report an error. - */ - if (testCases[i].bResult == FAIL) - { - bRet = FALSE; - Trace ("PALSUITE ERROR:\nCreateEvent(%lp, %d, %d, %S)" - "\nShould have returned INVALID_HANDLE_VALUE but " - "didn't at index '%d'.\n", - testCases[i].lpEventAttributes, - testCases[i].bManualReset, - testCases[i].bInitialState, - testCases[i].lpName, i); - } - /* - * If result hasn't been set already set it to 0 so all the - * resources will be freed. - */ - if (!result[i]) - { - result[i] = 0; - } - } - else - { - /* - * If we get an INVALID_HANDLE_VALUE and we expected the - * test case to pass, report an error. - */ - result[i] = 1; - - if (testCases[i].bResult == PASS) - { - bRet = FALSE; - Trace ("PALSUITE ERROR:\nCreateEvent(%lp, %d, %d, %S);" - "\nReturned INVALID_HANDLE_VALUE at index '%d'.\n", - testCases[i].lpEventAttributes, - testCases[i].bManualReset, testCases[i].bInitialState, - testCases[i].lpName, i); - } - } - } - - /* cleanup */ - for (i = 0; i < sizeof(testCases)/sizeof(struct testCase); i++) - { - if (result[i]) - { - continue; - } - dwRet = WaitForSingleObject ( hEvent[i], 0 ); - - if (dwRet != WAIT_TIMEOUT) - { - bRet = FALSE; - Trace("PALSUITE ERROR: CreateEventW:\nWaitForSingleObject (%lp, " - "%d) call failed at index %d .\nGetLastError returned " - "'%u'.\n", hEvent[i], 0, i, GetLastError()); - } - - if (!CloseHandle(hEvent[i])) - { - bRet = FALSE; - Trace("PALSUITE ERROR: CreateEventW: CloseHandle(%lp) call " - "failed at index %d\nGetLastError returned '%u'.\n", - hEvent[i], i, GetLastError()); - } - } - -done: - if (hFMap != NULL && !CloseHandle(hFMap)) - { - bRet = FALSE; - Trace("PALSUITE ERROR: CreateEventW: CloseHandle(%p) call " - "failed\nGetLastError returned '%u'.\n", hFMap, - GetLastError()); - } - - if (FALSE == bRet) - { - bRet = FAIL; - } - else - { - bRet = PASS; - } - - PAL_TerminateEx(bRet); - - return(bRet); - -} - - - diff --git a/src/coreclr/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test1/CreateMutexW.cpp b/src/coreclr/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test1/CreateMutexW.cpp deleted file mode 100644 index f4fc377d5d6da8..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/CreateMutexW_ReleaseMutex/test1/CreateMutexW.cpp +++ /dev/null @@ -1,343 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** Source: CreateMutexW_ReleaseMutex/test1/CreateMutexW.c -** -** Purpose: This test case tests whether a Mutex object created -** with CreateMutex really works by mutually excluding -** threads from accessing a data structure at the same -** time. Here we have a buffer that can be filled or -** emptied, we use a Mutex object to ensure that one -** operation cannot be started until the other is -** finished. If one operation detects that the other -** has not finished, it fails. There is a Producer -** thread which will try to fill the buffer 25 times, -** and a consumer thread which try to empty the buffer -** 25 times. If either the fill or empty operations -** fails because the Mutex failed to mutually exclude -** them, the corresponding thread will set an error -** flag and return. This will cause the test case to -** fail. -** -** To increase the probability of identifying problems, -** the Fill operation has been slowed dowm with a call -** to Sleep. This ensures that one operation will try -** to access the shared buffer while the other is in -** progress. -** -** NOTE: this test case also serves as a test case for -** WaitForSingleObject. -** -** -** Dependencies: CreateThread -** ReleaseMutex -** WaitForSingleObject -** WaitForMultipleObjects -** Sleep -** memset -** - -** -**=========================================================*/ - -#define UNICODE -#include - -/* Define some values that we will using many times */ -#define MAIN_BUF_SIZE 40 -#define NUM_OF_CYCLES 40 - -/* Buffer Operation return codes */ -#define OP_OK 0 -#define OP_ERR 1 -#define OP_NONE 2 - - -static HANDLE hMutex; /* handle to mutex */ - -static BOOL bProdErr; /* Producer error Flag */ -static BOOL bConErr; /* Consumer error Flag */ - -/* Test Buffer */ -static char Buffer[MAIN_BUF_SIZE]; - -/* - * EmptyBuffer implements the empty operation for test buffer. - */ -int -EmptyBuffer() -{ - int i; - - if ( WaitForSingleObject(hMutex, INFINITE) == WAIT_FAILED) - { - Fail("ERROR: WaitForSingleObject failed.\n"); - } - - /* Check to see if the buffer is already completely empty */ - for (i=0; i - -#define szMutex "MyMutex" -#define szEmpty "" - -/* Function Prototypes */ -BOOL NegativeReleaseMutexTests_CreateMutexW_ReleaseMutex_test2(); - -struct ThreadData -{ - HANDLE hMutex; - BOOL bReturnCode; -}; -typedef struct ThreadData THREADDATA; - - -PALTEST(threading_CreateMutexW_ReleaseMutex_test2_paltest_createmutexw_releasemutex_test2, "threading/CreateMutexW_ReleaseMutex/test2/paltest_createmutexw_releasemutex_test2") -{ - BOOL bFailures = FALSE; - char *szMaxPath; - - if(0 != (PAL_Initialize(argc, argv))) - { - return ( FAIL ); - } - - /* - * Run some negative tests on ReleaseMutex - */ - - if (!NegativeReleaseMutexTests_CreateMutexW_ReleaseMutex_test2()) - { - bFailures = TRUE; - } - - - /* - * If there were any failures, then abort with a call to Fail - */ - - if (bFailures == TRUE) - { - Fail("ERROR: There some failures in the Mutex tests.\n"); - } - - PAL_Terminate(); - return ( PASS ); -} - -/* - * Testing Function - * - * Try some negative tests on ReleaseMutex - */ -BOOL NegativeReleaseMutexTests_CreateMutexW_ReleaseMutex_test2() -{ - HANDLE hMutex; - BOOL bRet; - BOOL bResults = TRUE; - - - /* - * Try calling ReleaseMutex on a null handle - */ - hMutex = 0; - bRet = ReleaseMutex(hMutex); - - if (bRet != 0) - { - Trace("Error: ReleaseMutex accepted null handle.\n"); - bResults = FALSE; - } - - - /* - * Try calling ReleaseMutex on an handle that we don't own - */ - hMutex = CreateMutexW (NULL, TRUE, NULL); - if (hMutex == 0) - { - Trace("Error: CreateMutex failed.\n"); - bResults = FALSE; - } - - bRet = ReleaseMutex(hMutex); - bRet = ReleaseMutex(hMutex); - - if (bRet != FALSE) - { - Trace("Error: ReleaseMutex accepted unowned handle.\n"); - bResults = FALSE; - } - - if (CloseHandle(hMutex) == FALSE) - { - Trace("Error: CloseHandle failed.\n"); - bResults = FALSE; - } - - - - /* - * Try calling ReleaseMutex on an handle that has been closed - */ - hMutex = CreateMutexW (NULL, TRUE, NULL); - if (hMutex == 0) - { - Trace("Error: CreateMutex failed.\n"); - bResults = FALSE; - } - - if (ReleaseMutex(hMutex) == FALSE) - { - Trace("Error: ReleaseMutex failed.\n"); - bResults = FALSE; - } - if (CloseHandle(hMutex) == FALSE) - { - Trace("Error: CloseHandle failed.\n"); - bResults = FALSE; - } - - bRet = ReleaseMutex(hMutex); - - if (bRet != FALSE) - { - Trace("Error: ReleaseMutex accepted invalid handle.\n"); - bResults = FALSE; - } - - return bResults; -} diff --git a/src/coreclr/pal/tests/palsuite/threading/DuplicateHandle/test11/childprocess.cpp b/src/coreclr/pal/tests/palsuite/threading/DuplicateHandle/test11/childprocess.cpp deleted file mode 100644 index aafa76918ee34f..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/DuplicateHandle/test11/childprocess.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** Source: childprocess.c -** -** Purpose: Test to ensure DuplicateHandle works properly. -** -** Dependencies: PAL_Initialize -** PAL_Terminate -** CreateMutexW -** WaitForSingleObject -** CloseHandle -** -** -**=========================================================*/ - -#include -#include "myexitcode.h" - - -PALTEST(threading_DuplicateHandle_test11_paltest_duplicatehandle_test11_child, "threading/DuplicateHandle/test11/paltest_duplicatehandle_test11_child") -{ - HANDLE hMutex; - WCHAR wszMutexName[] = { 'T','E','S','T','1','1','\0' }; - DWORD dwRet; - int i; - - /* initialize the PAL */ - if( PAL_Initialize(argc, argv) != 0 ) - { - return( FAIL ); - } - - /* open a mutex to synchronize with the parent process */ - hMutex = CreateMutexW( NULL, FALSE, wszMutexName ); - if( hMutex == NULL ) - { - Fail( "ERROR:%lu:CreateMutex() call failed\r\n", GetLastError() ); - } - - /* acquire the mutex lock */ - dwRet = WaitForSingleObject( hMutex, 10000 ); - if( dwRet != WAIT_OBJECT_0 ) - { - Trace( "ERROR:WaitForSingleObject() returned %lu, " - "expected WAIT_OBJECT_0", - dwRet ); - if( ! CloseHandle( hMutex ) ) - { - Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); - } - Fail( "test failed\n" ); - } - - - /* simulate some activity */ - for( i=0; i<50000; i++ ) - ; - - /* close our mutex handle */ - if( ! CloseHandle( hMutex ) ) - { - Fail( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); - } - - /* terminate the PAL */ - PAL_Terminate(); - - /* return the predefined exit code */ - return TEST_EXIT_CODE; -} diff --git a/src/coreclr/pal/tests/palsuite/threading/DuplicateHandle/test11/myexitcode.h b/src/coreclr/pal/tests/palsuite/threading/DuplicateHandle/test11/myexitcode.h deleted file mode 100644 index b9446c00d1a999..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/DuplicateHandle/test11/myexitcode.h +++ /dev/null @@ -1,12 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** Source: duplicatehandle/test11/myexitcode.h -** -** Purpose: Define an exit code constant. -** -** -**=========================================================*/ -#define TEST_EXIT_CODE 31 diff --git a/src/coreclr/pal/tests/palsuite/threading/DuplicateHandle/test11/test11.cpp b/src/coreclr/pal/tests/palsuite/threading/DuplicateHandle/test11/test11.cpp deleted file mode 100644 index 94f177f3561cd9..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/DuplicateHandle/test11/test11.cpp +++ /dev/null @@ -1,321 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================================= -** -** Source: test11.c -** -** Purpose: -** -** Test to ensure proper operation of the DuplicateHandle API. -** The test launches a trivial child process, then opens -** a handle to it using OpenProcess. It then duplicates that -** handle and uses it to wait for the child process to terminate, -** and then checks the exit code of the child process in order to -** verify that it was in fact a handle to the correct -** process. The test tries to duplicate the handle again after -** the process has been closed, to verify that failure ensues. -** -** Dependencies: PAL_Initialize -** PAL_Terminate -** Fail -** ZeroMemory -** GetCurrentDirectoryW -** CreateProcessW -** WaitForSingleObject -** CreateMutexW -** ReleaseMutex -** CloseHandle -** GetLastError -** strlen -** strncpy -** -** -**===========================================================================*/ -#include -#include "myexitcode.h" - -PALTEST(threading_DuplicateHandle_test11_paltest_duplicatehandle_test11, "threading/DuplicateHandle/test11/paltest_duplicatehandle_test11") -{ - const char* rgchChildFile = "childprocess"; - - STARTUPINFO si; - PROCESS_INFORMATION pi; - - DWORD dwError; - DWORD dwExitCode; - DWORD dwFileLength; - DWORD dwDirLength; - DWORD dwSize; - DWORD dwRet; - - HANDLE hMutex; - HANDLE hChildProcess; - HANDLE hDupChildProcess; - - char rgchDirName[_MAX_DIR]; - char absPathBuf[MAX_PATH]; - char* rgchAbsPathName; - - BOOL ret = FAIL; - BOOL bChildDone = FALSE; - WCHAR wszMutexName[] = { 'T','E','S','T','1','1','\0' }; - - /* initialize the PAL */ - if( PAL_Initialize(argc, argv) != 0 ) - { - return( FAIL ); - } - - /* create a mutex to synchronize with the child process */ - hMutex = CreateMutexW( NULL, TRUE, wszMutexName ); - if( hMutex == NULL ) - { - Fail( "ERROR:%lu:CreateMutex() call failed\r\n", GetLastError() ); - } - - /* zero our process and startup info structures */ - ZeroMemory( &si, sizeof(si) ); - si.cb = sizeof( si ); - ZeroMemory( &pi, sizeof(pi) ); - - /* build the absolute path to the child process */ - rgchAbsPathName = &absPathBuf[0]; - dwFileLength = strlen( rgchChildFile ); - - strcpy(rgchDirName, ".\\"); - dwDirLength = strlen(rgchDirName); - - dwSize = mkAbsoluteFilename( rgchDirName, - dwDirLength, - rgchChildFile, - dwFileLength, - rgchAbsPathName ); - if( dwSize == 0 ) - { - if( ReleaseMutex( hMutex ) == 0 ) - { - Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); - } - if( CloseHandle( hMutex ) == 0 ) - { - Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); - } - Fail( "Palsuite Code: mkAbsoluteFilename() call failed. Could ", - "not build absolute path name to file\n. Exiting.\n" ); - } - - LPWSTR rgchAbsPathNameW = convert(rgchAbsPathName); - /* launch the child process */ - if( !CreateProcess( NULL, /* module name to execute */ - rgchAbsPathNameW, /* command line */ - NULL, /* process handle not */ - /* inheritable */ - NULL, /* thread handle not */ - /*inheritable */ - FALSE, /* handle inheritance */ - CREATE_NEW_CONSOLE, /* dwCreationFlags */ - NULL, /* use parent's environment */ - NULL, /* use parent's starting */ - /* directory */ - &si, /* startup info struct */ - &pi ) /* process info struct */ - ) - { - dwError = GetLastError(); - free(rgchAbsPathNameW); - if( ReleaseMutex( hMutex ) == 0 ) - { - Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); - } - if( CloseHandle( hMutex ) == 0 ) - { - Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); - } - Fail( "CreateProcess call failed with error code %d\n", - dwError ); - } - - free(rgchAbsPathNameW); - - /* open another handle to the child process */ - hChildProcess = OpenProcess( PROCESS_ALL_ACCESS, /* access */ - FALSE, /* inheritable */ - pi.dwProcessId /* process id */ - ); - if( hChildProcess == NULL ) - { - dwError = GetLastError(); - if( ReleaseMutex( hMutex ) == 0 ) - { - Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); - } - Trace( "ERROR:%lu:OpenProcess call failed\n", dwError ); - goto cleanup3; - } - - /* duplicate the child process handle */ - if( ! DuplicateHandle( GetCurrentProcess(), - hChildProcess, - GetCurrentProcess(), - &hDupChildProcess, - GENERIC_READ|GENERIC_WRITE, - FALSE, - DUPLICATE_SAME_ACCESS) ) - { - Trace( "ERROR:%lu:DuplicateHandle() call failed\n", GetLastError() ); - if( ReleaseMutex( hMutex ) == 0 ) - { - Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); - } - goto cleanup2; - } - - /* release the mutex so the child can proceed */ - if( ReleaseMutex( hMutex ) == 0 ) - { - Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); - goto cleanup1; - } - - /* wait for the child process to complete, using the new handle */ - dwRet = WaitForSingleObject( hDupChildProcess, 10000 ); - if( dwRet != WAIT_OBJECT_0 ) - { - Trace( "ERROR:WaitForSingleObject call returned %lu, " - "expected WAIT_OBJECT_0", - dwRet ); - goto cleanup1; - } - - /* remember that we waited until the child was finished */ - bChildDone = TRUE; - - /* check the exit code from the process -- this is a bit of an */ - /* extra verification that we opened the correct process handle */ - if( ! GetExitCodeProcess( hDupChildProcess, &dwExitCode ) ) - { - Trace( "ERROR:%lu:GetExitCodeProcess call failed\n", GetLastError() ); - goto cleanup1; - } - - /* verification */ - if( (dwExitCode & 0xFF) != (TEST_EXIT_CODE & 0xFF) ) - { - Trace( "GetExitCodeProcess returned an incorrect exit code %d, " - "expected value is %d\n", - (dwExitCode & 0xFF), - (TEST_EXIT_CODE & 0xFF)); - goto cleanup1; - } - - /* close the duplicate handle */ - if( ! CloseHandle( hDupChildProcess ) ) - { - Trace( "ERROR:%lu:CloseHandle call failed\n", GetLastError() ); - goto cleanup2; - } - - /* close the child process handle */ - if( ! CloseHandle ( hChildProcess ) ) - { - Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); - goto cleanup3; - } - - /* try to call duplicate handle on the closed child process handle */ - if( DuplicateHandle( GetCurrentProcess(), - hChildProcess, - GetCurrentProcess(), - &hDupChildProcess, - GENERIC_READ|GENERIC_WRITE, - FALSE, - DUPLICATE_SAME_ACCESS) ) - { - Trace( "ERROR:%lu:DuplicateHandle call succeeded on " - "a closed process handle, expected ERROR_INVALID_HANDLE\n" ); - if( ! CloseHandle( hDupChildProcess ) ) - { - Trace( "ERROR:%lu:CloseHandle call failed\n", GetLastError() ); - } - goto cleanup3; - } - - /* verify that the last error was ERROR_INVALID_HANDLE */ - dwRet = GetLastError(); - if( dwRet != ERROR_INVALID_HANDLE ) - { - Trace( "ERROR:DuplicateHandle returned %lu, " - "expected ERROR_INVALID_HANDLE\n", - dwRet ); - goto cleanup3; - } - - - /* success if we get here */ - ret = PASS; - - /* skip the cleanup stuff that's already done */ - goto cleanup3; - - -cleanup1: - /* close our duplicate handle */ - if( ! CloseHandle( hDupChildProcess ) ) - { - Trace( "ERROR:%lu:CloseHandle call failed\n", GetLastError() ); - ret = FAIL; - } - -cleanup2: - /* wait on the child process to complete if necessary */ - if( ! bChildDone ) - { - dwRet = WaitForSingleObject( hChildProcess, 10000 ); - if( dwRet != WAIT_OBJECT_0 ) - { - Trace( "ERROR:WaitForSingleObject call returned %lu, " - "expected WAIT_OBJECT_0", - dwRet ); - ret = FAIL; - } - } - - /* close our child process handle */ - if( CloseHandle ( hChildProcess ) == 0 ) - { - Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); - ret = FAIL; - } - -cleanup3: - /* close all our other handles */ - if( CloseHandle ( pi.hProcess ) == 0 ) - { - Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); - ret = FAIL; - } - if( CloseHandle ( pi.hThread ) == 0 ) - { - Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); - ret = FAIL; - } - if( CloseHandle( hMutex ) == 0 ) - { - Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); - ret = FAIL; - } - - if( ret == FAIL ) - { - Fail( "test failed\n" ); - } - - - - /* terminate the PAL */ - PAL_Terminate(); - - /* return success */ - return PASS; -} diff --git a/src/coreclr/pal/tests/palsuite/threading/DuplicateHandle/test4/test4.cpp b/src/coreclr/pal/tests/palsuite/threading/DuplicateHandle/test4/test4.cpp deleted file mode 100644 index e489f54aa5f6ea..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/DuplicateHandle/test4/test4.cpp +++ /dev/null @@ -1,238 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*===================================================================== -** -** Source: test4.c (DuplicateHandle) -** -** Purpose: Tests the PAL implementation of the DuplicateHandle function. -** This test duplication of a Mutex handle. The test will comprise -** of creating a Mutex and its duplicate and create a thread that -** will get ownership. Another thread will be create that will -** attempt to get ownership of the duplicate Mutex, this will -** fail, since the Mutex is owned by another thread. The Mutex -** will be released and then the thread will attempt to get -** ownership of the duplicate Mutex, this will succeed. -** -** -**===================================================================*/ -#include - -enum wait_results -{ - WR_WAITING, - WR_GOT_MUTEX, - WR_TIMED_OUT, - WR_RELEASED -}; - - -volatile int t1_result_DuplicateHandle_test4=WR_WAITING; -volatile int t2_result_DuplicateHandle_test4=WR_WAITING; - - -DWORD PALAPI ThreadTest1_DuplicateHandle_test4(LPVOID lpParam) -{ - DWORD dwWait; - - dwWait = WaitForSingleObject((HANDLE)lpParam, 0); - if (dwWait == WAIT_OBJECT_0) - { - /* tell the main thread we got the mutex */ - t1_result_DuplicateHandle_test4=WR_GOT_MUTEX; - - /* wait for main thread to tell us to release the mutex */ - while(WR_GOT_MUTEX == t1_result_DuplicateHandle_test4) - Sleep(1); - ReleaseMutex((HANDLE)lpParam); - - /* tell the main thread we released the mutex */ - t1_result_DuplicateHandle_test4 = WR_RELEASED; - } - else - { - t1_result_DuplicateHandle_test4 = WR_TIMED_OUT; - } - return 0; -} - -DWORD PALAPI ThreadTest2_DuplicateHandle_test4(LPVOID lpParam) -{ - DWORD dwWait; - - dwWait = WaitForSingleObject((HANDLE)lpParam, 0 ); - if (dwWait == WAIT_OBJECT_0) - { - ReleaseMutex((HANDLE)lpParam); - t2_result_DuplicateHandle_test4 = WR_GOT_MUTEX; - } - else - { - t2_result_DuplicateHandle_test4 = WR_TIMED_OUT; - } - - return 0; -} - - -PALTEST(threading_DuplicateHandle_test4_paltest_duplicatehandle_test4, "threading/DuplicateHandle/test4/paltest_duplicatehandle_test4") -{ - - HANDLE hDupMutex; - HANDLE hMutex; - HANDLE hThread; - HANDLE hThread2; - BOOL bDupHandle=FALSE; - DWORD dwThreadId = 0; - - if ((PAL_Initialize(argc,argv)) != 0) - { - return(FAIL); - } - - /*Create Mutex without ownership*/ - hMutex = CreateMutexW(NULL, // no security attributes - FALSE, // initially not owned - NULL); // name of mutex - if (hMutex == NULL) - { - Fail("ERROR:%u: Unable to create mutex\n", - GetLastError()); - } - - /*Create Duplicate of the Mutex above*/ - bDupHandle = DuplicateHandle(GetCurrentProcess(), - hMutex, - GetCurrentProcess(), - &hDupMutex, - GENERIC_READ|GENERIC_WRITE, - FALSE, - DUPLICATE_SAME_ACCESS); - if (!bDupHandle) - { - Trace("ERROR:%u: Created the duplicate handle to " - "closed event handle hMutex=0x%lx\n", - GetLastError(), - hMutex); - CloseHandle(hMutex); - Fail(""); - } - - /*Create a thread to test the Mutex*/ - hThread = CreateThread(NULL, - 0, - &ThreadTest1_DuplicateHandle_test4, - hMutex, - 0, - &dwThreadId); - if (hThread == NULL) - { - Trace("ERROR:%u: unable to create thread\n", - GetLastError()); - CloseHandle(hMutex); - CloseHandle(hDupMutex); - Fail(""); - } - - /* wait until thread has taken the mutex */ - while (WR_WAITING == t1_result_DuplicateHandle_test4) - Sleep(1); - - if(WR_TIMED_OUT == t1_result_DuplicateHandle_test4) - { - Trace("ERROR: %u: thread 1 couldn't acquire the mutex\n"); - CloseHandle(hMutex); - CloseHandle(hDupMutex); - CloseHandle(hThread); - Fail(""); - } - - /*Create a second thread to use the duplicate Mutex*/ - /*This should fail since the Mutex is owned hThread*/ - hThread2 = CreateThread(NULL, - 0, - &ThreadTest2_DuplicateHandle_test4, - hDupMutex, - 0, - &dwThreadId); - - if (hThread2 == NULL) - { - Trace("ERROR:%u: unable to create thread\n", - GetLastError()); - CloseHandle(hMutex); - CloseHandle(hDupMutex); - CloseHandle(hThread); - Fail(""); - } - - /* wait until thread has tried to take the mutex */ - while (WR_WAITING == t2_result_DuplicateHandle_test4) - Sleep(1); - - if (WR_TIMED_OUT != t2_result_DuplicateHandle_test4 ) - { - Trace("ERROR:%u: Able to take mutex %#x while its duplicate %#x is " - "held\n", hDupMutex, hMutex); - CloseHandle(hMutex); - CloseHandle(hDupMutex); - CloseHandle(hThread); - CloseHandle(hThread2); - Fail(""); - } - - /* reset second thread status */ - t2_result_DuplicateHandle_test4 = WR_WAITING; - - /* tell thread 1 to release the mutex */ - t1_result_DuplicateHandle_test4 = WR_WAITING; - - /* wait for thread 1 to release the mutex */ - while (WR_WAITING == t1_result_DuplicateHandle_test4) - Sleep(1); - - CloseHandle(hThread2); - - /*Re-Create the second thread to reuse the duplicated Mutex*/ - /*This test should pass, the Mutex has since been released*/ - hThread2 = CreateThread(NULL, - 0, - &ThreadTest2_DuplicateHandle_test4, - hDupMutex, - 0, - &dwThreadId); - - if (hThread2 == NULL) - { - Trace("ERROR:%u: unable to create thread\n", - GetLastError()); - CloseHandle(hMutex); - CloseHandle(hDupMutex); - CloseHandle(hThread); - Fail(""); - } - - /* wait until thread has taken the mutex */ - while (WR_WAITING == t2_result_DuplicateHandle_test4) - Sleep(1); - - if (WR_GOT_MUTEX != t2_result_DuplicateHandle_test4 ) - { - Trace("ERROR:%u: Unable to take mutex %#x after its duplicate %#x was " - "released\n", hDupMutex, hMutex); - CloseHandle(hMutex); - CloseHandle(hDupMutex); - CloseHandle(hThread); - CloseHandle(hThread2); - Fail(""); - } - - /*Cleanup.*/ - CloseHandle(hMutex); - CloseHandle(hDupMutex); - CloseHandle(hThread); - CloseHandle(hThread2); - - PAL_Terminate(); - return (PASS); -} diff --git a/src/coreclr/pal/tests/palsuite/threading/OpenEventW/test4/test4.cpp b/src/coreclr/pal/tests/palsuite/threading/OpenEventW/test4/test4.cpp deleted file mode 100644 index b56af149a4dbcb..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/OpenEventW/test4/test4.cpp +++ /dev/null @@ -1,111 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================================= -** -** Source: test4.c -** -** Purpose: Positive test for OpenEventW. -** -** Dependencies: PAL_Initialize -** PAL_Terminate -** CreateEvent -** CloseHandle -** WaitForSingleObject -** -** Purpose: -** -** Test to ensure proper operation of the OpenEventW() -** API by trying to open an event with a name that is -** already taken by a non-event object. -** -** -**===========================================================================*/ -#include - - - -PALTEST(threading_OpenEventW_test4_paltest_openeventw_test4, "threading/OpenEventW/test4/paltest_openeventw_test4") - -{ - /* local variables */ - BOOL bRet = PASS; - DWORD dwLastError = 0; - HANDLE hMutex = NULL; - HANDLE hTestEvent = NULL; - LPSECURITY_ATTRIBUTES lpSecurityAttributes = NULL; - BOOL bInitialState = TRUE; - WCHAR wcName[] = {'I','m','A','M','u','t','e','x','\0'}; - LPWSTR lpName = wcName; - - - /* PAL initialization */ - if( (PAL_Initialize(argc, argv)) != 0 ) - { - return( FAIL ); - } - - /* create a mutex object */ - hMutex = CreateMutexW( lpSecurityAttributes, - bInitialState, - lpName ); - - if( hMutex == NULL ) - { - /* ERROR */ - Fail( "ERROR:%lu:CreateMutexW() call failed\n", GetLastError() ); - } - - /* open a new handle to our event */ - hTestEvent = OpenEventW(EVENT_ALL_ACCESS, /* we want all rights */ - FALSE, /* no inherit */ - lpName ); - - if( hTestEvent != NULL ) - { - /* ERROR */ - Trace( "ERROR:OpenEventW() call succeeded against a named " - "mutex, should have returned NULL\n" ); - if( ! CloseHandle( hTestEvent ) ) - { - Trace( "ERROR:%lu:CloseHandle() call failed \n", GetLastError() ); - } - bRet = FAIL; - } - else - { - dwLastError = GetLastError(); - if( dwLastError != ERROR_INVALID_HANDLE ) - { - /* ERROR */ - Trace( "ERROR:OpenEventW() call failed against a named " - "mutex, but returned an unexpected result: %lu\n", - dwLastError ); - bRet = FAIL; - } - } - - - /* close the mutex handle */ - if( ! CloseHandle( hMutex ) ) - { - Trace( "ERROR:%lu:CloseHandle() call failed \n", GetLastError() ); - bRet = FAIL; - } - - - /* fail here if we weren't successful */ - if( bRet == FAIL ) - { - Fail( "" ); - } - - - /* PAL termination */ - PAL_Terminate(); - - /* return success or failure */ - return PASS; -} - - diff --git a/src/coreclr/pal/tests/palsuite/threading/OpenProcess/test1/childProcess.cpp b/src/coreclr/pal/tests/palsuite/threading/OpenProcess/test1/childProcess.cpp index db78c20ebc773c..9c12e981ec3663 100644 --- a/src/coreclr/pal/tests/palsuite/threading/OpenProcess/test1/childProcess.cpp +++ b/src/coreclr/pal/tests/palsuite/threading/OpenProcess/test1/childProcess.cpp @@ -23,8 +23,6 @@ PALTEST(threading_OpenProcess_test1_paltest_openprocess_test1_child, "threading/OpenProcess/test1/paltest_openprocess_test1_child") { - HANDLE hMutex; - WCHAR wszMutexName[] = { 'T','E','S','T','1','\0' }; DWORD dwRet; int i; @@ -34,38 +32,10 @@ PALTEST(threading_OpenProcess_test1_paltest_openprocess_test1_child, "threading/ return( FAIL ); } - /* open a mutex to synchronize with the parent process */ - hMutex = CreateMutexW( NULL, FALSE, wszMutexName ); - if( hMutex == NULL ) - { - Fail( "ERROR:%lu:CreateMutex() call failed\r\n", GetLastError() ); - } - - /* acquire the mutex lock */ - dwRet = WaitForSingleObject( hMutex, 10000 ); - if( dwRet != WAIT_OBJECT_0 ) - { - Trace( "ERROR:WaitForSingleObject() returned %lu, " - "expected WAIT_OBJECT_0", - dwRet ); - if( ! CloseHandle( hMutex ) ) - { - Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); - } - Fail( "test failed\n" ); - } - - /* simulate some activity */ for( i=0; i<50000; i++ ) ; - /* close our mutex handle */ - if( ! CloseHandle( hMutex ) ) - { - Fail( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); - } - /* terminate the PAL */ PAL_Terminate(); diff --git a/src/coreclr/pal/tests/palsuite/threading/OpenProcess/test1/test1.cpp b/src/coreclr/pal/tests/palsuite/threading/OpenProcess/test1/test1.cpp index 9bcdcb129eab98..5c5e2d3e5d19bf 100644 --- a/src/coreclr/pal/tests/palsuite/threading/OpenProcess/test1/test1.cpp +++ b/src/coreclr/pal/tests/palsuite/threading/OpenProcess/test1/test1.cpp @@ -40,7 +40,6 @@ PALTEST(threading_OpenProcess_test1_paltest_openprocess_test1, "threading/OpenPr DWORD dwSize; DWORD dwRet; - HANDLE hMutex; HANDLE hChildProcess; char rgchDirName[_MAX_DIR]; @@ -49,7 +48,6 @@ PALTEST(threading_OpenProcess_test1_paltest_openprocess_test1, "threading/OpenPr BOOL ret = FAIL; BOOL bChildDone = FALSE; - WCHAR wszMutexName[] = { 'T','E','S','T','1','\0' }; /* initialize the PAL */ if( PAL_Initialize(argc, argv) != 0 ) @@ -57,13 +55,6 @@ PALTEST(threading_OpenProcess_test1_paltest_openprocess_test1, "threading/OpenPr return( FAIL ); } - /* create a mutex to synchronize with the child process */ - hMutex = CreateMutexW( NULL, TRUE, wszMutexName ); - if( hMutex == NULL ) - { - Fail( "ERROR:%lu:CreateMutex() call failed\r\n", GetLastError() ); - } - /* zero our process and startup info structures */ ZeroMemory( &si, sizeof(si) ); si.cb = sizeof( si ); @@ -83,14 +74,6 @@ PALTEST(threading_OpenProcess_test1_paltest_openprocess_test1, "threading/OpenPr rgchAbsPathName ); if( dwSize == 0 ) { - if( ReleaseMutex( hMutex ) == 0 ) - { - Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); - } - if( CloseHandle( hMutex ) == 0 ) - { - Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); - } Fail( "Palsuite Code: mkAbsoluteFilename() call failed. Could ", "not build absolute path name to file\n. Exiting.\n" ); } @@ -114,14 +97,6 @@ PALTEST(threading_OpenProcess_test1_paltest_openprocess_test1, "threading/OpenPr { dwError = GetLastError(); free(rgchAbsPathNameW); - if( ReleaseMutex( hMutex ) == 0 ) - { - Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); - } - if( CloseHandle( hMutex ) == 0 ) - { - Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); - } Fail( "CreateProcess call failed with error code %d\n", dwError ); } @@ -136,21 +111,10 @@ PALTEST(threading_OpenProcess_test1_paltest_openprocess_test1, "threading/OpenPr if( hChildProcess == NULL ) { dwError = GetLastError(); - if( ReleaseMutex( hMutex ) == 0 ) - { - Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); - } Trace( "ERROR:%lu:OpenProcess call failed\n", dwError ); goto cleanup2; } - /* release the mutex so the child can proceed */ - if( ReleaseMutex( hMutex ) == 0 ) - { - Trace( "ERROR:%lu:ReleaseMutex() call failed\n", GetLastError() ); - goto cleanup; - } - /* wait for the child process to complete, using the new handle */ dwRet = WaitForSingleObject( hChildProcess, 10000 ); if( dwRet != WAIT_OBJECT_0 ) @@ -218,11 +182,6 @@ PALTEST(threading_OpenProcess_test1_paltest_openprocess_test1, "threading/OpenPr Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); ret = FAIL; } - if( CloseHandle( hMutex ) == 0 ) - { - Trace( "ERROR:%lu:CloseHandle() call failed\n", GetLastError() ); - ret = FAIL; - } if( ret == FAIL ) { diff --git a/src/coreclr/pal/tests/palsuite/threading/ReleaseMutex/test3/ReleaseMutex.cpp b/src/coreclr/pal/tests/palsuite/threading/ReleaseMutex/test3/ReleaseMutex.cpp deleted file mode 100644 index be43bce844efb1..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/ReleaseMutex/test3/ReleaseMutex.cpp +++ /dev/null @@ -1,102 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** Source: ReleaseMutex/test3/ReleaseMutex.c -** -** Purpose: Test failure code for ReleaseMutex. -** -** Dependencies: CreateMutex -** ReleaseMutex -** CreateThread -** - -** -**=========================================================*/ - -#include - -DWORD dwTestResult_ReleaseMutex_test3; /* global for test result */ - -DWORD dwThreadId_ReleaseMutex_test3; /* consumer thread identifier */ - -HANDLE hMutex_ReleaseMutex_test3; /* handle to mutex */ - -HANDLE hThread_ReleaseMutex_test3; /* handle to thread */ - -/* - * Thread function. - */ -DWORD -PALAPI -ThreadFunction_ReleaseMutex_test3( LPVOID lpNoArg ) -{ - - dwTestResult_ReleaseMutex_test3 = ReleaseMutex(hMutex_ReleaseMutex_test3); - - return 0; -} - -PALTEST(threading_ReleaseMutex_test3_paltest_releasemutex_test3, "threading/ReleaseMutex/test3/paltest_releasemutex_test3") -{ - - if(0 != (PAL_Initialize(argc, argv))) - { - return (FAIL); - } - - /* - * set dwTestResult so test fails even if ReleaseMutex is not called - */ - dwTestResult_ReleaseMutex_test3 = 1; - - /* - * Create mutex - */ - hMutex_ReleaseMutex_test3 = CreateMutexW ( - NULL, - TRUE, - NULL); - - if ( NULL == hMutex_ReleaseMutex_test3 ) - { - Fail ( "hMutex = CreateMutex () - returned NULL\n" - "Failing Test.\nGetLastError returned %d\n", GetLastError()); - } - - /* - * Create ThreadFunction - */ - hThread_ReleaseMutex_test3 = CreateThread( - NULL, - 0, - ThreadFunction_ReleaseMutex_test3, - NULL, - 0, - &dwThreadId_ReleaseMutex_test3); - - if ( NULL == hThread_ReleaseMutex_test3 ) - { - - Fail ( "CreateThread() returned NULL. Failing test.\n" - "GetLastError returned %d\n", GetLastError()); - } - - /* - * Wait for ThreadFunction to complete - */ - WaitForSingleObject (hThread_ReleaseMutex_test3, INFINITE); - - if (dwTestResult_ReleaseMutex_test3) - { - Fail ("ReleaseMutex() test was expected to return 0.\n" - "It returned %d. Failing test.\n", dwTestResult_ReleaseMutex_test3 ); - } - - Trace ("ReleaseMutex() test returned 0.\nTest passed.\n"); - - PAL_Terminate(); - return ( PASS ); - -} diff --git a/src/coreclr/pal/tests/palsuite/threading/SignalObjectAndWait/SignalObjectAndWaitTest.cpp b/src/coreclr/pal/tests/palsuite/threading/SignalObjectAndWait/SignalObjectAndWaitTest.cpp index 3601abb4b904b7..7c7715adfb57ff 100644 --- a/src/coreclr/pal/tests/palsuite/threading/SignalObjectAndWait/SignalObjectAndWaitTest.cpp +++ b/src/coreclr/pal/tests/palsuite/threading/SignalObjectAndWait/SignalObjectAndWaitTest.cpp @@ -12,10 +12,7 @@ enum class SignalableObjectType AutoResetEvent, Semaphore, FullSemaphore, - Mutex, - UnlockedMutex, - - Last = UnlockedMutex + Last = FullSemaphore, }; enum class WaitableObjectType @@ -29,10 +26,8 @@ enum class WaitableObjectType UnsignaledAutoResetEvent, Semaphore, EmptySemaphore, - Mutex, - LockedMutex, - Last = LockedMutex + Last = EmptySemaphore }; void operator ++(SignalableObjectType &objectType) @@ -93,12 +88,6 @@ HANDLE CreateObjectToSignal(SignalableObjectType objectType) case SignalableObjectType::FullSemaphore: return CreateSemaphoreExW(nullptr, 1, 1, nullptr, 0, 0); - case SignalableObjectType::Mutex: - return CreateMutex(nullptr, true, nullptr); - - case SignalableObjectType::UnlockedMutex: - return CreateMutex(nullptr, false, nullptr); - default: TestAssert(false); } @@ -121,10 +110,6 @@ void VerifySignal(HANDLE h, SignalableObjectType objectType) TestAssert(!ReleaseSemaphore(h, 1, nullptr)); break; - case SignalableObjectType::Mutex: - TestAssert(!ReleaseMutex(h)); - break; - default: TestAssert(false); } @@ -163,12 +148,6 @@ HANDLE CreateObjectToWaitOn(WaitableObjectType objectType) case WaitableObjectType::EmptySemaphore: return CreateSemaphoreExW(nullptr, 0, 1, nullptr, 0, 0); - case WaitableObjectType::Mutex: - return CreateMutex(nullptr, false, nullptr); - - case WaitableObjectType::LockedMutex: - return CreateMutex(nullptr, true, nullptr); - default: TestAssert(false); } @@ -189,20 +168,6 @@ void VerifyWait(HANDLE h, WaitableObjectType objectType) TestAssert(WaitForSingleObject(h, 0) == WAIT_TIMEOUT); break; - case WaitableObjectType::Mutex: - TestAssert(ReleaseMutex(h)); - TestAssert(!ReleaseMutex(h)); - TestAssert(WaitForSingleObject(h, 0) == WAIT_OBJECT_0); - break; - - case WaitableObjectType::LockedMutex: - TestAssert(ReleaseMutex(h)); - TestAssert(ReleaseMutex(h)); - TestAssert(!ReleaseMutex(h)); - TestAssert(WaitForSingleObject(h, 0) == WAIT_OBJECT_0); - TestAssert(WaitForSingleObject(h, 0) == WAIT_OBJECT_0); - break; - default: TestAssert(false); } @@ -225,17 +190,6 @@ void CloseObjectToWaitOn(HANDLE h, WaitableObjectType objectType) CloseHandle(h); break; - case WaitableObjectType::Mutex: - ReleaseMutex(h); - CloseHandle(h); - break; - - case WaitableObjectType::LockedMutex: - ReleaseMutex(h); - ReleaseMutex(h); - CloseHandle(h); - break; - default: break; } @@ -257,11 +211,6 @@ bool Verify(SignalableObjectType signalableObjectType, WaitableObjectType waitab TestAssert(errorCode == ERROR_TOO_MANY_POSTS); return false; - case SignalableObjectType::UnlockedMutex: - TestAssert(waitResult == WAIT_FAILED); - TestAssert(errorCode == ERROR_NOT_OWNER); - return false; - default: break; } diff --git a/src/coreclr/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test3/test3.cpp b/src/coreclr/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test3/test3.cpp deleted file mode 100644 index 319690e5ed0f35..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test3/test3.cpp +++ /dev/null @@ -1,105 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*===================================================================== -** -** Source: test3.c -** -** Purpose: Tests that waiting on an open mutex will a return -** WAIT_OBJECT_0. Does this by creating a child thread that -** acquires the mutex, releases it, and exits. -** -** -**===================================================================*/ - -#include - - -const int ChildThreadWaitTime = 1000; -const int ParentDelayTime = 2000; - -DWORD PALAPI AcquiringProc(LPVOID lpParameter); - -PALTEST(threading_WaitForMultipleObjectsEx_test3_paltest_waitformultipleobjectsex_test3, "threading/WaitForMultipleObjectsEx/test3/paltest_waitformultipleobjectsex_test3") -{ - HANDLE Mutex; - HANDLE hThread = 0; - DWORD dwThreadId = 0; - int ret; - - if (0 != (PAL_Initialize(argc, argv))) - { - return FAIL; - } - - Mutex = CreateMutexW(NULL, FALSE, NULL); - if (Mutex == NULL) - { - Fail("Unable to create the mutex. GetLastError returned %d\n", - GetLastError()); - } - - hThread = CreateThread( NULL, - 0, - (LPTHREAD_START_ROUTINE)AcquiringProc, - (LPVOID) Mutex, - 0, - &dwThreadId); - - if (hThread == NULL) - { - Fail("ERROR: Was not able to create the thread to test!\n" - "GetLastError returned %d\n", GetLastError()); - } - - Sleep(ParentDelayTime); - - ret = WaitForMultipleObjectsEx(1, &Mutex, FALSE, INFINITE, FALSE); - if (ret != WAIT_OBJECT_0) - { - Fail("Expected WaitForMultipleObjectsEx to return WAIT_OBJECT_0\n" - "Got %d\n", ret); - } - - if (!CloseHandle(Mutex)) - { - Fail("CloseHandle on the mutex failed!\n"); - } - - if (!CloseHandle(hThread)) - { - Fail("CloseHandle on the thread failed!\n"); - } - - PAL_Terminate(); - return PASS; -} - -/* - * Entry Point for child thread. Acquries a mutex, releases it, and exits. - */ -DWORD PALAPI AcquiringProc(LPVOID lpParameter) -{ - HANDLE Mutex; - DWORD ret; - - Mutex = (HANDLE) lpParameter; - - Sleep(ChildThreadWaitTime); - - ret = WaitForSingleObject(Mutex, 0); - if (ret != WAIT_OBJECT_0) - { - Fail("Expected the WaitForSingleObject call on the mutex to succeed\n" - "Expected return of WAIT_OBJECT_0, got %d\n", ret); - } - - ret = ReleaseMutex(Mutex); - if (!ret) - { - Fail("Unable to release mutex! GetLastError returned %d\n", - GetLastError()); - } - - return 0; -} diff --git a/src/coreclr/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test4/test4.cpp b/src/coreclr/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test4/test4.cpp deleted file mode 100644 index c1589702a44c83..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/WaitForMultipleObjectsEx/test4/test4.cpp +++ /dev/null @@ -1,100 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*===================================================================== -** -** Source: test4.c -** -** Purpose: Tests that waiting on an abandonded mutex will a return -** WAIT_ABANDONED_0. Does this by creating a child thread that -** acquires the mutex and exits. -** -** -**===================================================================*/ - -#include - - -const int ChildThreadWaitTime = 1000; -const int ParentDelayTime = 2000; - -DWORD PALAPI AbandoningProc(LPVOID lpParameter); - -PALTEST(threading_WaitForMultipleObjectsEx_test4_paltest_waitformultipleobjectsex_test4, "threading/WaitForMultipleObjectsEx/test4/paltest_waitformultipleobjectsex_test4") -{ - HANDLE Mutex; - HANDLE hThread = 0; - DWORD dwThreadId = 0; - int ret; - - if (0 != (PAL_Initialize(argc, argv))) - { - return FAIL; - } - - Mutex = CreateMutexW(NULL, FALSE, NULL); - if (Mutex == NULL) - { - Fail("Unable to create the mutex. GetLastError returned %d\n", - GetLastError()); - } - - hThread = CreateThread( NULL, - 0, - (LPTHREAD_START_ROUTINE)AbandoningProc, - (LPVOID) Mutex, - 0, - &dwThreadId); - - if (hThread == NULL) - { - Fail("ERROR: Was not able to create the thread to test!\n" - "GetLastError returned %d\n", GetLastError()); - } - - Sleep(ParentDelayTime); - - ret = WaitForMultipleObjectsEx(1, &Mutex, FALSE, INFINITE, FALSE); - if (ret != WAIT_ABANDONED_0) - { - Fail("Expected WaitForMultipleObjectsEx to return WAIT_ABANDONED_0\n" - "Got %d\n", ret); - } - - ReleaseMutex(Mutex); - if (!CloseHandle(Mutex)) - { - Fail("CloseHandle on the mutex failed!\n"); - } - - if (!CloseHandle(hThread)) - { - Fail("CloseHandle on the thread failed!\n"); - } - - PAL_Terminate(); - return PASS; -} - -/* - * Entry Point for child thread. Acquries a mutex and exit's without - * releasing it. - */ -DWORD PALAPI AbandoningProc(LPVOID lpParameter) -{ - HANDLE Mutex; - DWORD ret; - - Mutex = (HANDLE) lpParameter; - - Sleep(ChildThreadWaitTime); - - ret = WaitForSingleObject(Mutex, 0); - if (ret != WAIT_OBJECT_0) - { - Fail("Expected the WaitForSingleObject call on the mutex to succeed\n" - "Expected return of WAIT_OBJECT_0, got %d\n", ret); - } - - return 0; -} diff --git a/src/coreclr/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExMutexTest/WFSOExMutexTest.cpp b/src/coreclr/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExMutexTest/WFSOExMutexTest.cpp deleted file mode 100644 index 66ae05bf230f66..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/WaitForSingleObject/WFSOExMutexTest/WFSOExMutexTest.cpp +++ /dev/null @@ -1,213 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*===================================================================== -** -** Source: WFSOExMutex.c -** -** Purpose: Tests a child thread in the middle of a -** WaitForSingleObjectEx call will be interrupted by QueueUserAPC -** if the alert flag was set. -** -** -**===================================================================*/ - -#include - -/*Based on SleepEx/test2 */ - -const int ChildThreadWaitTime = 4000; -const int InterruptTime = 2000; -const DWORD AcceptableDelta = 300; - -void RunTest_WFSOExMutexTest(BOOL AlertThread); -VOID PALAPI APCFunc_WFSOExMutexTest(ULONG_PTR dwParam); -DWORD PALAPI WaiterProc_WFSOExMutexTest(LPVOID lpParameter); - -DWORD ThreadWaitDelta_WFSOExMutexTest; -HANDLE hMutex_WFSOExMutexTest; -static volatile bool s_preWaitTimestampRecorded = false; - -PALTEST(threading_WaitForSingleObject_WFSOExMutexTest_paltest_waitforsingleobject_wfsoexmutextest, "threading/WaitForSingleObject/WFSOExMutexTest/paltest_waitforsingleobject_wfsoexmutextest") -{ - int ret=0; - - if (0 != (PAL_Initialize(argc, argv))) - { - return FAIL; - } - - /* - On some platforms (e.g. FreeBSD 4.9) the first call to some synch objects - (such as conditions) involves some pthread internal initialization that - can make the first wait slighty longer, potentially going above the - acceptable delta for this test. Let's add a dummy wait to preinitialize - internal structures - */ - Sleep(100); - - /* - The state of a mutex object is signaled when it is not owned by any thread. - The creating thread can use the bInitialOwner flag to request immediate ownership - of the mutex. Otherwise, a thread must use one of the wait functions to request - ownership. When the mutex's state is signaled, one waiting thread is granted - ownership, the mutex's state changes to nonsignaled, and the wait function returns. - Only one thread can own a mutex at any given time. The owning thread uses the - ReleaseMutex function to release its ownership. - */ - - /* Create a mutex that is not in the signalled state */ - hMutex_WFSOExMutexTest = CreateMutex(NULL, //No security attributes - TRUE, //Iniitally owned - NULL); //Name of mutex - - if (hMutex_WFSOExMutexTest == NULL) - { - Fail("Failed to create mutex! GetLastError returned %d.\n", - GetLastError()); - } - /* - * Check that Queueing an APC in the middle of a wait does interrupt - * it, if it's in an alertable state. - */ - - RunTest_WFSOExMutexTest(TRUE); - if ((ThreadWaitDelta_WFSOExMutexTest - InterruptTime) > AcceptableDelta) - { - Fail("Expected thread to wait for %d ms (and get interrupted).\n" - "Thread waited for %d ms! (Acceptable delta: %d)\n", - InterruptTime, ThreadWaitDelta_WFSOExMutexTest, AcceptableDelta); - } - - - /* - * Check that Queueing an APC in the middle of a wait does NOT interrupt - * it, if it is not in an alertable state. - */ - RunTest_WFSOExMutexTest(FALSE); - if ((ThreadWaitDelta_WFSOExMutexTest - ChildThreadWaitTime) > AcceptableDelta) - { - Fail("Expected thread to wait for %d ms (and not be interrupted).\n" - "Thread waited for %d ms! (Acceptable delta: %d)\n", - ChildThreadWaitTime, ThreadWaitDelta_WFSOExMutexTest, AcceptableDelta); - } - - - - //Release Mutex - ret = ReleaseMutex(hMutex_WFSOExMutexTest); - if (0==ret) - { - Fail("Unable to Release Mutex!\n" - "GetLastError returned %d\n", GetLastError()); - } - - //Close Mutex Handle - ret = CloseHandle(hMutex_WFSOExMutexTest); - if (!ret) - { - Fail("Unable to close handle to Mutex!\n" - "GetLastError returned %d\n", GetLastError()); - } - - PAL_Terminate(); - return PASS; -} - -void RunTest_WFSOExMutexTest(BOOL AlertThread) -{ - - HANDLE hThread = 0; - DWORD dwThreadId = 0; - - int ret=0; - - s_preWaitTimestampRecorded = false; - hThread = CreateThread( NULL, - 0, - (LPTHREAD_START_ROUTINE)WaiterProc_WFSOExMutexTest, - (LPVOID) AlertThread, - 0, - &dwThreadId); - - if (hThread == NULL) - { - Fail("ERROR: Was not able to create the thread to test!\n" - "GetLastError returned %d\n", GetLastError()); - } - - // Wait for the pre-wait timestamp to be recorded on the other thread before sleeping, since the sleep duration here will be - // compared against the sleep/wait duration on the other thread - while (!s_preWaitTimestampRecorded) - { - Sleep(0); - } - - Sleep(InterruptTime); - - ret = QueueUserAPC(APCFunc_WFSOExMutexTest, hThread, 0); - - if (ret == 0) - { - Fail("QueueUserAPC failed! GetLastError returned %d\n", - GetLastError()); - } - - ret = WaitForSingleObject(hThread, INFINITE); - - if (ret == WAIT_FAILED) - { - Fail("Unable to wait on child thread!\nGetLastError returned %d.\n", - GetLastError()); - } - - - if (0==CloseHandle(hThread)) - { - Trace("Could not close Thread handle\n"); - Fail ( "GetLastError returned %d\n", GetLastError()); - } -} - -/* Function doesn't do anything, just needed to interrupt the wait*/ -VOID PALAPI APCFunc_WFSOExMutexTest(ULONG_PTR dwParam) -{ -} - -/* Entry Point for child thread. */ -DWORD PALAPI WaiterProc_WFSOExMutexTest(LPVOID lpParameter) -{ - int64_t OldTimeStamp; - int64_t NewTimeStamp; - BOOL Alertable; - DWORD ret; - - Alertable = (BOOL)(SIZE_T) lpParameter; - - OldTimeStamp = minipal_hires_ticks(); - s_preWaitTimestampRecorded = true; - - ret = WaitForSingleObjectEx( hMutex_WFSOExMutexTest, - ChildThreadWaitTime, - Alertable); - - NewTimeStamp = minipal_hires_ticks(); - - if (Alertable && ret != WAIT_IO_COMPLETION) - { - Fail("Expected the interrupted wait to return WAIT_IO_COMPLETION.\n" - "Got %d\n", ret); - } - else if (!Alertable && ret != WAIT_TIMEOUT) - { - Fail("WaitForSingleObjectEx did not timeout.\n" - "Expected return of WAIT_TIMEOUT, got %d.\n", ret); - } - - ThreadWaitDelta_WFSOExMutexTest = (NewTimeStamp - OldTimeStamp) / (minipal_hires_tick_frequency() / 1000); - - return 0; -} - - - diff --git a/src/coreclr/pal/tests/palsuite/threading/WaitForSingleObject/WFSOMutexTest/WFSOMutexTest.cpp b/src/coreclr/pal/tests/palsuite/threading/WaitForSingleObject/WFSOMutexTest/WFSOMutexTest.cpp deleted file mode 100644 index 9ac6c11bc52ab6..00000000000000 --- a/src/coreclr/pal/tests/palsuite/threading/WaitForSingleObject/WFSOMutexTest/WFSOMutexTest.cpp +++ /dev/null @@ -1,183 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -/*============================================================ -** -** Source: WFSOMutexTest.c -** -** Purpose: Test for WaitForSingleObjectTest. -** Create Mutex Object -** Create Two Threads, Each Threads does WFSO for the Mutex Object -** Increments Counter -** Releases Mutex -** Test Passes if the above operations are successful -** -** -** -**=========================================================*/ - - - -#include - - -#define NUMBER_OF_WORKER_THREADS 2 - -//Declaring Variables -HANDLE hMutex_WFSOMutexTest = NULL; -unsigned int globalcounter_WFSOMutexTest =0; -int testReturnCode_WFSOMutexTest = PASS; - -//Declaring Function Prototypes -DWORD PALAPI WFSOMutexTest(LPVOID params); -void incrementCounter_WFSOMutexTest(void); - - - -PALTEST(threading_WaitForSingleObject_WFSOMutexTest_paltest_waitforsingleobject_wfsomutextest, "threading/WaitForSingleObject/WFSOMutexTest/paltest_waitforsingleobject_wfsomutextest") -{ - - //Declare local variables - int i =0; - - // 2 dimensional array to hold thread handles for each worker thread - HANDLE hThread[NUMBER_OF_WORKER_THREADS]; - DWORD dwThreadId=0; - int returnCode = 0; - - //Initialize PAL - if(0 != (PAL_Initialize(argc, argv))) - { - return ( FAIL ); - } - - //Create Mutex - hMutex_WFSOMutexTest = CreateMutex(NULL, // no security attributes - FALSE, // initially not owned - NULL); // name of mutex - - //Check for Mutex Creation - - if (hMutex_WFSOMutexTest == NULL) - { - Fail("Create Mutex Failed, GetLastError: %d\n", GetLastError()); - } - - - //Spawn 2 worker threads - for (i=0;i Date: Tue, 29 Jul 2025 21:34:44 +0000 Subject: [PATCH 45/47] Remove concept of ownership and abandonment and other vistigial components of mutexes from the CoreCLR PAL. --- src/coreclr/pal/inc/pal.h | 2 - src/coreclr/pal/src/config.h.in | 2 - src/coreclr/pal/src/configure.cmake | 224 ---------- src/coreclr/pal/src/file/file.cpp | 3 +- src/coreclr/pal/src/include/pal/corunix.hpp | 67 +-- .../pal/src/include/pal/synchobjects.hpp | 9 - src/coreclr/pal/src/map/map.cpp | 3 +- .../pal/src/synchmgr/synchcontrollers.cpp | 411 +----------------- src/coreclr/pal/src/synchmgr/synchmanager.cpp | 142 +----- src/coreclr/pal/src/synchmgr/synchmanager.hpp | 89 +--- src/coreclr/pal/src/synchmgr/wait.cpp | 23 +- src/coreclr/pal/src/synchobj/event.cpp | 6 +- src/coreclr/pal/src/synchobj/semaphore.cpp | 3 +- src/coreclr/pal/src/thread/process.cpp | 3 +- src/coreclr/pal/src/thread/thread.cpp | 17 +- .../test1/CreateSemaphore.cpp | 7 - .../test2/CreateSemaphore.cpp | 7 - .../WFSOProcessTest/WFSOProcessTest.cpp | 8 - .../WFSOThreadTest/WFSOThreadTest.cpp | 99 ++--- 19 files changed, 78 insertions(+), 1047 deletions(-) diff --git a/src/coreclr/pal/inc/pal.h b/src/coreclr/pal/inc/pal.h index 7a06fe1f079693..c5f63852289ae5 100644 --- a/src/coreclr/pal/inc/pal.h +++ b/src/coreclr/pal/inc/pal.h @@ -847,8 +847,6 @@ GetExitCodeProcess( #define MAXIMUM_WAIT_OBJECTS 64 #define WAIT_OBJECT_0 0 -#define WAIT_ABANDONED 0x00000080 -#define WAIT_ABANDONED_0 0x00000080 #define WAIT_TIMEOUT 258 #define WAIT_FAILED ((DWORD)0xFFFFFFFF) diff --git a/src/coreclr/pal/src/config.h.in b/src/coreclr/pal/src/config.h.in index 941e60e71ee62e..3338faf883ce30 100644 --- a/src/coreclr/pal/src/config.h.in +++ b/src/coreclr/pal/src/config.h.in @@ -104,8 +104,6 @@ #cmakedefine PAL_PTRACE(cmd, pid, addr, data) @PAL_PTRACE@ #cmakedefine01 SYNCHMGR_SUSPENSION_SAFE_CONDITION_SIGNALING #cmakedefine01 ERROR_FUNC_FOR_GLOB_HAS_FIXED_PARAMS -#cmakedefine01 HAVE_FULLY_FEATURED_PTHREAD_MUTEXES -#cmakedefine01 HAVE_FUNCTIONAL_PTHREAD_ROBUST_MUTEXES #cmakedefine BSD_REGS_STYLE(reg, RR, rr) @BSD_REGS_STYLE@ #cmakedefine01 HAVE_SCHED_OTHER_ASSIGNABLE #cmakedefine01 SET_SCHEDPARAM_NEEDS_PRIVS diff --git a/src/coreclr/pal/src/configure.cmake b/src/coreclr/pal/src/configure.cmake index 3d89ba2f593a13..c5814814ff446e 100644 --- a/src/coreclr/pal/src/configure.cmake +++ b/src/coreclr/pal/src/configure.cmake @@ -665,230 +665,6 @@ int main(int argc, char **argv) return 0; }" HAVE_PR_SET_PTRACER) -set(CMAKE_REQUIRED_LIBRARIES pthread) -check_cxx_source_compiles(" -#include -#include -#include - -int main() -{ - pthread_mutexattr_t mutexAttributes; - pthread_mutexattr_init(&mutexAttributes); - pthread_mutexattr_setpshared(&mutexAttributes, PTHREAD_PROCESS_SHARED); - pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_RECURSIVE); - pthread_mutexattr_setrobust(&mutexAttributes, PTHREAD_MUTEX_ROBUST); - - pthread_mutex_t mutex; - pthread_mutex_init(&mutex, &mutexAttributes); - - pthread_mutexattr_destroy(&mutexAttributes); - - struct timespec timeoutTime; - timeoutTime.tv_sec = 1; // not the right way to specify absolute time, but just checking availability of timed lock - timeoutTime.tv_nsec = 0; - pthread_mutex_timedlock(&mutex, &timeoutTime); - pthread_mutex_consistent(&mutex); - - pthread_mutex_destroy(&mutex); - - int error = EOWNERDEAD; - error = ENOTRECOVERABLE; - error = ETIMEDOUT; - error = 0; - return error; -}" HAVE_FULLY_FEATURED_PTHREAD_MUTEXES) -set(CMAKE_REQUIRED_LIBRARIES) - -if(NOT CLR_CMAKE_HOST_ARCH_ARM AND NOT CLR_CMAKE_HOST_ARCH_ARM64) - set(CMAKE_REQUIRED_LIBRARIES pthread) - check_cxx_source_runs(" - // This test case verifies the pthread process-shared robust mutex's cross-process abandon detection. The parent process starts - // a child process that locks the mutex, the process process then waits to acquire the lock, and the child process abandons the - // mutex by exiting the process while holding the lock. The parent process should then be released from its wait, be assigned - // ownership of the lock, and be notified that the mutex was abandoned. - - #include - #include - - #include - #include - #include - #include - - #include - using namespace std; - - struct Shm - { - pthread_mutex_t syncMutex; - pthread_cond_t syncCondition; - pthread_mutex_t robustMutex; - int conditionValue; - - Shm() : conditionValue(0) - { - } - } *shm; - - int GetFailTimeoutTime(struct timespec *timeoutTimeRef) - { - int getTimeResult = clock_gettime(CLOCK_REALTIME, timeoutTimeRef); - if (getTimeResult != 0) - { - struct timeval tv; - getTimeResult = gettimeofday(&tv, NULL); - if (getTimeResult != 0) - return 1; - timeoutTimeRef->tv_sec = tv.tv_sec; - timeoutTimeRef->tv_nsec = tv.tv_usec * 1000; - } - timeoutTimeRef->tv_sec += 30; - return 0; - } - - int WaitForConditionValue(int desiredConditionValue) - { - struct timespec timeoutTime; - if (GetFailTimeoutTime(&timeoutTime) != 0) - return 1; - if (pthread_mutex_timedlock(&shm->syncMutex, &timeoutTime) != 0) - return 1; - - if (shm->conditionValue != desiredConditionValue) - { - if (GetFailTimeoutTime(&timeoutTime) != 0) - return 1; - if (pthread_cond_timedwait(&shm->syncCondition, &shm->syncMutex, &timeoutTime) != 0) - return 1; - if (shm->conditionValue != desiredConditionValue) - return 1; - } - - if (pthread_mutex_unlock(&shm->syncMutex) != 0) - return 1; - return 0; - } - - int SetConditionValue(int newConditionValue) - { - struct timespec timeoutTime; - if (GetFailTimeoutTime(&timeoutTime) != 0) - return 1; - if (pthread_mutex_timedlock(&shm->syncMutex, &timeoutTime) != 0) - return 1; - - shm->conditionValue = newConditionValue; - if (pthread_cond_signal(&shm->syncCondition) != 0) - return 1; - - if (pthread_mutex_unlock(&shm->syncMutex) != 0) - return 1; - return 0; - } - - void DoTest_Child(); - - int DoTest() - { - // Map some shared memory - void *shmBuffer = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); - if (shmBuffer == MAP_FAILED) - return 1; - shm = new(shmBuffer) Shm; - - // Create sync mutex - pthread_mutexattr_t syncMutexAttributes; - if (pthread_mutexattr_init(&syncMutexAttributes) != 0) - return 1; - if (pthread_mutexattr_setpshared(&syncMutexAttributes, PTHREAD_PROCESS_SHARED) != 0) - return 1; - if (pthread_mutex_init(&shm->syncMutex, &syncMutexAttributes) != 0) - return 1; - if (pthread_mutexattr_destroy(&syncMutexAttributes) != 0) - return 1; - - // Create sync condition - pthread_condattr_t syncConditionAttributes; - if (pthread_condattr_init(&syncConditionAttributes) != 0) - return 1; - if (pthread_condattr_setpshared(&syncConditionAttributes, PTHREAD_PROCESS_SHARED) != 0) - return 1; - if (pthread_cond_init(&shm->syncCondition, &syncConditionAttributes) != 0) - return 1; - if (pthread_condattr_destroy(&syncConditionAttributes) != 0) - return 1; - - // Create the robust mutex that will be tested - pthread_mutexattr_t robustMutexAttributes; - if (pthread_mutexattr_init(&robustMutexAttributes) != 0) - return 1; - if (pthread_mutexattr_setpshared(&robustMutexAttributes, PTHREAD_PROCESS_SHARED) != 0) - return 1; - if (pthread_mutexattr_setrobust(&robustMutexAttributes, PTHREAD_MUTEX_ROBUST) != 0) - return 1; - if (pthread_mutex_init(&shm->robustMutex, &robustMutexAttributes) != 0) - return 1; - if (pthread_mutexattr_destroy(&robustMutexAttributes) != 0) - return 1; - - // Start child test process - int error = fork(); - if (error == -1) - return 1; - if (error == 0) - { - DoTest_Child(); - return -1; - } - - // Wait for child to take a lock - WaitForConditionValue(1); - - // Wait to try to take a lock. Meanwhile, child abandons the robust mutex. - struct timespec timeoutTime; - if (GetFailTimeoutTime(&timeoutTime) != 0) - return 1; - error = pthread_mutex_timedlock(&shm->robustMutex, &timeoutTime); - if (error != EOWNERDEAD) // expect to be notified that the robust mutex was abandoned - return 1; - if (pthread_mutex_consistent(&shm->robustMutex) != 0) - return 1; - - if (pthread_mutex_unlock(&shm->robustMutex) != 0) - return 1; - if (pthread_mutex_destroy(&shm->robustMutex) != 0) - return 1; - return 0; - } - - void DoTest_Child() - { - // Lock the robust mutex - struct timespec timeoutTime; - if (GetFailTimeoutTime(&timeoutTime) != 0) - return; - if (pthread_mutex_timedlock(&shm->robustMutex, &timeoutTime) != 0) - return; - - // Notify parent that robust mutex is locked - if (SetConditionValue(1) != 0) - return; - - // Wait a short period to let the parent block on waiting for a lock - sleep(1); - - // Abandon the mutex by exiting the process while holding the lock. Parent's wait should be released by EOWNERDEAD. - } - - int main() - { - int result = DoTest(); - return result >= 0 ? result : 0; - }" HAVE_FUNCTIONAL_PTHREAD_ROBUST_MUTEXES) - set(CMAKE_REQUIRED_LIBRARIES) -endif() - if(CLR_CMAKE_TARGET_APPLE) set(HAVE__NSGETENVIRON 1) set(DEADLOCK_WHEN_THREAD_IS_SUSPENDED_WHILE_BLOCKED_ON_MUTEX 1) diff --git a/src/coreclr/pal/src/file/file.cpp b/src/coreclr/pal/src/file/file.cpp index 8d0cfa99f789d3..e6ced69cd4baef 100644 --- a/src/coreclr/pal/src/file/file.cpp +++ b/src/coreclr/pal/src/file/file.cpp @@ -75,8 +75,7 @@ CObjectType CorUnix::otFile( CFileProcessLocalDataCleanupRoutine, CObjectType::UnwaitableObject, CObjectType::SignalingNotApplicable, - CObjectType::ThreadReleaseNotApplicable, - CObjectType::OwnershipNotApplicable + CObjectType::ThreadReleaseNotApplicable ); CAllowedObjectTypes CorUnix::aotFile(otiFile); diff --git a/src/coreclr/pal/src/include/pal/corunix.hpp b/src/coreclr/pal/src/include/pal/corunix.hpp index 69f4071752757f..17d83a8ac0d189 100644 --- a/src/coreclr/pal/src/include/pal/corunix.hpp +++ b/src/coreclr/pal/src/include/pal/corunix.hpp @@ -204,10 +204,6 @@ namespace CorUnix // Must be ThreadReleaseHasNoSideEffects if eSignalingSemantics is // SingleTransitionObject // - // * eOwnershipSemantics: OwnershipTracked only for mutexes, for which the - // previous two items must also ObjectCanBeUnsignaled and - // ThreadReleaseAltersSignalCount. - // class CObjectType { @@ -232,13 +228,6 @@ namespace CorUnix ThreadReleaseNotApplicable }; - enum OwnershipSemantics - { - OwnershipTracked, - NoOwner, - OwnershipNotApplicable - }; - private: // @@ -259,7 +248,6 @@ namespace CorUnix SynchronizationSupport m_eSynchronizationSupport; SignalingSemantics m_eSignalingSemantics; ThreadReleaseSemantics m_eThreadReleaseSemantics; - OwnershipSemantics m_eOwnershipSemantics; public: @@ -273,8 +261,7 @@ namespace CorUnix OBJECT_PROCESS_LOCAL_DATA_CLEANUP_ROUTINE pProcessLocalDataCleanupRoutine, SynchronizationSupport eSynchronizationSupport, SignalingSemantics eSignalingSemantics, - ThreadReleaseSemantics eThreadReleaseSemantics, - OwnershipSemantics eOwnershipSemantics + ThreadReleaseSemantics eThreadReleaseSemantics ) : m_eTypeId(eTypeId), @@ -286,8 +273,7 @@ namespace CorUnix m_pProcessLocalDataCleanupRoutine(pProcessLocalDataCleanupRoutine), m_eSynchronizationSupport(eSynchronizationSupport), m_eSignalingSemantics(eSignalingSemantics), - m_eThreadReleaseSemantics(eThreadReleaseSemantics), - m_eOwnershipSemantics(eOwnershipSemantics) + m_eThreadReleaseSemantics(eThreadReleaseSemantics) { s_rgotIdMapping[eTypeId] = this; }; @@ -398,14 +384,6 @@ namespace CorUnix { return m_eThreadReleaseSemantics; }; - - OwnershipSemantics - GetOwnershipSemantics( - void - ) - { - return m_eOwnershipSemantics; - }; }; class CAllowedObjectTypes @@ -529,36 +507,6 @@ namespace CorUnix LONG lAmountToDecrement ) = 0; - // - // The following two routines may only be used for object types - // where eOwnershipSemantics is OwnershipTracked (i.e., mutexes). - // - - // - // SetOwner is intended to be used in the implementation of - // CreateMutex when bInitialOwner is TRUE. It must be called - // before the new object instance is registered with the - // handle manager. Any other call to this method is an error. - // - - virtual - PAL_ERROR - SetOwner( - CPalThread *pNewOwningThread - ) = 0; - - // - // DecrementOwnershipCount returns an error if the object - // is unowned, or if the thread this controller is bound to - // is not the owner of the object. - // - - virtual - PAL_ERROR - DecrementOwnershipCount( - void - ) = 0; - virtual void ReleaseController( @@ -604,8 +552,7 @@ namespace CorUnix virtual PAL_ERROR CanThreadWaitWithoutBlocking( - bool *pfCanWaitWithoutBlocking, // OUT - bool *pfAbandoned + bool *pfCanWaitWithoutBlocking // OUT ) = 0; virtual @@ -915,7 +862,6 @@ namespace CorUnix { WaitSucceeded, Alerted, - MutexAbandoned, WaitTimeout, WaitFailed }; @@ -946,13 +892,6 @@ namespace CorUnix DWORD *pdwSignaledObject // OUT ) = 0; - virtual - PAL_ERROR - AbandonObjectsOwnedByThread( - CPalThread *pCallingThread, - CPalThread *pTargetThread - ) = 0; - virtual PAL_ERROR QueueUserAPC( diff --git a/src/coreclr/pal/src/include/pal/synchobjects.hpp b/src/coreclr/pal/src/include/pal/synchobjects.hpp index e984e600ad03db..2c21da9b2d3a1b 100644 --- a/src/coreclr/pal/src/include/pal/synchobjects.hpp +++ b/src/coreclr/pal/src/include/pal/synchobjects.hpp @@ -67,7 +67,6 @@ namespace CorUnix class CSynchData; typedef struct _WaitingThreadsListNode * PWaitingThreadsListNode; - typedef struct _OwnedObjectsListNode * POwnedObjectsListNode; typedef struct _ThreadApcInfoNode * PThreadApcInfoNode; typedef struct _ThreadWaitInfo @@ -157,14 +156,6 @@ namespace CorUnix PAL_ERROR RunDeferredThreadConditionSignalings(); #endif // SYNCHMGR_SUSPENSION_SAFE_CONDITION_SIGNALING - // NOTE: the following methods provide non-synchronized access to - // the list of owned objects for this thread. Any thread - // accessing this list MUST own the appropriate - // synchronization lock(s). - void AddObjectToOwnedList(POwnedObjectsListNode pooln); - void RemoveObjectFromOwnedList(POwnedObjectsListNode pooln); - POwnedObjectsListNode RemoveFirstObjectFromOwnedList(void); - // The following methods provide access to the native wait lock for // those implementations that need a lock to protect the support for // native thread blocking (e.g.: pthread conditions) diff --git a/src/coreclr/pal/src/map/map.cpp b/src/coreclr/pal/src/map/map.cpp index 8900ccd1058c01..66d50bcb4bdca2 100644 --- a/src/coreclr/pal/src/map/map.cpp +++ b/src/coreclr/pal/src/map/map.cpp @@ -134,8 +134,7 @@ CObjectType CorUnix::otFileMapping( NULL, // No process local data cleanup routine CObjectType::UnwaitableObject, CObjectType::SignalingNotApplicable, - CObjectType::ThreadReleaseNotApplicable, - CObjectType::OwnershipNotApplicable + CObjectType::ThreadReleaseNotApplicable ); CAllowedObjectTypes aotFileMapping(otiFileMapping); diff --git a/src/coreclr/pal/src/synchmgr/synchcontrollers.cpp b/src/coreclr/pal/src/synchmgr/synchcontrollers.cpp index 52f38913d69dcd..99ca5a966f970b 100644 --- a/src/coreclr/pal/src/synchmgr/synchcontrollers.cpp +++ b/src/coreclr/pal/src/synchmgr/synchcontrollers.cpp @@ -133,8 +133,7 @@ namespace CorUnix signaled) --*/ PAL_ERROR CSynchWaitController::CanThreadWaitWithoutBlocking( - bool * pfCanWaitWithoutBlocking, - bool * pfAbandoned) + bool * pfCanWaitWithoutBlocking) { VALIDATEOBJECT(m_psdSynchData); @@ -142,9 +141,8 @@ namespace CorUnix _ASSERTE(InternalGetCurrentThread() == m_pthrOwner); _ASSERTE(NULL != pfCanWaitWithoutBlocking); - _ASSERTE(NULL != pfAbandoned); - fRetVal = m_psdSynchData->CanWaiterWaitWithoutBlocking(m_pthrOwner, pfAbandoned); + fRetVal = m_psdSynchData->CanWaiterWaitWithoutBlocking(m_pthrOwner); if(!fRetVal && otiProcess == m_psdSynchData->GetObjectTypeId()) { @@ -561,117 +559,6 @@ namespace CorUnix return palErr; } - /*++ - Method: - CSynchStateController::SetOwner - - Sets the owner of the target object and initializes the ownership - count to 1 (for objects with tracked ownership). - --*/ - PAL_ERROR CSynchStateController::SetOwner(CPalThread * pNewOwningThread) - { - VALIDATEOBJECT(m_psdSynchData); - - PAL_ERROR palErr = NO_ERROR; - - _ASSERTE(InternalGetCurrentThread() == m_pthrOwner); - _ASSERTE(NULL != pNewOwningThread); - _ASSERT_MSG(CObjectType::OwnershipTracked == - m_potObjectType->GetOwnershipSemantics(), - "SetOwner called on an object without OwnershipTracked " - "semantics\n"); - - if (0 != m_psdSynchData->GetOwnershipCount()) - { - ASSERT("Ownership count should be zero at this time\n"); - palErr = ERROR_INTERNAL_ERROR; - goto SO_exit; - } - - palErr = m_psdSynchData->AssignOwnershipToThread(m_pthrOwner, - pNewOwningThread); - - _ASSERT_MSG(0 == m_psdSynchData->GetOwnershipCount() || - 0 == m_psdSynchData->GetSignalCount(), - "Conflicting values for SignalCount [%d] and " - "OwnershipCount [%d]\n", - m_psdSynchData->GetOwnershipCount(), - m_psdSynchData->GetSignalCount()); - - SO_exit: - return palErr; - } - - /*++ - Method: - CSynchStateController::DecrementOwnershipCount - - Decrements the ownership count of the target object possibly triggering - waiting threads awakening (for objects with tracked ownership). - --*/ - PAL_ERROR CSynchStateController::DecrementOwnershipCount() - { - VALIDATEOBJECT(m_psdSynchData); - - PAL_ERROR palErr = NO_ERROR; - LONG lOwnershipCount = m_psdSynchData->GetOwnershipCount(); - - _ASSERTE(InternalGetCurrentThread() == m_pthrOwner); - _ASSERT_MSG(CObjectType::OwnershipTracked == - m_potObjectType->GetOwnershipSemantics(), - "Trying to decrement ownership count on an object with " - "ownership semantics other than OwnershipTracked\n"); - _ASSERT_MSG(0 <= lOwnershipCount, - "Operation would make ownership count negative - object " - "should be owned at this time [ownership count=%d]\n", - lOwnershipCount); - - if ( (1 > lOwnershipCount) || - (m_psdSynchData->GetOwnerProcessID() != gPID) || - (m_psdSynchData->GetOwnerThread() != m_pthrOwner) ) - { - palErr = ERROR_NOT_OWNER; - goto DOC_exit; - } - - lOwnershipCount--; - m_psdSynchData->SetOwnershipCount(lOwnershipCount); - - if (0 == lOwnershipCount) - { - CPalSynchronizationManager * pSynchManager = - CPalSynchronizationManager::GetInstance(); - OwnedObjectsListNode * pooln = - m_psdSynchData->GetOwnershipListNode(); - - _ASSERT_MSG(NULL != pooln, - "Null ownership node pointer in SynchData with ownership " - "semantics\n"); - _ASSERT_MSG(m_psdSynchData == pooln->pPalObjSynchData, - "Corrupted ownership node\n"); - - // Object has been released - // Remove it from list of owned objs for current thread - m_pthrOwner->synchronizationInfo.RemoveObjectFromOwnedList(pooln); - - // Release SynchData reference count implied by the ownership - // list node - m_psdSynchData->Release(m_pthrOwner); - - // Return node to the cache - pSynchManager->CacheAddOwnedObjsListNode(m_pthrOwner, pooln); - - // Reset ownership - m_psdSynchData->ResetOwnership(); - - // Signal it and trigger waiter thread awakening - m_psdSynchData->Signal(m_pthrOwner, 1); - } - - DOC_exit: - return palErr; - } - /*++ Method: CSynchStateController::ReleaseController @@ -786,11 +673,8 @@ namespace CorUnix CObjectType::SignalingSemantics ssSignalingSemantics = potObjectType->GetSignalingSemantics(); #endif // _DEBUG - CObjectType::OwnershipSemantics osOwnershipSemantics = - potObjectType->GetOwnershipSemantics(); CObjectType::ThreadReleaseSemantics trsThreadReleaseSemantics = potObjectType->GetThreadReleaseSemantics(); - bool fReenteringObjWithOwnership = false; _ASSERT_MSG(CObjectType::SignalingNotApplicable != ssSignalingSemantics, "Signaling not applicable"); @@ -798,59 +682,24 @@ namespace CorUnix trsThreadReleaseSemantics, "Thread releasing not applicable"); _ASSERT_MSG(CObjectType::SingleTransitionObject != ssSignalingSemantics || - (CObjectType::ThreadReleaseHasNoSideEffects == - trsThreadReleaseSemantics && - CObjectType::NoOwner == osOwnershipSemantics), + CObjectType::ThreadReleaseHasNoSideEffects == trsThreadReleaseSemantics, "Conflicting object synchronization attributes " - "[SignalingSemantics=%u OwnershipSemantics=%u " - "ThreadReleaseSemantics=%u]\n", ssSignalingSemantics, - osOwnershipSemantics, trsThreadReleaseSemantics); + "[SignalingSemantics=%u " + "ThreadReleaseSemantics=%u]\n", + ssSignalingSemantics, + trsThreadReleaseSemantics); - if (CObjectType::OwnershipTracked == osOwnershipSemantics && - 0 < GetOwnershipCount()) - { - // We are rentering an object with ownership: we need to skip - // the object unsignaling - fReenteringObjWithOwnership = true; - } - - if (!fReenteringObjWithOwnership && - CObjectType::ThreadReleaseAltersSignalCount == trsThreadReleaseSemantics) + if (CObjectType::ThreadReleaseAltersSignalCount == trsThreadReleaseSemantics) { _ASSERT_MSG(0 < GetSignalCount(), "Internal error: operation would make signal count " "negative - object should be signaled at this time " "[signal count=%d]", GetSignalCount()); - _ASSERT_MSG(CObjectType::OwnershipTracked != osOwnershipSemantics || - 1 == GetSignalCount(), - "Ownable objects cannot have signal count greater " - "than zero [current SignalCount=%d]\n", - GetSignalCount()); // Unsignal the object DecrementSignalCount(); } - if (CObjectType::OwnershipTracked == osOwnershipSemantics) - { - _ASSERT_MSG(0 == GetOwnershipCount() || 0 == GetSignalCount(), - "OwnershipCount and SignalCount with conflicting " - "values\n"); - - // Take ownership or increment ownership count. - // We do this after the object unsignaling to minimize possibilities - // of having both SignalCount and OwnershipCount greater than zero - // (see comment in AssignOwnershipToThread) - palErr = AssignOwnershipToThread(pthrCurrent, pthrTarget); - - if (NO_ERROR != palErr) - { - ERROR("AssignOwnershipToThread failed with error %u; " - "ownership data on object with SynchData {p=%p} " - "may be corrupted\n", palErr, this); - } - } - #ifdef SYNCH_STATISTICS if (NO_ERROR == palErr) { @@ -873,45 +722,11 @@ namespace CorUnix object is local, both local and shared one if the object is shared). --*/ bool CSynchData::CanWaiterWaitWithoutBlocking( - CPalThread * pWaiterThread, - bool * pfAbandoned) + CPalThread * pWaiterThread) { VALIDATEOBJECT(this); - bool fRetVal = (0 < GetSignalCount()); - bool fAbandoned = false; - bool fOwnershipTracked = (CObjectType::OwnershipTracked == - GetObjectType()->GetOwnershipSemantics()); - if (fRetVal) - { - // Object signaled: thread can wait without blocking - if (fOwnershipTracked) - { - fAbandoned = IsAbandoned(); - } - - goto CWWWB_exit; - } - - // Object not signaled: thread can wait without blocking only if the - // object is an ownable one, and it is owned by the current thread - if (fOwnershipTracked) - { - _ASSERT_MSG(0 < GetSignalCount() || 0 < GetOwnershipCount(), - "Objects with ownership must be either signaled or " - "owned by a thread\n"); - - if ((GetOwnerProcessID() == gPID) && - (GetOwnerThread() == pWaiterThread) ) - { - fRetVal = true; - goto CWWWB_exit; - } - } - - CWWWB_exit: - *pfAbandoned = fAbandoned; - return fRetVal; + return 0 < GetSignalCount(); } /*++ @@ -958,12 +773,6 @@ namespace CorUnix } } - _ASSERT_MSG(CObjectType::OwnershipTracked != - GetObjectType()->GetOwnershipSemantics() || - 0 == GetOwnershipCount() || 0 == GetSignalCount(), - "Conflicting values for SignalCount [%d] and " - "OwnershipCount [%d]\n", - GetOwnershipCount(), GetSignalCount()); return; } @@ -1038,31 +847,6 @@ namespace CorUnix dwObjIdx = pwtlnItem->dwObjIndex; ThreadWaitInfo * ptwiWaitInfo = pwtlnItem->ptwiWaitInfo; - bool fAbandoned = false; - - if (CObjectType::OwnershipTracked == - GetObjectType()->GetOwnershipSemantics()) - { - // Get the abandoned status before resetting it by - // assigning ownership to target thread - fAbandoned = IsAbandoned(); - - // Assign ownership to target thread - // Note: This will cause both ownership count and - // signal count to be greater than zero at the - // same time; the signal count will be anyway - // decremented immediately by the caller - // CsynchData::Signal - palErr = AssignOwnershipToThread(pthrCurrent, - ptwiWaitInfo->pthrOwner); - if (NO_ERROR != palErr) - { - ERROR("Synch Worker: AssignOwnershipToThread " - "failed with error %u; ownership data on " - "object with SynchData %p may be " - "corrupted\n", palErr, this); - } - } if (fWaitAll) { @@ -1089,7 +873,7 @@ namespace CorUnix palErr = CPalSynchronizationManager::WakeUpLocalThread( pthrCurrent, ptwiWaitInfo->pthrOwner, - fAbandoned ? MutexAbandoned : WaitSucceeded, + WaitSucceeded, dwObjIdx); if (NO_ERROR != palErr) @@ -1172,26 +956,6 @@ namespace CorUnix dwObjIdx = pwtlnItem->dwObjIndex; ThreadWaitInfo * ptwiWaitInfo = pwtlnItem->ptwiWaitInfo; - bool fAbandoned = false; - - if (CObjectType::OwnershipTracked == - GetObjectType()->GetOwnershipSemantics()) - { - // Get the abandoned status before resetting it by - // assigning ownership to target thread - fAbandoned = IsAbandoned(); - - // Assign ownership to target thread - palErr = AssignOwnershipToThread(pthrCurrent, - ptwiWaitInfo->pthrOwner); - if (NO_ERROR != palErr) - { - ERROR("Synch Worker: AssignOwnershipToThread " - "failed with error %u; ownership data on " - "object with SynchData %p may be " - "corrupted\n", palErr, this); - } - } if (fWaitAll) { @@ -1218,7 +982,7 @@ namespace CorUnix palErr = CPalSynchronizationManager::WakeUpLocalThread( pthrCurrent, ptwiWaitInfo->pthrOwner, - fAbandoned ? MutexAbandoned : WaitSucceeded, + WaitSucceeded, dwObjIdx); if (NO_ERROR != palErr) @@ -1263,7 +1027,7 @@ namespace CorUnix WaitCompletionState CSynchData::IsRestOfWaitAllSatisfied( WaitingThreadsListNode * pwtlnNode) { - int iSignaledOrOwnedObjCount = 0; + int iSignaledObjCount = 0; int iTgtCount = 0; int i; WaitCompletionState wcsWaitCompletionState = WaitIsNotSatisfied; @@ -1290,7 +1054,6 @@ namespace CorUnix { WaitingThreadsListNode * pwtlnItem = ptwiWaitInfo->rgpWTLNodes[i]; bool fRetVal; - bool fIsAbandoned; VALIDATEOBJECT(pwtlnItem); @@ -1307,17 +1070,16 @@ namespace CorUnix // The target object (the one related to pwtlnNode) is counted as // signaled/owned without checking it (also if it is not, as // it normally happens when this method is called) - iSignaledOrOwnedObjCount++; + iSignaledObjCount++; continue; } fRetVal = psdSynchDataItem->CanWaiterWaitWithoutBlocking( - ptwiWaitInfo->pthrOwner, - &fIsAbandoned); + ptwiWaitInfo->pthrOwner); if (fRetVal) { - iSignaledOrOwnedObjCount++; + iSignaledObjCount++; } else { @@ -1325,7 +1087,7 @@ namespace CorUnix } } - if (iSignaledOrOwnedObjCount < iTgtCount) + if (iSignaledObjCount < iTgtCount) { wcsWaitCompletionState = WaitIsNotSatisfied; } @@ -1339,145 +1101,6 @@ namespace CorUnix return wcsWaitCompletionState; } - - /*++ - Method: - CSynchData::SetOwner - - Blindly sets the thread whose CPalThread is passed as argument, as the - owner of the current object. - WARNING: this method discards any previous ownership data and does not - update the list of the object owned by the owner thread. - - Note: this method must be called while holding the appropriate - synchronization locks (the local process synch lock if the target - object is local, both local and shared one if the object is shared). - --*/ - void CSynchData::SetOwner(CPalThread * pOwnerThread) - { - VALIDATEOBJECT(this); - - m_dwOwnerPid = gPID; - m_dwOwnerTid = pOwnerThread->GetThreadId(); - m_pOwnerThread = pOwnerThread; - } - - /*++ - Method: - CSynchData::ResetOwnership - - Resets current object's ownership data - - Note: this method must be called while holding the appropriate - synchronization locks (the local process synch lock if the target - object is local, both local and shared one if the object is shared). - --*/ - void CSynchData::ResetOwnership() - { - VALIDATEOBJECT(this); - - m_lOwnershipCount = 0; - m_dwOwnerPid = 0; - m_dwOwnerTid = 0; - m_pOwnerThread = NULL; - m_poolnOwnedObjectListNode = NULL; - } - - /*++ - Method: - CSynchData::AssignOwnershipToThread - - Assigns thw ownership of the current object to the target thread, performing - all the operations neede to mantain the correct status of ownership data, - also handling recursive object ownership acquisition - - Note: this method must be called while holding the appropriate - synchronization locks (the local process synch lock if the target - object is local, both local and shared one if the object is shared). - --*/ - PAL_ERROR CSynchData::AssignOwnershipToThread( - CPalThread * pthrCurrent, - CPalThread * pthrTarget) - { - // Note: when this method is called by ReleaseFirstWaiter there is - // a small time window in which both SignalCount and - // OwnershipCount can be greater than zero (which normally - // is illegal). Anyway that is fine since ReleaseFirstWaiter - // will restore the value right after, and such situation - // takes place while holding synchroniztion locks, so no - // other thread/process can access the object. - - PAL_ERROR palErr = NO_ERROR; - - _ASSERT_MSG(CObjectType::OwnershipTracked == - GetObjectType()->GetOwnershipSemantics(), - "AssignOwnershipToThread called on a non-ownable " - "CSynchData [this=%p OwnershipSemantics=%u]\n", this, - GetObjectType()->GetOwnershipSemantics()); - - - if (0 < m_lOwnershipCount) - { - // - // Object already owned, incrementing ownership count - // - _ASSERT_MSG(0 == GetSignalCount(), - "Conflicting OwnershipCount and SignalCount values\n"); - - _ASSERT_MSG(pthrTarget == m_pOwnerThread && gPID == m_dwOwnerPid, - "Attempting to assign ownership of CSynchData %p to " - "thread {pid=%#x tid=%#x} while it is currently owned " - "by thread {pid=%#x tid=%#x}\n", this, - gPID, pthrTarget->GetThreadId(), - m_dwOwnerPid, m_pOwnerThread->GetThreadId()); - - m_lOwnershipCount++; - - TRACE("Incrementing ownership count for object with " - "SynchData %p owned by thread %#x [new count=%d]\n", - this, pthrTarget->GetThreadId(), m_lOwnershipCount); - } - else - { - // - // Acquiring currently not owned object - // - CPalSynchronizationManager * pSynchManager = - CPalSynchronizationManager::GetInstance(); - OwnedObjectsListNode * pooln; - - pooln = pSynchManager->CacheGetOwnedObjsListNode(pthrCurrent); - if (NULL == pooln) - { - ERROR("Out of memory while acquiring mutex ownership"); - // In this case we bail out. It will result in no - // thread being awakend, which may cause deadlock, - // but it is anyway better than corrupting the - // ownership list - palErr = ERROR_NOT_ENOUGH_MEMORY; - goto AOTT_exit; - } - - TRACE("Assigning ownable object with SynchData %p to " - "thread %#x\n", - this, pthrTarget->GetThreadId()); - - // Set ownership data - SetOwner(pthrTarget); - SetOwnershipListNode(pooln); - SetOwnershipCount(1); - SetAbandoned(false); - - // Add object to list of owned objs for current thread - pooln->pPalObjSynchData = this; - AddRef(); - pthrTarget->synchronizationInfo.AddObjectToOwnedList(pooln); - } - - AOTT_exit: - return palErr; - } - /*++ Method: CSynchData::WaiterEnqueue diff --git a/src/coreclr/pal/src/synchmgr/synchmanager.cpp b/src/coreclr/pal/src/synchmgr/synchmanager.cpp index a2fa13c742cedd..4cf80dd3aa6a1e 100644 --- a/src/coreclr/pal/src/synchmgr/synchmanager.cpp +++ b/src/coreclr/pal/src/synchmgr/synchmanager.cpp @@ -159,8 +159,7 @@ namespace CorUnix m_cacheSHRSynchData(SynchDataCacheMaxSize), m_cacheWTListNodes(WTListNodeCacheMaxSize), m_cacheSHRWTListNodes(WTListNodeCacheMaxSize), - m_cacheThreadApcInfoNodes(ApcInfoNodeCacheMaxSize), - m_cacheOwnedObjectsListNodes(OwnedObjectsListCacheMaxSize) + m_cacheThreadApcInfoNodes(ApcInfoNodeCacheMaxSize) { #if HAVE_KQUEUE && !HAVE_BROKEN_FIFO_KEVENT m_iKQueue = -1; @@ -396,7 +395,6 @@ namespace CorUnix break; } case WaitSucceeded: - case MutexAbandoned: *pdwSignaledObject = dwSigObjIdx; break; default: @@ -533,98 +531,6 @@ namespace CorUnix return palErr; } - /*++ - Method: - CPalSynchronizationManager::AbandonObjectsOwnedByThread - - This method is called by a thread at thread-exit time to abandon - any currently owned waitable object (mutexes). If pthrTarget is - different from pthrCurrent, AbandonObjectsOwnedByThread assumes - to be called whether by TerminateThread or at shutdown time. See - comments below for more details - --*/ - PAL_ERROR CPalSynchronizationManager::AbandonObjectsOwnedByThread( - CPalThread * pthrCurrent, - CPalThread * pthrTarget) - { - PAL_ERROR palErr = NO_ERROR; - OwnedObjectsListNode * poolnItem; - CThreadSynchronizationInfo * pSynchInfo = &pthrTarget->synchronizationInfo; - CPalSynchronizationManager * pSynchManager = GetInstance(); - - // Local lock - AcquireLocalSynchLock(pthrCurrent); - - // Abandon owned objects - while (NULL != (poolnItem = pSynchInfo->RemoveFirstObjectFromOwnedList())) - { - CSynchData * psdSynchData = poolnItem->pPalObjSynchData; - - _ASSERT_MSG(NULL != psdSynchData, - "NULL psdSynchData pointer in ownership list node\n"); - - VALIDATEOBJECT(psdSynchData); - - TRACE("Abandoning object with SynchData at %p\n", psdSynchData); - - // Reset ownership data - psdSynchData->ResetOwnership(); - - // Set abandoned status; in case there is a thread to be released: - // - if the thread is local, ReleaseFirstWaiter will reset the - // abandoned status - // - if the thread is remote, the remote worker thread will use - // the value and reset it - psdSynchData->SetAbandoned(true); - - // Signal the object and trigger thread awakening - psdSynchData->Signal(pthrCurrent, 1); - - // Release reference to SynchData - psdSynchData->Release(pthrCurrent); - - // Return node to the cache - pSynchManager->m_cacheOwnedObjectsListNodes.Add(pthrCurrent, poolnItem); - } - - if (pthrTarget != pthrCurrent) - { - // If the target thead is not the current one, we are being called - // at shutdown time, right before the target thread is suspended, - // or anyway the target thread is being terminated. - // In this case we switch its wait state to TWS_EARLYDEATH so that, - // if the thread is currently waiting/sleeping and it wakes up - // before shutdown code manage to suspend it, it will be rerouted - // to ThreadPrepareForShutdown (that will be done without holding - // any internal lock, in a way to accommodate shutdown time thread - // suspension). - // At this time we also unregister the wait, so no dummy nodes are - // left around on waiting objects. - // The TWS_EARLYDEATH wait-state will also prevent the thread from - // successfully registering for a possible new wait in the same - // time window. - LONG lTWState; - DWORD * pdwWaitState; - - pdwWaitState = SharedIDToTypePointer(DWORD, pthrTarget->synchronizationInfo.m_shridWaitAwakened); - lTWState = InterlockedExchange((LONG *)pdwWaitState, TWS_EARLYDEATH); - - if (( ((LONG)TWS_WAITING == lTWState) || ((LONG)TWS_ALERTABLE == lTWState) ) && - (0 < pSynchInfo->m_twiWaitInfo.lObjCount)) - { - // Unregister the wait - UnRegisterWait(pthrCurrent, &pSynchInfo->m_twiWaitInfo); - } - } - - // Unlock - ReleaseLocalSynchLock(pthrCurrent); - - DiscardAllPendingAPCs(pthrCurrent, pthrTarget); - - return palErr; - } - /*++ Method: CPalSynchronizationManager::GetSynchWaitControllersForObjects @@ -3159,52 +3065,6 @@ namespace CorUnix return palErr; } - - /*++ - Method: - CThreadSynchronizationInfo::AddObjectToOwnedList - - Adds an object to the list of currently owned objects. - --*/ - void CThreadSynchronizationInfo::AddObjectToOwnedList(POwnedObjectsListNode pooln) - { - InsertTailList(&m_leOwnedObjsList, &pooln->Link); - } - - /*++ - Method: - CThreadSynchronizationInfo::RemoveObjectFromOwnedList - - Removes an object from the list of currently owned objects. - --*/ - void CThreadSynchronizationInfo::RemoveObjectFromOwnedList(POwnedObjectsListNode pooln) - { - RemoveEntryList(&pooln->Link); - } - - /*++ - Method: - CThreadSynchronizationInfo::RemoveFirstObjectFromOwnedList - - Removes the first object from the list of currently owned objects. - --*/ - POwnedObjectsListNode CThreadSynchronizationInfo::RemoveFirstObjectFromOwnedList() - { - OwnedObjectsListNode * poolnItem; - - if (IsListEmpty(&m_leOwnedObjsList)) - { - poolnItem = NULL; - } - else - { - PLIST_ENTRY pLink = RemoveHeadList(&m_leOwnedObjsList); - poolnItem = CONTAINING_RECORD(pLink, OwnedObjectsListNode, Link); - } - - return poolnItem; - } - #if SYNCHMGR_SUSPENSION_SAFE_CONDITION_SIGNALING /*++ diff --git a/src/coreclr/pal/src/synchmgr/synchmanager.hpp b/src/coreclr/pal/src/synchmgr/synchmanager.hpp index 7d86ab6fb12d4d..f1bb0df602401d 100644 --- a/src/coreclr/pal/src/synchmgr/synchmanager.hpp +++ b/src/coreclr/pal/src/synchmgr/synchmanager.hpp @@ -116,12 +116,6 @@ namespace CorUnix CPalThread * pthrTarget; } DeferredSignalingListNode; - typedef struct _OwnedObjectsListNode - { - LIST_ENTRY Link; - CSynchData * pPalObjSynchData; - } OwnedObjectsListNode; - typedef struct _ThreadApcInfoNode { struct _ThreadApcInfoNode * pNext; @@ -147,15 +141,6 @@ namespace CorUnix LONG m_lRefCount; LONG m_lSignalCount; - // Ownership data - LONG m_lOwnershipCount; - DWORD m_dwOwnerPid; - DWORD m_dwOwnerTid; // used only by remote processes - // (thread ids may be recycled) - CPalThread * m_pOwnerThread; // valid only on the target process - OwnedObjectsListNode * m_poolnOwnedObjectListNode; - bool m_fAbandoned; - #ifdef SYNCH_STATISTICS ULONG m_lStatWaitCount; ULONG m_lStatContentionCount; @@ -164,9 +149,7 @@ namespace CorUnix public: CSynchData() : m_ulcWaitingThreads(0), m_lRefCount(1), - m_lSignalCount(0), m_lOwnershipCount(0), m_dwOwnerPid(0), - m_dwOwnerTid(0), m_pOwnerThread(NULL), - m_poolnOwnedObjectListNode(NULL), m_fAbandoned(false) + m_lSignalCount(0) { // m_ptrWTLHead, m_ptrWTLTail // and m_otiObjectTypeId are initialized by @@ -190,8 +173,7 @@ namespace CorUnix LONG Release(CPalThread * pthrCurrent); bool CanWaiterWaitWithoutBlocking( - CPalThread * pWaiterThread, - bool * pfAbandoned); + CPalThread * pWaiterThread); PAL_ERROR ReleaseWaiterWithoutBlocking( CPalThread * pthrCurrent, @@ -248,48 +230,6 @@ namespace CorUnix return --m_lSignalCount; } - // Object ownership accessor methods - void SetOwner(CPalThread * pOwnerThread); - void ResetOwnership(void); - PAL_ERROR AssignOwnershipToThread( - CPalThread * pthrCurrent, - CPalThread * pthrTarget); - DWORD GetOwnerProcessID(void) - { - return m_dwOwnerPid; - } - DWORD GetOwnerThreadID(void) - { - return m_dwOwnerTid; - } - CPalThread * GetOwnerThread(void) - { - return m_pOwnerThread; - } - OwnedObjectsListNode * GetOwnershipListNode(void) - { - return m_poolnOwnedObjectListNode; - } - void SetOwnershipListNode(OwnedObjectsListNode * pooln) - { - m_poolnOwnedObjectListNode = pooln; - } - - // Object ownership count accessor methods - LONG GetOwnershipCount(void) - { - return m_lOwnershipCount; - } - void SetOwnershipCount(LONG lOwnershipCount) - { - m_lOwnershipCount = lOwnershipCount; - } - - // Object abandoned flag accessor methods - void SetAbandoned(bool fAbandoned) - { m_fAbandoned = fAbandoned; } - bool IsAbandoned(void) { return m_fAbandoned; } - void IncrementWaitingThreadCount(void) { m_ulcWaitingThreads += 1; @@ -419,8 +359,7 @@ namespace CorUnix // ISynchWaitController methods // virtual PAL_ERROR CanThreadWaitWithoutBlocking( - bool * pfCanWaitWithoutBlocking, - bool * pfAbandoned); + bool * pfCanWaitWithoutBlocking); virtual PAL_ERROR ReleaseWaitingThreadWithoutBlocking(void); @@ -452,8 +391,6 @@ namespace CorUnix virtual PAL_ERROR SetSignalCount(LONG lNewCount); virtual PAL_ERROR IncrementSignalCount(LONG lAmountToIncrement); virtual PAL_ERROR DecrementSignalCount(LONG lAmountToDecrement); - virtual PAL_ERROR SetOwner(CPalThread *pNewOwningThread); - virtual PAL_ERROR DecrementOwnershipCount(void); virtual void ReleaseController(void); }; @@ -470,7 +407,6 @@ namespace CorUnix typedef CSynchCache CWaitingThreadsListNodeCache; typedef CSHRSynchCache CSHRWaitingThreadsListNodeCache; typedef CSynchCache CThreadApcInfoNodeCache; - typedef CSynchCache COwnedObjectsListNodeCache; private: // types @@ -510,7 +446,6 @@ namespace CorUnix static const int SynchDataCacheMaxSize = 256; static const int WTListNodeCacheMaxSize = 256; static const int ApcInfoNodeCacheMaxSize = 32; - static const int OwnedObjectsListCacheMaxSize = 16; static const int MaxWorkerConsecutiveEintrs = 128; static const int MaxConsecutiveEagains = 128; static const int WorkerThreadProcMonitoringTimeout = 250; // ms @@ -548,7 +483,6 @@ namespace CorUnix CWaitingThreadsListNodeCache m_cacheWTListNodes; CSHRWaitingThreadsListNodeCache m_cacheSHRWTListNodes; CThreadApcInfoNodeCache m_cacheThreadApcInfoNodes; - COwnedObjectsListNodeCache m_cacheOwnedObjectsListNodes; // static methods static PAL_ERROR Initialize(); @@ -715,19 +649,6 @@ namespace CorUnix m_cacheThreadApcInfoNodes.Add(pthrCurrent, pNode); } - OwnedObjectsListNode * CacheGetOwnedObjsListNode( - CPalThread * pthrCurrent) - { - return m_cacheOwnedObjectsListNodes.Get(pthrCurrent); - } - void CacheAddOwnedObjsListNode( - CPalThread * pthrCurrent, - OwnedObjectsListNode * pNode) - { - m_cacheOwnedObjectsListNodes.Add(pthrCurrent, pNode); - } - - // // IPalSynchronizationManager methods // @@ -739,10 +660,6 @@ namespace CorUnix ThreadWakeupReason *ptwrWakeupReason, DWORD *pdwSignaledObject); - virtual PAL_ERROR AbandonObjectsOwnedByThread( - CPalThread *pthrCurrent, - CPalThread *pthrTarget); - virtual PAL_ERROR GetSynchWaitControllersForObjects( CPalThread *pthrCurrent, IPalObject *rgObjects[], diff --git a/src/coreclr/pal/src/synchmgr/wait.cpp b/src/coreclr/pal/src/synchmgr/wait.cpp index 781cf1bce3a34e..51a9da87483798 100644 --- a/src/coreclr/pal/src/synchmgr/wait.cpp +++ b/src/coreclr/pal/src/synchmgr/wait.cpp @@ -328,7 +328,6 @@ DWORD CorUnix::InternalWaitForMultipleObjectsEx( PAL_ERROR palErr = NO_ERROR; int i, iSignaledObjCount, iSignaledObjIndex = -1; bool fWAll = (bool)bWaitAll, fNeedToBlock = false; - bool fAbandoned = false; WaitType wtWaitType; IPalObject * pIPalObjStackArray[MAXIMUM_STACK_WAITOBJ_ARRAY_SIZE] = { NULL }; @@ -444,8 +443,8 @@ DWORD CorUnix::InternalWaitForMultipleObjectsEx( iSignaledObjIndex = -1; for (i=0;i<(int)nCount;i++) { - bool fValue, fWaitObjectAbandoned = false; - palErr = ppISyncWaitCtrlrs[i]->CanThreadWaitWithoutBlocking(&fValue, &fWaitObjectAbandoned); + bool fValue; + palErr = ppISyncWaitCtrlrs[i]->CanThreadWaitWithoutBlocking(&fValue); if (NO_ERROR != palErr) { ERROR("ISynchWaitController::CanThreadWaitWithoutBlocking() failed for " @@ -453,10 +452,6 @@ DWORD CorUnix::InternalWaitForMultipleObjectsEx( pThread->SetLastError(ERROR_INTERNAL_ERROR); goto WFMOExIntReleaseControllers; } - if (fWaitObjectAbandoned) - { - fAbandoned = true; - } if (fValue) { iSignaledObjCount++; @@ -505,7 +500,7 @@ DWORD CorUnix::InternalWaitForMultipleObjectsEx( } } - dwRet = (fAbandoned ? WAIT_ABANDONED_0 : WAIT_OBJECT_0); + dwRet = WAIT_OBJECT_0; } else if (0 == dwMilliseconds) { @@ -571,9 +566,6 @@ DWORD CorUnix::InternalWaitForMultipleObjectsEx( case WaitSucceeded: dwRet = WAIT_OBJECT_0; // offset added later break; - case MutexAbandoned: - dwRet = WAIT_ABANDONED_0; // offset added later - break; case WaitTimeout: dwRet = WAIT_TIMEOUT; break; @@ -595,14 +587,14 @@ DWORD CorUnix::InternalWaitForMultipleObjectsEx( } } - if (!fWAll && ((WAIT_OBJECT_0 == dwRet) || (WAIT_ABANDONED_0 == dwRet))) + if (!fWAll && (WAIT_OBJECT_0 == dwRet)) { _ASSERT_MSG(0 <= iSignaledObjIndex, - "Failed to identify signaled/abandoned object\n"); + "Failed to identify signaled object\n"); _ASSERT_MSG(iSignaledObjIndex >= 0 && nCount > static_cast(iSignaledObjIndex), "SignaledObjIndex object out of range " "[index=%d obj_count=%u\n", - iSignaledObjCount, nCount); + iSignaledObjIndex, nCount); if (iSignaledObjIndex < 0) { @@ -784,9 +776,6 @@ DWORD CorUnix::InternalSleepEx ( palErr = g_pSynchronizationManager->DispatchPendingAPCs(pThread); _ASSERT_MSG(NO_ERROR == palErr, "Awakened for APC, but no APC is pending\n"); - break; - case MutexAbandoned: - ASSERT("Thread %p awakened with reason=MutexAbandoned from a SleepEx\n", pThread); break; case WaitFailed: default: diff --git a/src/coreclr/pal/src/synchobj/event.cpp b/src/coreclr/pal/src/synchobj/event.cpp index 619a55f28a4595..c59ebf52a05946 100644 --- a/src/coreclr/pal/src/synchobj/event.cpp +++ b/src/coreclr/pal/src/synchobj/event.cpp @@ -39,8 +39,7 @@ CObjectType CorUnix::otManualResetEvent( NULL, // No process local data cleanup routine CObjectType::WaitableObject, CObjectType::ObjectCanBeUnsignaled, - CObjectType::ThreadReleaseHasNoSideEffects, - CObjectType::NoOwner + CObjectType::ThreadReleaseHasNoSideEffects ); CObjectType CorUnix::otAutoResetEvent( @@ -53,8 +52,7 @@ CObjectType CorUnix::otAutoResetEvent( NULL, // No process local data cleanup routine CObjectType::WaitableObject, CObjectType::ObjectCanBeUnsignaled, - CObjectType::ThreadReleaseAltersSignalCount, - CObjectType::NoOwner + CObjectType::ThreadReleaseAltersSignalCount ); PalObjectTypeId rgEventIds[] = {otiManualResetEvent, otiAutoResetEvent}; diff --git a/src/coreclr/pal/src/synchobj/semaphore.cpp b/src/coreclr/pal/src/synchobj/semaphore.cpp index 3ea6fd487691ba..c16cb9a4677d6b 100644 --- a/src/coreclr/pal/src/synchobj/semaphore.cpp +++ b/src/coreclr/pal/src/synchobj/semaphore.cpp @@ -39,8 +39,7 @@ CObjectType CorUnix::otSemaphore( NULL, // No process local data cleanup routine CObjectType::WaitableObject, CObjectType::ObjectCanBeUnsignaled, - CObjectType::ThreadReleaseAltersSignalCount, - CObjectType::NoOwner + CObjectType::ThreadReleaseAltersSignalCount ); CAllowedObjectTypes aotSempahore(otiSemaphore); diff --git a/src/coreclr/pal/src/thread/process.cpp b/src/coreclr/pal/src/thread/process.cpp index 057540776a61d5..c73b3c8ab6b1e8 100644 --- a/src/coreclr/pal/src/thread/process.cpp +++ b/src/coreclr/pal/src/thread/process.cpp @@ -128,8 +128,7 @@ CObjectType CorUnix::otProcess( NULL, // No process local data cleanup routine CObjectType::WaitableObject, CObjectType::SingleTransitionObject, - CObjectType::ThreadReleaseHasNoSideEffects, - CObjectType::NoOwner + CObjectType::ThreadReleaseHasNoSideEffects ); // diff --git a/src/coreclr/pal/src/thread/thread.cpp b/src/coreclr/pal/src/thread/thread.cpp index fbe9d8501d03f1..7acf3439735b6e 100644 --- a/src/coreclr/pal/src/thread/thread.cpp +++ b/src/coreclr/pal/src/thread/thread.cpp @@ -103,8 +103,7 @@ CObjectType CorUnix::otThread( NULL, // No process local data cleanup routine CObjectType::WaitableObject, CObjectType::SingleTransitionObject, - CObjectType::ThreadReleaseHasNoSideEffects, - CObjectType::NoOwner + CObjectType::ThreadReleaseHasNoSideEffects ); CAllowedObjectTypes aotThread(otiThread); @@ -796,20 +795,6 @@ CorUnix::InternalEndCurrentThread( PAL_ERROR palError = NO_ERROR; ISynchStateController *pSynchStateController = NULL; - // - // Abandon any objects owned by this thread - // - - palError = g_pSynchronizationManager->AbandonObjectsOwnedByThread( - pThread, - pThread - ); - - if (NO_ERROR != palError) - { - ERROR("Failure abandoning owned objects"); - } - // // Need to synchronize setting the thread state to TS_DONE since // this is checked for in InternalSuspendThreadFromData. diff --git a/src/coreclr/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test1/CreateSemaphore.cpp b/src/coreclr/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test1/CreateSemaphore.cpp index 8650b2140a2ada..ba98229594ed0a 100644 --- a/src/coreclr/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test1/CreateSemaphore.cpp +++ b/src/coreclr/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test1/CreateSemaphore.cpp @@ -95,13 +95,6 @@ down(HANDLE hSemaphore) * semaphore. */ break; - case WAIT_ABANDONED: /* - * Object was mutex object whose owning - * thread has terminated. Shouldn't occur. - */ - Fail("WaitForSingleObject call returned 'WAIT_ABANDONED'.\n" - "Failing Test.\n"); - break; case WAIT_FAILED: /* WaitForSingleObject function failed */ Fail("WaitForSingleObject call returned 'WAIT_FAILED'.\n" "GetLastError returned %d\nFailing Test.\n",GetLastError()); diff --git a/src/coreclr/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test2/CreateSemaphore.cpp b/src/coreclr/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test2/CreateSemaphore.cpp index 85c10482ccc6c0..f5601c4f782686 100644 --- a/src/coreclr/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test2/CreateSemaphore.cpp +++ b/src/coreclr/pal/tests/palsuite/threading/CreateSemaphoreW_ReleaseSemaphore/test2/CreateSemaphore.cpp @@ -95,13 +95,6 @@ down_CreateSemaphoreW_test2(HANDLE hSemaphore) * semaphore. */ break; - case WAIT_ABANDONED: /* - * Object was mutex object whose owning - * thread has terminated. Shouldn't occur. - */ - Fail("WaitForSingleObject call returned 'WAIT_ABANDONED'.\n" - "Failing Test.\n"); - break; case WAIT_FAILED: /* WaitForSingleObject function failed */ Fail("WaitForSingleObject call returned 'WAIT_FAILED'.\n" "GetLastError returned %d\nFailing Test.\n",GetLastError()); diff --git a/src/coreclr/pal/tests/palsuite/threading/WaitForSingleObject/WFSOProcessTest/WFSOProcessTest.cpp b/src/coreclr/pal/tests/palsuite/threading/WaitForSingleObject/WFSOProcessTest/WFSOProcessTest.cpp index dae1fab6f3458a..f850973a4cdea2 100644 --- a/src/coreclr/pal/tests/palsuite/threading/WaitForSingleObject/WFSOProcessTest/WFSOProcessTest.cpp +++ b/src/coreclr/pal/tests/palsuite/threading/WaitForSingleObject/WFSOProcessTest/WFSOProcessTest.cpp @@ -82,14 +82,6 @@ switch (dwWaitResult) return FALSE; } - // Got ownership of the abandoned process object. - case WAIT_ABANDONED: - { - Fail ( "Got ownership of the abandoned Process object. Failing test.\n" - "GetLastError returned %d\n", GetLastError()); - return FALSE; - } - //Error condition case WAIT_FAILED: { diff --git a/src/coreclr/pal/tests/palsuite/threading/WaitForSingleObject/WFSOThreadTest/WFSOThreadTest.cpp b/src/coreclr/pal/tests/palsuite/threading/WaitForSingleObject/WFSOThreadTest/WFSOThreadTest.cpp index 589e27a72223bb..6cdd34cb790735 100644 --- a/src/coreclr/pal/tests/palsuite/threading/WaitForSingleObject/WFSOThreadTest/WFSOThreadTest.cpp +++ b/src/coreclr/pal/tests/palsuite/threading/WaitForSingleObject/WFSOThreadTest/WFSOThreadTest.cpp @@ -3,14 +3,14 @@ /*============================================================ ** -** Source: WFSOThreadTest.c +** Source: WFSOThreadTest.c ** -** Purpose: Test for WaitForSingleObjectTest. +** Purpose: Test for WaitForSingleObjectTest. ** Create One Thread and do some work -** Use WFSO For the Thread to finish -** +** Use WFSO For the Thread to finish +** ** Test Passes if the above operations are successful -** +** ** ** **=========================================================*/ @@ -33,10 +33,10 @@ PALTEST(threading_WaitForSingleObject_WFSOThreadTest_paltest_waitforsingleobject { //Declare local variables - DWORD dwThreadId=0; - DWORD dwWaitResult=0; + DWORD dwThreadId=0; + DWORD dwWaitResult=0; - //Initialize PAL + //Initialize PAL if(0 != (PAL_Initialize(argc, argv))) { return ( FAIL ); @@ -51,101 +51,84 @@ PALTEST(threading_WaitForSingleObject_WFSOThreadTest_paltest_waitforsingleobject "GetLastError returned %d\n", GetLastError()); } - + //Create Thread hThread_WFSOThreadTest = CreateThread( - NULL, - 0, - incrementCounter, - NULL, - 0, + NULL, + 0, + incrementCounter, + NULL, + 0, &dwThreadId); - if ( NULL == hThread_WFSOThreadTest ) + if ( NULL == hThread_WFSOThreadTest ) { Fail ( "CreateThread() returned NULL. Failing test.\n" - "GetLastError returned %d\n", GetLastError()); + "GetLastError returned %d\n", GetLastError()); } - //Wait For Thread to signal start + //Wait For Thread to signal start dwWaitResult = WaitForSingleObject(hEvent_WFSOThreadTest,INFINITE); - - switch (dwWaitResult) + + switch (dwWaitResult) { // The thread wait was successful - case WAIT_OBJECT_0: + case WAIT_OBJECT_0: { Trace ("Wait for Single Object (hEvent) was successful.\n"); - break; - } + break; + } // Time-out. - case WAIT_TIMEOUT: + case WAIT_TIMEOUT: { Fail ( "Time -out. Failing test.\n" - "GetLastError returned %d\n", GetLastError()); + "GetLastError returned %d\n", GetLastError()); return FALSE; } - - // Got ownership of the abandoned event object. - case WAIT_ABANDONED: - { - Fail ( "Got ownership of the abandoned event object. Failing test.\n" - "GetLastError returned %d\n", GetLastError()); - return FALSE; - } - } - - //Wait for Thread to finish - dwWaitResult = WaitForSingleObject( + + //Wait for Thread to finish + dwWaitResult = WaitForSingleObject( hThread_WFSOThreadTest, //handle to thread 5000L); //Wait Indefinitely - - switch (dwWaitResult) + + switch (dwWaitResult) { // The thread wait was successful - case WAIT_OBJECT_0: + case WAIT_OBJECT_0: { Trace("Wait for thread was successful\n"); - - break; - } + + break; + } // Time-out. - case WAIT_TIMEOUT: + case WAIT_TIMEOUT: { Fail ( "Time -out. Failing test.\n" - "GetLastError returned %d\n", GetLastError()); + "GetLastError returned %d\n", GetLastError()); return FALSE; } - // Got ownership of the abandoned thread object. - case WAIT_ABANDONED: - { - Fail ( "Got ownership of the abandoned thread object. Failing test.\n" - "GetLastError returned %d\n", GetLastError()); - return FALSE; - } - } //Close Handles if (0==CloseHandle(hEvent_WFSOThreadTest)) { - Trace("Could not Close event handle\n"); - Fail ( "GetLastError returned %d\n", GetLastError()); + Trace("Could not Close event handle\n"); + Fail ( "GetLastError returned %d\n", GetLastError()); } if (0==CloseHandle(hThread_WFSOThreadTest)) { - Trace("Could not Close thread handle\n"); - Fail ( "GetLastError returned %d\n", GetLastError()); + Trace("Could not Close thread handle\n"); + Fail ( "GetLastError returned %d\n", GetLastError()); } PAL_Terminate(); @@ -160,13 +143,13 @@ DWORD PALAPI incrementCounter(LPVOID params) if (0==SetEvent(hEvent_WFSOThreadTest)) { Fail ( "SetEvent returned Zero. Failing test.\n" - "GetLastError returned %d\n", GetLastError()); + "GetLastError returned %d\n", GetLastError()); } for (globalcounter_WFSOThreadTest=0;globalcounter_WFSOThreadTest<100000;globalcounter_WFSOThreadTest++); //Sleep(5000); - + Trace("Global Counter Value: %d \n", globalcounter_WFSOThreadTest); return 0; } From 32c9e60d08001e9191afd47f797a8c04b91b6186 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 29 Jul 2025 21:47:18 +0000 Subject: [PATCH 46/47] Change spinlock scoping --- src/coreclr/pal/src/thread/threadsusp.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/coreclr/pal/src/thread/threadsusp.cpp b/src/coreclr/pal/src/thread/threadsusp.cpp index 74462ce4fc8d9f..ec0dc9c5b92594 100644 --- a/src/coreclr/pal/src/thread/threadsusp.cpp +++ b/src/coreclr/pal/src/thread/threadsusp.cpp @@ -50,14 +50,18 @@ suspension mutex or spinlock. The downside is that it restricts us to only performing one suspension or resumption in the PAL at a time. */ #ifdef USE_GLOBAL_LOCK_FOR_SUSPENSION +namespace +{ + LONG g_ssSuspensionLock = 0; +} +#endif + #define SYNCSPINLOCK_F_ASYMMETRIC 1 #define SPINLOCKInit(lock) (*(lock) = 0) namespace { - LONG g_ssSuspensionLock = 0; - /* Basic spinlock implementation */ void SPINLOCKAcquire (LONG * lock, unsigned int flags) { @@ -94,9 +98,7 @@ namespace return InterlockedCompareExchange(lock, 1, 0); // only returns 0 or 1. } - } -#endif /*++ Function: From 63a1aa6ff37876b441245aed14641d6b5c4c98a1 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 29 Jul 2025 22:26:49 +0000 Subject: [PATCH 47/47] Remove prioritized waits from the PAL and remove WAIT_ABANDONED usage --- src/coreclr/pal/src/include/pal/corunix.hpp | 3 +- .../pal/src/include/pal/synchobjects.hpp | 3 +- .../pal/src/synchmgr/synchcontrollers.cpp | 65 +++++-------------- src/coreclr/pal/src/synchmgr/synchmanager.hpp | 5 +- src/coreclr/pal/src/synchmgr/wait.cpp | 6 +- src/coreclr/vm/finalizerthread.cpp | 4 -- src/coreclr/vm/threads.cpp | 10 ++- 7 files changed, 33 insertions(+), 63 deletions(-) diff --git a/src/coreclr/pal/src/include/pal/corunix.hpp b/src/coreclr/pal/src/include/pal/corunix.hpp index 17d83a8ac0d189..8e57767be3a07a 100644 --- a/src/coreclr/pal/src/include/pal/corunix.hpp +++ b/src/coreclr/pal/src/include/pal/corunix.hpp @@ -571,8 +571,7 @@ namespace CorUnix RegisterWaitingThread( WaitType eWaitType, DWORD dwIndex, - bool fAltertable, - bool fPrioritize + bool fAltertable ) = 0; // diff --git a/src/coreclr/pal/src/include/pal/synchobjects.hpp b/src/coreclr/pal/src/include/pal/synchobjects.hpp index 2c21da9b2d3a1b..407df2b52b510c 100644 --- a/src/coreclr/pal/src/include/pal/synchobjects.hpp +++ b/src/coreclr/pal/src/include/pal/synchobjects.hpp @@ -37,8 +37,7 @@ namespace CorUnix CONST HANDLE *lpHandles, BOOL bWaitAll, DWORD dwMilliseconds, - BOOL bAlertable, - BOOL bPrioritize = FALSE); + BOOL bAlertable); DWORD InternalSignalObjectAndWait( CPalThread *thread, diff --git a/src/coreclr/pal/src/synchmgr/synchcontrollers.cpp b/src/coreclr/pal/src/synchmgr/synchcontrollers.cpp index 99ca5a966f970b..945268b31f49a6 100644 --- a/src/coreclr/pal/src/synchmgr/synchcontrollers.cpp +++ b/src/coreclr/pal/src/synchmgr/synchcontrollers.cpp @@ -247,8 +247,7 @@ namespace CorUnix PAL_ERROR CSynchWaitController::RegisterWaitingThread( WaitType wtWaitType, DWORD dwIndex, - bool fAlertable, - bool fPrioritize) + bool fAlertable) { VALIDATEOBJECT(m_psdSynchData); @@ -366,7 +365,7 @@ namespace CorUnix } // Add new node to queue - m_psdSynchData->WaiterEnqueue(pwtlnNewNode, fPrioritize); + m_psdSynchData->WaiterEnqueue(pwtlnNewNode); // Succeeded: update object count ptwiWaitInfo->lObjCount++; @@ -1113,60 +1112,32 @@ namespace CorUnix Note: this method must be called while holding the local process synchronization lock. --*/ - void CSynchData::WaiterEnqueue(WaitingThreadsListNode * pwtlnNewNode, bool fPrioritize) + void CSynchData::WaiterEnqueue(WaitingThreadsListNode * pwtlnNewNode) { VALIDATEOBJECT(this); VALIDATEOBJECT(pwtlnNewNode); - if (!fPrioritize) - { - // Enqueue normally to the end of the queue - WaitingThreadsListNode * pwtlnCurrLast = m_ptrWTLTail.ptr; + // Enqueue normally to the end of the queue + WaitingThreadsListNode * pwtlnCurrLast = m_ptrWTLTail.ptr; - pwtlnNewNode->ptrNext.ptr = NULL; - if (NULL == pwtlnCurrLast) - { - _ASSERT_MSG(NULL == m_ptrWTLHead.ptr, - "Corrupted waiting list on local CSynchData @ %p\n", - this); - - pwtlnNewNode->ptrPrev.ptr = NULL; - m_ptrWTLHead.ptr = pwtlnNewNode; - m_ptrWTLTail.ptr = pwtlnNewNode; - } - else - { - VALIDATEOBJECT(pwtlnCurrLast); + pwtlnNewNode->ptrNext.ptr = NULL; + if (NULL == pwtlnCurrLast) + { + _ASSERT_MSG(NULL == m_ptrWTLHead.ptr, + "Corrupted waiting list on local CSynchData @ %p\n", + this); - pwtlnNewNode->ptrPrev.ptr = pwtlnCurrLast; - pwtlnCurrLast->ptrNext.ptr = pwtlnNewNode; - m_ptrWTLTail.ptr = pwtlnNewNode; - } + pwtlnNewNode->ptrPrev.ptr = NULL; + m_ptrWTLHead.ptr = pwtlnNewNode; + m_ptrWTLTail.ptr = pwtlnNewNode; } else { - // The wait is prioritized, enqueue to the beginning of the queue - WaitingThreadsListNode * pwtlnCurrFirst = m_ptrWTLHead.ptr; + VALIDATEOBJECT(pwtlnCurrLast); - pwtlnNewNode->ptrPrev.ptr = NULL; - if (NULL == pwtlnCurrFirst) - { - _ASSERT_MSG(NULL == m_ptrWTLTail.ptr, - "Corrupted waiting list on local CSynchData @ %p\n", - this); - - pwtlnNewNode->ptrNext.ptr = NULL; - m_ptrWTLHead.ptr = pwtlnNewNode; - m_ptrWTLTail.ptr = pwtlnNewNode; - } - else - { - VALIDATEOBJECT(pwtlnCurrFirst); - - pwtlnNewNode->ptrNext.ptr = pwtlnCurrFirst; - pwtlnCurrFirst->ptrPrev.ptr = pwtlnNewNode; - m_ptrWTLHead.ptr = pwtlnNewNode; - } + pwtlnNewNode->ptrPrev.ptr = pwtlnCurrLast; + pwtlnCurrLast->ptrNext.ptr = pwtlnNewNode; + m_ptrWTLTail.ptr = pwtlnNewNode; } m_ulcWaitingThreads += 1; diff --git a/src/coreclr/pal/src/synchmgr/synchmanager.hpp b/src/coreclr/pal/src/synchmgr/synchmanager.hpp index f1bb0df602401d..1b884ab478f722 100644 --- a/src/coreclr/pal/src/synchmgr/synchmanager.hpp +++ b/src/coreclr/pal/src/synchmgr/synchmanager.hpp @@ -179,7 +179,7 @@ namespace CorUnix CPalThread * pthrCurrent, CPalThread * pthrTarget); - void WaiterEnqueue(WaitingThreadsListNode * pwtlnNewNode, bool fPrioritize); + void WaiterEnqueue(WaitingThreadsListNode * pwtlnNewNode); // Object Type accessor methods CObjectType * GetObjectType(void) @@ -366,8 +366,7 @@ namespace CorUnix virtual PAL_ERROR RegisterWaitingThread( WaitType wtWaitType, DWORD dwIndex, - bool fAlertable, - bool fPrioritize); + bool fAlertable); virtual void ReleaseController(void); diff --git a/src/coreclr/pal/src/synchmgr/wait.cpp b/src/coreclr/pal/src/synchmgr/wait.cpp index 51a9da87483798..d0907ee334629f 100644 --- a/src/coreclr/pal/src/synchmgr/wait.cpp +++ b/src/coreclr/pal/src/synchmgr/wait.cpp @@ -321,8 +321,7 @@ DWORD CorUnix::InternalWaitForMultipleObjectsEx( CONST HANDLE *lpHandles, BOOL bWaitAll, DWORD dwMilliseconds, - BOOL bAlertable, - BOOL bPrioritize) + BOOL bAlertable) { DWORD dwRet = WAIT_FAILED; PAL_ERROR palErr = NO_ERROR; @@ -516,8 +515,7 @@ DWORD CorUnix::InternalWaitForMultipleObjectsEx( palErr = ppISyncWaitCtrlrs[i]->RegisterWaitingThread( wtWaitType, i, - (TRUE == bAlertable), - bPrioritize != FALSE); + (TRUE == bAlertable)); if (NO_ERROR != palErr) { ERROR("RegisterWaitingThread() failed for %d-th object " diff --git a/src/coreclr/vm/finalizerthread.cpp b/src/coreclr/vm/finalizerthread.cpp index 0ad3d680bcd8bc..3287db4dd1fc5c 100644 --- a/src/coreclr/vm/finalizerthread.cpp +++ b/src/coreclr/vm/finalizerthread.cpp @@ -249,8 +249,6 @@ void FinalizerThread::WaitForFinalizerEvent (CLREvent *event) { case (WAIT_OBJECT_0): return; - case (WAIT_ABANDONED): - return; case (WAIT_TIMEOUT): break; } @@ -312,8 +310,6 @@ void FinalizerThread::WaitForFinalizerEvent (CLREvent *event) { case (WAIT_OBJECT_0): return; - case (WAIT_ABANDONED): - return; case (WAIT_TIMEOUT): break; } diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index 80cbf8b2e3136d..460c120bb35f1e 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -3361,7 +3361,6 @@ DWORD Thread::DoAppropriateWaitWorker(int countHandles, HANDLE *handles, BOOL wa goto retry; } _ASSERTE((ret >= WAIT_OBJECT_0 && ret < (WAIT_OBJECT_0 + (DWORD)countHandles)) || - (ret >= WAIT_ABANDONED && ret < (WAIT_ABANDONED + (DWORD)countHandles)) || (ret == WAIT_TIMEOUT) || (ret == WAIT_FAILED)); // countHandles is used as an unsigned -- it should never be negative. _ASSERTE(countHandles >= 0); @@ -3460,11 +3459,13 @@ DWORD Thread::DoAppropriateWaitWorker(int countHandles, HANDLE *handles, BOOL wa DWORD subRet = WaitForSingleObject (handles[i], 0); if ((subRet == WAIT_OBJECT_0) || (subRet == WAIT_FAILED)) break; +#ifdef HOST_WINDOWS if (subRet == WAIT_ABANDONED) { ret = (ret - WAIT_OBJECT_0) + WAIT_ABANDONED; break; } +#endif // HOST_WINDOWS // If we get alerted it just masks the real state of the current // handle, so retry the wait. if (subRet == WAIT_IO_COMPLETION) @@ -3622,11 +3623,18 @@ DWORD Thread::DoSignalAndWaitWorker(HANDLE* pHandles, DWORD millis,BOOL alertabl WaitCompleted: //Check that the return state is valid +#ifdef HOST_WINDOWS _ASSERTE(WAIT_OBJECT_0 == ret || WAIT_ABANDONED == ret || WAIT_TIMEOUT == ret || WAIT_FAILED == ret || ERROR_TOO_MANY_POSTS == ret); +#else + _ASSERTE(WAIT_OBJECT_0 == ret || + WAIT_TIMEOUT == ret || + WAIT_FAILED == ret || + ERROR_TOO_MANY_POSTS == ret); +#endif // HOST_WINDOWS //Wrong to time out if the wait was infinite _ASSERTE((WAIT_TIMEOUT != ret) || (INFINITE != millis));