mirror of https://github.com/dotnet/runtime
Merge bccae1d15f
into 02596ba8d9
This commit is contained in:
commit
aaa20cf58d
|
@ -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
|
||||
{
|
||||
|
|
|
@ -262,3 +262,9 @@ FCIMPL0(OBJECTREF, RhpGetNextFinalizableObject)
|
|||
}
|
||||
}
|
||||
FCIMPLEND
|
||||
|
||||
FCIMPL0(FC_BOOL_RET, RhpCurrentThreadIsFinalizerThread)
|
||||
{
|
||||
FC_RETURN_BOOL(ThreadStore::GetCurrentThread() == g_pFinalizerThread);
|
||||
}
|
||||
FCIMPLEND
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -517,5 +517,10 @@ namespace System.Threading
|
|||
}
|
||||
s_allDone.WaitOne();
|
||||
}
|
||||
|
||||
internal static bool CurrentThreadIsFinalizerThread()
|
||||
{
|
||||
return RuntimeImports.RhpCurrentThreadIsFinalizerThread();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"/>.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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*);
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "pal_types.h"
|
||||
|
||||
typedef struct LowLevelMonitor LowLevelMonitor;
|
||||
typedef struct LowLevelCrossProcessMutex LowLevelCrossProcessMutex;
|
||||
|
||||
PALEXPORT LowLevelMonitor *SystemNative_LowLevelMonitor_Create(void);
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "pal_config.h"
|
||||
#include "pal_threading.h"
|
||||
#include "pal_errno.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue