This commit is contained in:
Jeremy Koritzinsky 2025-07-30 15:56:25 +02:00 committed by GitHub
commit aaa20cf58d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 2143 additions and 63 deletions

View File

@ -513,6 +513,10 @@ namespace System.Threading
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
{

View File

@ -262,3 +262,9 @@ FCIMPL0(OBJECTREF, RhpGetNextFinalizableObject)
}
}
FCIMPLEND
FCIMPL0(FC_BOOL_RET, RhpCurrentThreadIsFinalizerThread)
{
FC_RETURN_BOOL(ThreadStore::GetCurrentThread() == g_pFinalizerThread);
}
FCIMPLEND

View File

@ -302,15 +302,9 @@
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.Exit.cs">
<Link>Interop\Unix\System.Native\Interop.Exit.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.MMap.cs">
<Link>Interop\Unix\System.Native\Interop.MMap.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.MProtect.cs">
<Link>Interop\Unix\System.Native\Interop.MProtect.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.MUnmap.cs">
<Link>Interop\Unix\System.Native\Interop.MUnmap.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="$(CompilerCommonPath)\System\Collections\Generic\ArrayBuilder.cs">

View File

@ -90,8 +90,12 @@ namespace System.Runtime
RhWaitForPendingFinalizers(allowReentrantWait ? 1 : 0);
}
[MethodImpl(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhpCurrentThreadIsFinalizerThread")]
internal static extern bool RhpCurrentThreadIsFinalizerThread();
// Get maximum GC generation number.
[MethodImplAttribute(MethodImplOptions.InternalCall)]
[MethodImplAttribute(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhGetMaxGcGeneration")]
internal static extern int RhGetMaxGcGeneration();

View File

@ -517,5 +517,10 @@ namespace System.Threading
}
s_allDone.WaitOne();
}
internal static bool CurrentThreadIsFinalizerThread()
{
return RuntimeImports.RhpCurrentThreadIsFinalizerThread();
}
}
}

View File

@ -899,3 +899,18 @@ extern "C" void QCALLTYPE ThreadNative_ResetAbort()
pThread->UnmarkThreadForAbort(EEPolicy::TA_Safe);
}
}
extern "C" BOOL QCALLTYPE ThreadNative_CurrentThreadIsFinalizerThread()
{
QCALL_CONTRACT;
BOOL retval = FALSE;
BEGIN_QCALL;
retval = IsFinalizerThread() ? TRUE : FALSE;
END_QCALL;
return retval;
}

View File

@ -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

View File

@ -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)

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -4378,5 +4378,31 @@
</data>
<data name="PlatformNotSupported_DynamicEntrypoint" xml:space="preserve">
<value>Dynamic entrypoint allocation is not supported in the current environment.</value>
</data> <data name="Arg_InvalidOperationException_CannotReleaseUnownedMutex" xml:space="preserve">
<value>Cannot release a lock that is not owned by the current thread.</value>
</data>
<data name="Arg_FailedToInitializePThreadMutex" xml:space="preserve">
<value>Failed to initialize pthread mutex</value>
</data>
<data name="IO_SharedMemory_FileNotOwnedByUid" xml:space="preserve">
<value>The file '{0}' is not owned by the current user with UID {1}.</value>
</data>
<data name="IO_SharedMemory_FilePermissionsIncorrect" xml:space="preserve">
<value>The file '{0}' does not have the expected permissions for user scope: {1}.</value>
</data>
<data name="IO_SharedMemory_PathExistsButNotDirectory" xml:space="preserve">
<value>The path '{0}' exists but is not a directory.</value>
</data>
<data name="IO_SharedMemory_DirectoryPermissionsIncorrect" xml:space="preserve">
<value>The directory '{0}' does not have the expected owner or permissions for system scope: {1}, {2}.</value>
</data>
<data name="IO_SharedMemory_DirectoryNotOwnedByUid" xml:space="preserve">
<value>The directory '{0}' is not owned by the current user with UID {1}.</value>
</data>
<data name="IO_SharedMemory_DirectoryPermissionsIncorrectUserScope" xml:space="preserve">
<value>The directory '{0}' does not have the expected permissions for user scope: {1}.</value>
</data>
<data name="IO_SharedMemory_DirectoryOwnerPermissionsIncorrect" xml:space="preserve">
<value>The directory '{0}' does not have the expected owner permissions: {1}.</value>
</data>
</root>

View File

@ -24,6 +24,7 @@
<Is64Bit Condition="'$(Platform)' == 'arm64' or '$(Platform)' == 'x64' or '$(Platform)' == 's390x' or '$(Platform)' == 'loongarch64' or '$(Platform)' == 'ppc64le' or '$(Platform)' == 'riscv64'">true</Is64Bit>
<UseMinimalGlobalizationData Condition="'$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true'">true</UseMinimalGlobalizationData>
<EnableLibraryImportGenerator>true</EnableLibraryImportGenerator>
<FeatureCrossProcessMutex Condition="'$(IsMobileLike)' != 'true'">true</FeatureCrossProcessMutex>
</PropertyGroup>
<PropertyGroup>
<DefineConstants Condition="'$(IsBigEndian)' == 'true'">$(DefineConstants);BIGENDIAN</DefineConstants>
@ -49,6 +50,9 @@
<DefineConstants Condition="'$(TargetsSolaris)' == 'true'">$(DefineConstants);TARGET_SOLARIS</DefineConstants>
<DefineConstants Condition="'$(TargetsHaiku)' == 'true'">$(DefineConstants);TARGET_HAIKU</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<DefineConstants Condition="'$(FeatureCrossProcessMutex)' == 'true'">$(DefineConstants);FEATURE_CROSS_PROCESS_MUTEX</DefineConstants>
</PropertyGroup>
<ItemGroup>
<ApiCompatSuppressionFile Include="$(MSBuildThisFileDirectory)CompatibilitySuppressions.xml" />
</ItemGroup>
@ -2405,6 +2409,9 @@
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetOSArchitecture.cs">
<Link>Common\Interop\Unix\System.Native\Interop.GetOSArchitecture.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.GetPageSize.cs">
<Link>Common\Interop\Unix\System.Native\Interop.GetPageSize.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.GetProcessPath.cs">
<Link>Common\Interop\Unix\System.Native\Interop.GetProcessPath.cs</Link>
</Compile>
@ -2459,6 +2466,12 @@
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.MksTemps.cs">
<Link>Common\Interop\Unix\System.Native\Interop.MksTemps.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.MMap.cs">
<Link>Common\Interop\Unix\System.Native\Interop.MMap.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.MUnmap.cs">
<Link>Common\Interop\Unix\System.Native\Interop.MUnmap.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.MountPoints.cs">
<Link>Common\Interop\Unix\System.Native\Interop.MountPoints.cs</Link>
</Compile>
@ -2620,6 +2633,9 @@
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.GetPid.cs">
<Link>Common\Interop\Unix\System.Native\Interop.GetPid.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.GetSid.cs">
<Link>Common\Interop\Unix\System.Native\Interop.GetSid.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\FreeBSD\Interop.Process.GetProcInfo.cs" Condition="'$(TargetsFreeBSD)' == 'true'" Link="Common\Interop\FreeBSD\Interop.Process.GetProcInfo.cs" />
<Compile Include="$(CommonPath)Interop\BSD\System.Native\Interop.Sysctl.cs" Condition="'$(TargetsFreeBSD)' == 'true'" Link="Common\Interop\BSD\System.Native\Interop.Sysctl.cs" />
<Compile Include="$(CommonPath)Interop\Linux\procfs\Interop.ProcFsStat.TryReadStatusFile.cs" Condition="'$(TargetsLinux)' == 'true'" Link="Common\Interop\Linux\Interop.ProcFsStat.TryReadStatusFile.cs" />
@ -2833,6 +2849,13 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\WaitSubsystem.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\WaitSubsystem.WaitableObject.Unix.cs" />
</ItemGroup>
<ItemGroup Condition="'$(FeatureCoreCLR)' != 'true' and '$(TargetsUnix)' == 'true' and '$(FeatureCrossProcessMutex)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)System\IO\SharedMemoryManager.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\NamedMutex.Unix.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.LowLevelCrossProcessMutex.cs">
<Link>Common\Interop\Unix\System.Native\Interop.LowLevelCrossProcessMutex.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup Condition="'$(FeatureCoreCLR)' != 'true' and '$(TargetsWindows)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\WaitHandle.Windows.cs" />
</ItemGroup>

View File

@ -56,6 +56,11 @@ namespace System.IO
// 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 @@ namespace System.IO
// 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 @@ namespace System.IO
fixed (byte* buf = &heapBuf[0])
{
if (TryGetHomeDirectoryFromPasswd(buf, heapBuf.Length, out userHomeDirectory))
return userHomeDirectory;
return userHomeDirectory!;
}
}
}

View File

@ -0,0 +1,835 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using System.Threading;
using Microsoft.Win32.SafeHandles;
namespace System.IO
{
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 '{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 byte Version;
}
[FieldOffset(0)]
private SharedMemoryAndVersion _data;
[FieldOffset(0)]
private ulong _raw;
public readonly SharedMemoryType Type => _data.Type;
public readonly byte Version => _data.Version;
public SharedMemorySharedDataHeader(SharedMemoryType type, byte version)
{
_data = new SharedMemoryAndVersion
{
Type = type,
Version = version
};
}
}
internal interface ISharedMemoryProcessData
{
void Close(bool releaseSharedData);
}
internal sealed unsafe class SharedMemoryProcessDataHeader<TSharedMemoryProcessData>
where TSharedMemoryProcessData : class, ISharedMemoryProcessData
{
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)
{
_id = id;
_fileHandle = fileHandle;
_sharedDataHeader = sharedDataHeader;
_sharedDataTotalByteCount = sharedDataTotalByteCount;
_processData = null; // Will be initialized later
SharedMemoryManager<TSharedMemoryProcessData>.Instance.AddProcessDataHeader(this);
}
public static void* GetDataPointer(SharedMemoryProcessDataHeader<TSharedMemoryProcessData>? processDataHeader)
{
return processDataHeader is null
? null
: (void*)((byte*)processDataHeader._sharedDataHeader + sizeof(SharedMemorySharedDataHeader));
}
internal static SharedMemoryProcessDataHeader<TSharedMemoryProcessData>? CreateOrOpen(
string name,
bool isUserScope,
SharedMemorySharedDataHeader requiredSharedDataHeader,
nuint sharedMemoryDataSize,
bool createIfNotExist,
bool acquireLockIfCreated,
out bool created,
out AutoReleaseFileLock creationDeletionLockFileHandle)
{
created = false;
AutoReleaseFileLock placeholderAutoReleaseLock = new AutoReleaseFileLock(new SafeFileHandle());
creationDeletionLockFileHandle = placeholderAutoReleaseLock;
SharedMemoryId id = new(name, isUserScope);
nuint sharedDataUsedByteCount = (nuint)sizeof(SharedMemorySharedDataHeader) + sharedMemoryDataSize;
nuint sharedDataTotalByteCount = AlignUp(sharedDataUsedByteCount, (nuint)Interop.Sys.GetPageSize());
SharedMemoryProcessDataHeader<TSharedMemoryProcessData>? processDataHeader = SharedMemoryManager<TSharedMemoryProcessData>.Instance.FindProcessDataHeader(id);
if (processDataHeader is not null)
{
Debug.Assert(processDataHeader._sharedDataTotalByteCount == sharedDataTotalByteCount);
processDataHeader.IncrementRefCount();
return processDataHeader;
}
creationDeletionLockFileHandle = SharedMemoryManager<TSharedMemoryProcessData>.Instance.AcquireCreationDeletionLockForId(id);
string sessionDirectory = Path.Combine(
SharedMemoryHelpers.SharedFilesPath,
id.GetRuntimeTempDirectoryName(),
SharedMemoryManager<TSharedMemoryProcessData>.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)
{
if (clearContents)
{
NativeMemory.Clear(memory.Pointer, sharedDataTotalByteCount);
}
*sharedDataHeader = requiredSharedDataHeader;
}
else
{
if (sharedDataHeader->Type != requiredSharedDataHeader.Type ||
sharedDataHeader->Version != requiredSharedDataHeader.Version)
{
throw new InvalidOperationException("Header mismatch");
}
}
if (!createdFile)
{
creationDeletionLockFileHandle.Dispose();
// Reset to the placeholder value to avoid returning a pre-disposed lock.
creationDeletionLockFileHandle = placeholderAutoReleaseLock;
}
processDataHeader = new SharedMemoryProcessDataHeader<TSharedMemoryProcessData>(
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, size) < 0)
{
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
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
// truncated. Ignore the error in such cases; in all others, throw.
throw Interop.GetExceptionForIoErrno(errorInfo, path);
}
}
}
}
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<NamedMutexProcessDataBase>.Instance.VerifyCreationDeletionProcessLockIsLocked();
SharedMemoryManager<TSharedMemoryProcessData>.Instance.RemoveProcessDataHeader(this);
using AutoReleaseFileLock autoReleaseFileLock = SharedMemoryManager<TSharedMemoryProcessData>.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<TSharedMemoryProcessData>.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);
}
}
}
internal static class SharedMemoryHelpers
{
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.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;
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();
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 | 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)
{
error = Interop.Sys.GetLastErrorInfo();
fd.Dispose();
throw Interop.GetExceptionForIoErrno(error, sharedMemoryFilePath);
}
if (fileStatus.Uid != id.Uid)
{
fd.Dispose();
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(SR.Format(SR.IO_SharedMemory_FilePermissionsIncorrect, sharedMemoryFilePath, PermissionsMask_OwnerUser_ReadWrite));
}
}
createdFile = false;
return fd;
}
if (error.Error == Interop.Error.ENAMETOOLONG)
{
throw new ArgumentException(SR.Arg_ArgumentException, "name");
}
Debug.Assert(error.Error == 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_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)
{
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)
{
#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
{
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(SR.Format(SR.IO_SharedMemory_PathExistsButNotDirectory, directoryPath));
}
}
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(SR.Format(SR.IO_SharedMemory_DirectoryPermissionsIncorrect, directoryPath, 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(SR.Format(SR.IO_SharedMemory_DirectoryNotOwnedByUid, directoryPath, 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(SR.Format(SR.IO_SharedMemory_DirectoryPermissionsIncorrectUserScope, directoryPath, 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(SR.Format(SR.IO_SharedMemory_DirectoryOwnerPermissionsIncorrect, directoryPath, 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,
0);
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);
}
}
}
internal sealed class SharedMemoryManager<TSharedMemoryProcessData>
where TSharedMemoryProcessData : class, ISharedMemoryProcessData
{
internal static SharedMemoryManager<TSharedMemoryProcessData> Instance { get; } = new SharedMemoryManager<TSharedMemoryProcessData>();
internal const string SharedMemorySharedMemoryDirectoryName = "shm";
private readonly LowLevelLock _creationDeletionProcessLock = new();
private SafeFileHandle? _creationDeletionLockFileHandle;
private readonly Dictionary<uint, SafeFileHandle> _uidToFileHandleMap = [];
public WaitSubsystem.LockHolder AcquireCreationDeletionProcessLock()
{
return new WaitSubsystem.LockHolder(_creationDeletionProcessLock);
}
public void VerifyCreationDeletionProcessLockIsLocked()
{
_creationDeletionProcessLock.VerifyIsLocked();
}
public AutoReleaseFileLock AcquireCreationDeletionLockForId(SharedMemoryId id)
{
_creationDeletionProcessLock.VerifyIsLocked();
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);
}
else
{
_creationDeletionLockFileHandle = fd;
}
}
bool acquired = SharedMemoryHelpers.TryAcquireFileLock(fd, nonBlocking: true, exclusive: true);
Debug.Assert(acquired);
return new AutoReleaseFileLock(fd);
SafeFileHandle? GetUserScopeCreationDeletionLockFileHandle(uint uid)
{
_uidToFileHandleMap.TryGetValue(uid, out SafeFileHandle? fileHandle);
return fileHandle;
}
}
private Dictionary<SharedMemoryId, SharedMemoryProcessDataHeader<TSharedMemoryProcessData>> _processDataHeaders = [];
public void AddProcessDataHeader(SharedMemoryProcessDataHeader<TSharedMemoryProcessData> processDataHeader)
{
VerifyCreationDeletionProcessLockIsLocked();
_processDataHeaders[processDataHeader._id] = processDataHeader;
}
public void RemoveProcessDataHeader(SharedMemoryProcessDataHeader<TSharedMemoryProcessData> processDataHeader)
{
VerifyCreationDeletionProcessLockIsLocked();
_processDataHeaders.Remove(processDataHeader._id);
}
public SharedMemoryProcessDataHeader<TSharedMemoryProcessData>? FindProcessDataHeader(SharedMemoryId id)
{
_processDataHeaders.TryGetValue(id, out SharedMemoryProcessDataHeader<TSharedMemoryProcessData>? header);
return header;
}
}
}

View File

@ -22,7 +22,7 @@ namespace System.Threading
{
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 @@ namespace System.Threading
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;
}
@ -62,11 +62,6 @@ namespace System.Threading
name = name.Substring(NamedWaitHandleOptionsInternal.CurrentSessionPrefix.Length);
}
if (options.WasSpecified && options.CurrentUserOnly)
{
name = @"User\" + name;
}
return name;
}

View File

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

View File

@ -10,9 +10,39 @@ namespace System.Threading
{
internal static partial class WaitSubsystem
{
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.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
{
public static IntPtr NewHandle(WaitableObject waitableObject)
public static IntPtr NewHandle(IWaitableObject waitableObject)
{
Debug.Assert(waitableObject != null);
@ -24,7 +54,7 @@ namespace System.Threading
return handle;
}
public static WaitableObject FromHandle(IntPtr handle)
public static IWaitableObject FromHandle(IntPtr handle)
{
if (handle == IntPtr.Zero || handle == new IntPtr(-1))
{
@ -33,7 +63,7 @@ namespace System.Threading
// 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 (IWaitableObject)GCHandle.FromIntPtr(handle).Target!;
}
/// <summary>

View File

@ -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
{
@ -86,6 +87,15 @@ namespace System.Threading
/// </summary>
private WaitableObject? _lockedMutexesHead;
#if FEATURE_CROSS_PROCESS_MUTEX
/// <summary>
/// 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.
/// </summary>
private NamedMutexOwnershipChain? _namedMutexOwnershipChain;
#endif
public ThreadWaitInfo(Thread thread)
{
Debug.Assert(thread != null);
@ -553,10 +563,31 @@ namespace System.Threading
}
}
#if FEATURE_CROSS_PROCESS_MUTEX
public NamedMutexOwnershipChain NamedMutexOwnershipChain
{
get
{
SharedMemoryManager<NamedMutexProcessDataBase>.Instance.VerifyCreationDeletionProcessLockIsLocked();
return Volatile.Read(ref _namedMutexOwnershipChain) ?? AllocateOwnershipChain();
NamedMutexOwnershipChain AllocateOwnershipChain()
{
Interlocked.CompareExchange(ref _namedMutexOwnershipChain, new NamedMutexOwnershipChain(this._thread), null!);
return _namedMutexOwnershipChain;
}
}
}
#endif
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
{
@ -576,6 +607,7 @@ namespace System.Threading
{
s_lock.Release();
}
}
public sealed class WaitedListNode

View File

@ -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,10 @@ namespace System.Threading
}
}
private static SafeWaitHandle NewHandle(WaitableObject waitableObject)
#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();
@ -193,7 +197,26 @@ namespace System.Threading
return safeWaitHandle;
}
public static SafeWaitHandle? CreateNamedMutex(bool initiallyOwned, string name, out bool createdNew)
#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);
if (namedMutex == null)
{
return null;
}
SafeWaitHandle safeWaitHandle = NewHandle(namedMutex);
return safeWaitHandle;
}
public static OpenExistingResult OpenNamedMutex(string name, bool isUserScope, out SafeWaitHandle? result)
{
OpenExistingResult status = NamedMutex.OpenNamedMutex(name, isUserScope, out NamedMutex? mutex);
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.
@ -202,6 +225,11 @@ namespace System.Threading
LockHolder lockHolder = new LockHolder(s_lock);
try
{
if (isUserScope)
{
name = $"User\\{name}";
}
WaitableObject? waitableObject = WaitableObject.CreateNamedMutex_Locked(name, out createdNew);
if (waitableObject == null)
{
@ -227,12 +255,18 @@ namespace System.Threading
}
}
public static OpenExistingResult OpenNamedMutex(string name, out SafeWaitHandle? result)
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)
{
@ -241,7 +275,7 @@ namespace System.Threading
public static void SetEvent(IntPtr handle)
{
SetEvent(HandleManager.FromHandle(handle));
SetEvent((WaitableObject)HandleManager.FromHandle(handle));
}
public static void SetEvent(WaitableObject waitableObject)
@ -261,7 +295,7 @@ namespace System.Threading
public static void ResetEvent(IntPtr handle)
{
ResetEvent(HandleManager.FromHandle(handle));
ResetEvent((WaitableObject)HandleManager.FromHandle(handle));
}
public static void ResetEvent(WaitableObject waitableObject)
@ -282,7 +316,7 @@ namespace System.Threading
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 +340,14 @@ namespace System.Threading
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 +362,7 @@ namespace System.Threading
}
public static int Wait(
WaitableObject waitableObject,
IWaitableObject waitableObject,
int timeoutMilliseconds,
bool interruptible = true,
bool prioritize = false)
@ -349,14 +383,30 @@ namespace System.Threading
Debug.Assert(timeoutMilliseconds >= -1);
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;
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 +471,8 @@ namespace System.Threading
}
public static int SignalAndWait(
WaitableObject waitableObjectToSignal,
WaitableObject waitableObjectToWaitOn,
IWaitableObject waitableObjectToSignal,
IWaitableObject waitableObjectToWaitOn,
int timeoutMilliseconds,
bool interruptible = true,
bool prioritize = false)

