Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@
// create unique identities for every test to allow every test to have
// it's own store.
[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true)]
[assembly: ActiveIssue("https://github.com/dotnet/runtime/issues/48720", TestPlatforms.AnyUnix, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[assembly: SkipOnPlatform(TestPlatforms.Browser, "System.IO.IsolatedStorage is not supported on Browser")]
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ public sealed partial class Mutex
{
private void CreateMutexCore(bool initiallyOwned, string? name, out bool createdNew)
{
// See https://github.com/dotnet/runtime/issues/48720
if (name != null)
{
throw new PlatformNotSupportedException(SR.PlatformNotSupported_NamedSynchronizationPrimitives);
SafeWaitHandle? safeWaitHandle = WaitSubsystem.CreateNamedMutex(initiallyOwned, name, out createdNew);
if (safeWaitHandle == null)
{
throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name));
}
SafeWaitHandle = safeWaitHandle;
return;
}

SafeWaitHandle = WaitSubsystem.NewMutex(initiallyOwned);
Expand All @@ -24,7 +29,9 @@ private void CreateMutexCore(bool initiallyOwned, string? name, out bool created

private static OpenExistingResult OpenExistingWorker(string name, out Mutex? result)
{
throw new PlatformNotSupportedException(SR.PlatformNotSupported_NamedSynchronizationPrimitives);
OpenExistingResult status = WaitSubsystem.OpenNamedMutex(name, out SafeWaitHandle? safeWaitHandle);
result = status == OpenExistingResult.Success ? new Mutex(safeWaitHandle!) : null;
return status;
}

public void ReleaseMutex()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,53 @@ public static SafeWaitHandle NewMutex(bool initiallyOwned)
return safeWaitHandle;
}

public static SafeWaitHandle? CreateNamedMutex(bool initiallyOwned, string name, out bool createdNew)
{
// For initially owned, newly created named mutexes, there is a potential race
// between adding the mutex to the named object table and initially acquiring it.
// To avoid the possibility of another thread retrieving the mutex via its name
// before we managed to acquire it, we perform both steps while holding s_lock.
s_lock.Acquire();
bool holdingLock = true;
try
{
WaitableObject? waitableObject = WaitableObject.CreateNamedMutex_Locked(name, out createdNew);
if (waitableObject == null)
{
return null;
}
SafeWaitHandle safeWaitHandle = NewHandle(waitableObject);
if (!initiallyOwned || !createdNew)
{
return safeWaitHandle;
}

// Acquire the mutex. A thread's <see cref="ThreadWaitInfo"/> has a reference to all <see cref="Mutex"/>es locked
// by the thread. See <see cref="ThreadWaitInfo.LockedMutexesHead"/>. So, acquire the lock only after all
// possibilities for exceptions have been exhausted.
ThreadWaitInfo waitInfo = Thread.CurrentThread.WaitInfo;
int status = waitableObject.Wait_Locked(waitInfo, timeoutMilliseconds: 0, interruptible: false, prioritize: false);
Debug.Assert(status == 0);
// Wait_Locked has already released s_lock, so we no longer hold it here.
holdingLock = false;
return safeWaitHandle;
}
finally
{
if (holdingLock)
{
s_lock.Release();
}
}
}

public static OpenExistingResult OpenNamedMutex(string name, out SafeWaitHandle? result)
{
OpenExistingResult status = WaitableObject.OpenNamedMutex(name, out WaitableObject? mutex);
result = status == OpenExistingResult.Success ? NewHandle(mutex!) : null;
return status;
}

public static void DeleteHandle(IntPtr handle)
{
HandleManager.DeleteHandle(handle);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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;

namespace System.Threading
Expand All @@ -14,9 +15,17 @@ internal static partial class WaitSubsystem
/// </summary>
public sealed class WaitableObject
{
/// <summary>
/// Dictionary to look up named waitable objects. This implementation only supports in-process
/// named waitable objects. Currently only named mutexes are supported.
/// </summary>
private static Dictionary<string, WaitableObject>? s_namedObjects;

private readonly WaitableObjectType _type;
private int _signalCount;
private readonly int _maximumSignalCount;
private int _referenceCount;
private readonly string? _name;

/// <summary>
/// Only <see cref="Mutex"/> has a thread ownership requirement, and it's a less common type to be used, so
Expand All @@ -33,6 +42,7 @@ private WaitableObject(
WaitableObjectType type,
int initialSignalCount,
int maximumSignalCount,
string? name,
OwnershipInfo? ownershipInfo)
{
Debug.Assert(initialSignalCount >= 0);
Expand All @@ -42,6 +52,8 @@ private WaitableObject(
_type = type;
_signalCount = initialSignalCount;
_maximumSignalCount = maximumSignalCount;
_referenceCount = 1;
_name = name;
_ownershipInfo = ownershipInfo;
}

Expand All @@ -56,24 +68,87 @@ public static WaitableObject NewEvent(bool initiallySignaled, EventResetMode res
: WaitableObjectType.AutoResetEvent,
initiallySignaled ? 1 : 0,
1,
null,
null);
}

public static WaitableObject NewSemaphore(int initialSignalCount, int maximumSignalCount)
{
return new WaitableObject(WaitableObjectType.Semaphore, initialSignalCount, maximumSignalCount, null);
return new WaitableObject(WaitableObjectType.Semaphore, initialSignalCount, maximumSignalCount, null, null);
}

public static WaitableObject NewMutex()
{
return new WaitableObject(WaitableObjectType.Mutex, 1, 1, new OwnershipInfo());
return new WaitableObject(WaitableObjectType.Mutex, 1, 1, null, new OwnershipInfo());
}

public static WaitableObject? CreateNamedMutex_Locked(string name, out bool createdNew)
{
s_lock.VerifyIsLocked();

s_namedObjects ??= new Dictionary<string, WaitableObject>();

if (s_namedObjects.TryGetValue(name, out WaitableObject? result))
{
createdNew = false;
if (!result.IsMutex)
{
return null;
}
result._referenceCount++;
}
else
{
createdNew = true;
result = new WaitableObject(WaitableObjectType.Mutex, 1, 1, name, new OwnershipInfo());
s_namedObjects.Add(name, result);
}

return result;
}

public static OpenExistingResult OpenNamedMutex(string name, out WaitableObject? result)
{
s_lock.Acquire();
try
{
if (s_namedObjects == null || !s_namedObjects.TryGetValue(name, out result))
{
result = null;
return OpenExistingResult.NameNotFound;
}
if (!result.IsMutex)
{
result = null;
return OpenExistingResult.NameInvalid;
}
result._referenceCount++;
return OpenExistingResult.Success;
}
finally
{
s_lock.Release();
}
}

public void OnDeleteHandle()
{
s_lock.Acquire();
try
{
// Multiple handles may refer to the same named object. Make sure the object
// is only abandoned once the last handle to it is deleted. Also, remove the
// object from the named objects dictionary at this point.
_referenceCount--;
if (_referenceCount > 0)
{
return;
}
if (_name != null)
{
s_namedObjects!.Remove(_name);
}

if (IsMutex && !IsSignaled)
{
// A thread has a reference to all <see cref="Mutex"/>es locked by it, see
Expand Down
6 changes: 0 additions & 6 deletions src/libraries/System.Threading/tests/MutexTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ public void Ctor_InvalidNames_Unix()
}

[Theory]
[ActiveIssue("https://github.com/dotnet/runtime/issues/48720", TestPlatforms.AnyUnix, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[MemberData(nameof(GetValidNames))]
public void Ctor_ValidName(string name)
{
Expand Down Expand Up @@ -97,7 +96,6 @@ public void Ctor_TryCreateGlobalMutexTest_Uwp()
}

[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/48720", TestPlatforms.AnyUnix, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[MemberData(nameof(GetValidNames))]
public void OpenExisting(string name)
{
Expand Down Expand Up @@ -132,7 +130,6 @@ public void OpenExisting_InvalidNames()
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/48720", TestPlatforms.AnyUnix, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public void OpenExisting_UnavailableName()
{
string name = Guid.NewGuid().ToString("N");
Expand Down Expand Up @@ -228,7 +225,6 @@ public static IEnumerable<object[]> AbandonExisting_MemberData()
}

[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/48720", TestPlatforms.AnyUnix, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[MemberData(nameof(AbandonExisting_MemberData))]
public void AbandonExisting(
string name,
Expand Down Expand Up @@ -469,7 +465,6 @@ private static void IncrementValueInFileNTimes(Mutex mutex, string fileName, int
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/48720", TestPlatforms.AnyUnix, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public void NamedMutex_ThreadExitDisposeRaceTest()
{
var mutexName = Guid.NewGuid().ToString("N");
Expand Down Expand Up @@ -530,7 +525,6 @@ public void NamedMutex_ThreadExitDisposeRaceTest()
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/48720", TestPlatforms.AnyUnix, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
public void NamedMutex_DisposeWhenLockedRaceTest()
{
var mutexName = Guid.NewGuid().ToString("N");
Expand Down