View File

@ -13,7 +13,7 @@ namespace System.Threading
///
/// Used by the wait subsystem on Unix, so this class cannot have any dependencies on the wait subsystem.
/// </summary>
public sealed class WaitableObject
public sealed class WaitableObject : IWaitableObject
{
/// <summary>
/// Dictionary to look up named waitable objects. This implementation only supports in-process
@ -301,30 +301,6 @@ namespace System.Threading
}
}
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();
}
}
/// <summary>
/// This function does not check for a pending thread interrupt. Callers are expected to do that soon after
/// acquiring <see cref="s_lock"/>.

View File

@ -24,11 +24,6 @@ namespace System.Threading.Tests
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;
}
}
@ -928,7 +923,7 @@ namespace System.Threading.Tests
}
return createdNew;
}
});
}, ThreadTestHelpers.UnexpectedTimeoutMilliseconds);
}
}

View File

@ -351,6 +351,9 @@ namespace System.Threading
[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

View File

@ -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*);

View File

@ -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))

View File

@ -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 ()) ? TRUE : FALSE;
}
static MonoInternalThread*
thread_handle_to_internal_ptr (MonoThreadObjectHandle thread_handle)
{

View File

@ -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

View File

@ -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_crossprocessmutex_unsupported.c)
else()
list (APPEND NATIVE_SOURCES
pal_crossprocessmutex.c
)
endif()
if (CLR_CMAKE_TARGET_APPLE)
list (APPEND NATIVE_SOURCES_OBJC_NO_ARC
pal_autoreleasepool.m

View File

@ -16,6 +16,7 @@
#include "pal_log.h"
#include "pal_memory.h"
#include "pal_mount.h"
#include "pal_crossprocessmutex.h"
#include "pal_networkchange.h"
#include "pal_networking.h"
#include "pal_networkstatistics.h"
@ -221,6 +222,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)
@ -285,6 +287,15 @@ static const Entry s_sysNative[] =
DllImportEntry(SystemNative_GetGroupName)
DllImportEntry(SystemNative_GetUInt64OSThreadId)
DllImportEntry(SystemNative_TryGetUInt32OSThreadId)
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)
};

View File

@ -0,0 +1,167 @@
// 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_crossprocessmutex.h"
#include <limits.h>
#include <sched.h>
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <minipal/thread.h>
#if HAVE_SCHED_GETCPU
#include <sched.h>
#endif
#include <pthread.h>
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);
#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;
error = gettimeofday(&tv, NULL);
assert(error == 0);
timeoutTimeSpec.tv_sec = tv.tv_sec;
timeoutTimeSpec.tv_nsec = (long)(tv.tv_usec * 1000);
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(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;
}

View File

@ -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);

View File

@ -0,0 +1,74 @@
// 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_crossprocessmutex.h"
#include "pal_errno.h"
#include <stdio.h>
#include <limits.h>
#include <sched.h>
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
struct LowLevelCrossProcessMutex
{
bool Dummy;
};
int32_t SystemNative_LowLevelCrossProcessMutex_Size(void)
{
return (int32_t)sizeof(LowLevelCrossProcessMutex);
}
int32_t SystemNative_LowLevelCrossProcessMutex_Init(LowLevelCrossProcessMutex* mutex)
{
(void)mutex;
return Error_EINVAL;
}
int32_t SystemNative_LowLevelCrossProcessMutex_Acquire(LowLevelCrossProcessMutex* mutex, int32_t timeoutMilliseconds)
{
(void)mutex;
(void)timeoutMilliseconds;
return Error_EINVAL;
}
int32_t SystemNative_LowLevelCrossProcessMutex_Release(LowLevelCrossProcessMutex* mutex)
{
(void)mutex;
return Error_EINVAL;
}
int32_t SystemNative_LowLevelCrossProcessMutex_Destroy(LowLevelCrossProcessMutex* mutex)
{
(void)mutex;
return Error_EINVAL;
}
void SystemNative_LowLevelCrossProcessMutex_GetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t* pOwnerProcessId, uint32_t* pOwnerThreadId)
{
(void)mutex;
(void)pOwnerProcessId;
(void)pOwnerThreadId;
}
void SystemNative_LowLevelCrossProcessMutex_SetOwnerProcessAndThreadId(LowLevelCrossProcessMutex* mutex, uint32_t ownerProcessId, uint32_t ownerThreadId)
{
(void)mutex;
(void)ownerProcessId;
(void)ownerThreadId;
}
uint8_t SystemNative_LowLevelCrossProcessMutex_IsAbandoned(LowLevelCrossProcessMutex* mutex)
{
(void)mutex;
return false;
}
void SystemNative_LowLevelCrossProcessMutex_SetAbandoned(LowLevelCrossProcessMutex* mutex, uint8_t isAbandoned)
{
(void)mutex;
(void)isAbandoned;
}

View File

@ -899,3 +899,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();
}

View File

@ -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);

View File

@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
#include "pal_config.h"
#include "pal_errno.h"
#include "pal_threading.h"
#include <limits.h>

View File

@ -7,6 +7,7 @@
#include "pal_types.h"
typedef struct LowLevelMonitor LowLevelMonitor;
typedef struct LowLevelCrossProcessMutex LowLevelCrossProcessMutex;
PALEXPORT LowLevelMonitor *SystemNative_LowLevelMonitor_Create(void);

View File

@ -3,6 +3,7 @@
#include "pal_config.h"
#include "pal_threading.h"
#include "pal_errno.h"
#include <stdio.h>
#include <limits.h>

View File

@ -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(