* Add methods to ref file.
* Add empty methods to src files.
* Add the .NET Framework version of these methods for .NET Standard.
* Move OpenExistingResult enum to Common and consume it where needed.
* Remove Unix comment in Windows-only file.
* Document OpenExistingResult enum.
* Make out result parameters nullable.
* Add exception resource string.
* Imlement EventWaitHandleAcl methods.
* Implement MutexAcl methods.
* Implement SemaphoreAcl methods.
* Remove unnecessary check for null or empty name.
* Document the EventWaitHandleAcl methods.
* Document the MutexAcl methods.
* Document the SemaphoreAcl methods.
* Add negative enum check. Fix incorrect cast.
* Add NotNullWhen attribute to TryOpenExisting out parameter. Adjust docs.
* Add EventWaitHandle basic unit tests.
* Add Semaphore basic unit tests
* Add Mutex basic unit tests.
* Add Mutex exception handling unit tests.
* Add EventWaitHandle exception handling unit tests.
* Add Semaphore exception handling unit tests.
* EventWaitHandle and Mutex throw DirectoryNotFoundException when PathNotFound. Adjust documentation.
* Nullability in ref file.
* Do not check for rights out of range value, let Windows handle it. Adjust unit tests accordingly.
* Spacing.
* Remove enum range test. Add PathNotFound tests.
Co-authored-by: Carlos Sanchez Lopez <carlossanlop@users.noreply.github.com>
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Threading
+{
+ /// <summary>
+ /// Mapping of the different success or failure values that can occur when p/invoking Interop.Kernel32.OpenEvent,
+ /// which is called inside the OpenExistingWorker methods defined in EventWaitHandle, Mutex and Semaphore.
+ /// </summary>
+ internal enum OpenExistingResult
+ {
+ Success,
+ NameNotFound,
+ PathNotFound,
+ NameInvalid
+ }
+}
<Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs">
<Link>Common\System\Text\ValueStringBuilder.cs</Link>
</Compile>
+ <Compile Include="$(CommonPath)System\Threading\OpenExistingResult.cs">
+ <Link>Common\System\Threading\OpenExistingResult.cs</Link>
+ </Compile>
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs">
<Link>Common\System\Threading\Tasks\TaskToApm.cs</Link>
</Compile>
return OpenExistingResult.NameNotFound;
if (errorCode == Interop.Errors.ERROR_PATH_NOT_FOUND)
return OpenExistingResult.PathNotFound;
- if (!string.IsNullOrEmpty(name) && errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
+ if (errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
return OpenExistingResult.NameInvalid;
throw Win32Marshal.GetExceptionForWin32Error(errorCode, name);
return OpenExistingResult.NameNotFound;
if (errorCode == Interop.Errors.ERROR_PATH_NOT_FOUND)
return OpenExistingResult.PathNotFound;
- if (!string.IsNullOrEmpty(name) && errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
+ if (errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
return OpenExistingResult.NameInvalid;
// this is for passed through NativeMethods Errors
throw Win32Marshal.GetExceptionForLastWin32Error();
[ThreadStatic]
private static SafeWaitHandle?[]? t_safeWaitHandlesForRent;
- private protected enum OpenExistingResult
- {
- Success,
- NameNotFound,
- PathNotFound,
- NameInvalid
- }
-
// The wait result values below match Win32 wait result codes (WAIT_OBJECT_0,
// WAIT_ABANDONED, WAIT_TIMEOUT).
public static partial class EventWaitHandleAcl
{
public static System.Threading.EventWaitHandle Create(bool initialState, System.Threading.EventResetMode mode, string? name, out bool createdNew, System.Security.AccessControl.EventWaitHandleSecurity? eventSecurity) { throw null; }
+ public static System.Threading.EventWaitHandle OpenExisting(string name, System.Security.AccessControl.EventWaitHandleRights rights) { throw null; }
+ public static bool TryOpenExisting(string name, System.Security.AccessControl.EventWaitHandleRights rights, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Threading.EventWaitHandle? result) { throw null; }
}
public static partial class MutexAcl
{
public static System.Threading.Mutex Create(bool initiallyOwned, string? name, out bool createdNew, System.Security.AccessControl.MutexSecurity? mutexSecurity) { throw null; }
+ public static System.Threading.Mutex OpenExisting(string name, System.Security.AccessControl.MutexRights rights) { throw null; }
+ public static bool TryOpenExisting(string name, System.Security.AccessControl.MutexRights rights, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Threading.Mutex? result) { throw null; }
}
public static partial class SemaphoreAcl
{
public static System.Threading.Semaphore Create(int initialCount, int maximumCount, string? name, out bool createdNew, System.Security.AccessControl.SemaphoreSecurity? semaphoreSecurity) { throw null; }
+ public static System.Threading.Semaphore OpenExisting(string name, System.Security.AccessControl.SemaphoreRights rights) { throw null; }
+ public static bool TryOpenExisting(string name, System.Security.AccessControl.SemaphoreRights rights, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Threading.Semaphore? result) { throw null; }
}
public static partial class ThreadingAclExtensions
{
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
+ <data name="ArgumentOutOfRange_Enum" xml:space="preserve">
+ <value>Enum value was out of legal range.</value>
+ </data>
<data name="ArgumentOutOfRange_NeedNonNegNum" xml:space="preserve">
<value>Non-negative number required.</value>
</data>
<data name="ArgumentOutOfRange_NeedPosNum" xml:space="preserve">
<value>Positive number required.</value>
</data>
- <data name="Argument_SemaphoreInitialMaximum" xml:space="preserve">
- <value>The initial count for the semaphore must be greater than or equal to zero and less than the maximum count.</value>
- </data>
<data name="Argument_CannotBeNullOrEmpty" xml:space="preserve">
<value>Argument cannot be null or empty.</value>
</data>
+ <data name="Argument_EmptyName" xml:space="preserve">
+ <value>Empty name is not legal.</value>
+ </data>
+ <data name="Argument_SemaphoreInitialMaximum" xml:space="preserve">
+ <value>The initial count for the semaphore must be greater than or equal to zero and less than the maximum count.</value>
+ </data>
<data name="Argument_WaitHandleNameTooLong" xml:space="preserve">
<value>The length of the name exceeds the maximum limit.</value>
</data>
- <data name="ArgumentOutOfRange_Enum" xml:space="preserve">
- <value>Enum value was out of legal range.</value>
- </data>
<data name="IO_AlreadyExists_Name" xml:space="preserve">
<value>Cannot create '{0}' because a file or directory with the same name already exists.</value>
</data>
<data name="WaitHandleCannotBeOpenedException_InvalidHandle" xml:space="preserve">
<value>A WaitHandle with system-wide name '{0}' cannot be created. A WaitHandle of a different type might have the same name.</value>
</data>
-</root>
\ No newline at end of file
+</root>
Link="Interop\Windows\Kernel32\Interop.Semaphore.cs" />
<Compile Include="$(CommonPath)System\IO\Win32Marshal.cs"
Link="Common\System\IO\Win32Marshal.cs" />
+ <Compile Include="$(CommonPath)System\Threading\OpenExistingResult.cs"
+ Link="Common\System\Threading\OpenExistingResult.cs" />
<Compile Include="System\Security\AccessControl\MutexSecurity.cs" />
<Compile Include="System\Security\AccessControl\EventWaitHandleSecurity.cs" />
<Compile Include="System\Security\AccessControl\SemaphoreSecurity.cs" />
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
eventFlags,
(uint)EventWaitHandleRights.FullControl);
- ValidateHandle(handle, name, out createdNew);
+ int errorCode = Marshal.GetLastWin32Error();
- EventWaitHandle ewh = new EventWaitHandle(initialState, mode);
- SafeWaitHandle old = ewh.SafeWaitHandle;
- ewh.SafeWaitHandle = handle;
- old.Dispose();
+ if (handle.IsInvalid)
+ {
+ handle.SetHandleAsInvalid();
+
+ if (!string.IsNullOrEmpty(name) && errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
+ {
+ throw new WaitHandleCannotBeOpenedException(SR.Format(SR.WaitHandleCannotBeOpenedException_InvalidHandle, name));
+ }
+
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode, name);
+ }
+
+ createdNew = (errorCode != Interop.Errors.ERROR_ALREADY_EXISTS);
- return ewh;
+ return CreateAndReplaceHandle(handle);
}
}
- private static void ValidateHandle(SafeWaitHandle handle, string? name, out bool createdNew)
+ /// <summary>
+ /// Opens a specified named event wait handle, if it already exists, applying the desired access rights.
+ /// </summary>
+ /// <param name="name">The name of the event wait handle to be opened. If it's prefixed by "Global", it refers to a machine-wide event wait handle. If it's prefixed by "Local", or doesn't have a prefix, it refers to a session-wide event wait handle. Both prefix and name are case-sensitive.</param>
+ /// <param name="rights">The desired access rights to apply to the returned event wait handle.</param>
+ /// <returns>An existing named event wait handle.</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null" />.</exception>
+ /// <exception cref="ArgumentException"><paramref name="name"/> is an empty string.</exception>
+ /// <exception cref="WaitHandleCannotBeOpenedException">The named event wait handle does not exist or is invalid.</exception>
+ /// <exception cref="DirectoryNotFoundException">The path was not found.</exception>
+ /// <exception cref="IOException">A Win32 error occurred.</exception>
+ /// <exception cref="UnauthorizedAccessException">The named event wait handle exists, but the user does not have the security access required to use it.</exception>
+ public static EventWaitHandle OpenExisting(string name, EventWaitHandleRights rights)
{
- int errorCode = Marshal.GetLastWin32Error();
+ switch (OpenExistingWorker(name, rights, out EventWaitHandle? result))
+ {
+ case OpenExistingResult.NameNotFound:
+ throw new WaitHandleCannotBeOpenedException();
+
+ case OpenExistingResult.NameInvalid:
+ throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name));
- if (handle.IsInvalid)
+ case OpenExistingResult.PathNotFound:
+ throw new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, name));
+
+ case OpenExistingResult.Success:
+ default:
+ Debug.Assert(result != null, "result should be non-null on success");
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Tries to open a specified named event wait handle, if it already exists, applying the desired access rights, and returns a value that indicates whether the operation succeeded.
+ /// </summary>
+ /// <param name="name">The name of the event wait handle to be opened. If it's prefixed by "Global", it refers to a machine-wide event wait handle. If it's prefixed by "Local", or doesn't have a prefix, it refers to a session-wide event wait handle. Both prefix and name are case-sensitive.</param>
+ /// <param name="rights">The desired access rights to apply to the returned event wait handle.</param>
+ /// <param name="result">When this method returns <see langword="true" />, contains an object that represents the named event wait handle if the call succeeded, or <see langword="null" /> otherwise. This parameter is treated as uninitialized.</param>
+ /// <returns><see langword="true" /> if the named event wait handle was opened successfully; otherwise, <see langword="false" />.</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null" /></exception>
+ /// <exception cref="ArgumentException"><paramref name="name"/> is an empty string.</exception>
+ /// <exception cref="IOException">A Win32 error occurred.</exception>
+ /// <exception cref="UnauthorizedAccessException">The named event wait handle exists, but the user does not have the security access required to use it.</exception>
+ public static bool TryOpenExisting(string name, EventWaitHandleRights rights, [NotNullWhen(returnValue: true)] out EventWaitHandle? result) =>
+ OpenExistingWorker(name, rights, out result) == OpenExistingResult.Success;
+
+ private static OpenExistingResult OpenExistingWorker(string name, EventWaitHandleRights rights, out EventWaitHandle? result)
+ {
+ if (name == null)
{
- handle.SetHandleAsInvalid();
+ throw new ArgumentNullException(nameof(name));
+ }
+ if (name.Length == 0)
+ {
+ throw new ArgumentException(SR.Argument_EmptyName, nameof(name));
+ }
- if (!string.IsNullOrEmpty(name) && errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
- throw new WaitHandleCannotBeOpenedException(SR.Format(SR.WaitHandleCannotBeOpenedException_InvalidHandle, name));
+ result = null;
+ SafeWaitHandle existingHandle = Interop.Kernel32.OpenEvent((uint)rights, false, name);
- throw Win32Marshal.GetExceptionForWin32Error(errorCode, name);
+ int errorCode = Marshal.GetLastWin32Error();
+ if (existingHandle.IsInvalid)
+ {
+ return errorCode switch
+ {
+ Interop.Errors.ERROR_FILE_NOT_FOUND or Interop.Errors.ERROR_INVALID_NAME => OpenExistingResult.NameNotFound,
+ Interop.Errors.ERROR_PATH_NOT_FOUND => OpenExistingResult.PathNotFound,
+ Interop.Errors.ERROR_INVALID_HANDLE => OpenExistingResult.NameInvalid,
+ _ => throw Win32Marshal.GetExceptionForWin32Error(errorCode, name)
+ };
}
- createdNew = (errorCode != Interop.Errors.ERROR_ALREADY_EXISTS);
+ result = CreateAndReplaceHandle(existingHandle);
+
+ return OpenExistingResult.Success;
+ }
+
+ private static EventWaitHandle CreateAndReplaceHandle(SafeWaitHandle replacementHandle)
+ {
+ // The values of initialState and mode should not matter since we are replacing the
+ // handle with one from an existing EventWaitHandle, and disposing the old one
+ // We should only make sure that they are valid values
+ EventWaitHandle eventWaitHandle = new EventWaitHandle(initialState: default, mode: default);
+
+ SafeWaitHandle old = eventWaitHandle.SafeWaitHandle;
+ eventWaitHandle.SafeWaitHandle = replacementHandle;
+ old.Dispose();
+
+ return eventWaitHandle;
}
}
}
{
return new EventWaitHandle(initialState, mode, name, out createdNew, eventSecurity);
}
+
+ public static EventWaitHandle OpenExisting(string name, EventWaitHandleRights rights)
+ {
+ return EventWaitHandle.OpenExisting(name, rights);
+ }
+
+ public static bool TryOpenExisting(string name, EventWaitHandleRights rights, out EventWaitHandle result)
+ {
+ return EventWaitHandle.TryOpenExisting(name, rights, out result);
+ }
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
(uint)MutexRights.FullControl // Equivalent to MUTEX_ALL_ACCESS
);
- ValidateMutexHandle(handle, name, out createdNew);
+ int errorCode = Marshal.GetLastWin32Error();
- Mutex mutex = new Mutex(initiallyOwned);
- SafeWaitHandle old = mutex.SafeWaitHandle;
- mutex.SafeWaitHandle = handle;
- old.Dispose();
+ if (handle.IsInvalid)
+ {
+ handle.SetHandleAsInvalid();
+
+ if (errorCode == Interop.Errors.ERROR_FILENAME_EXCED_RANGE)
+ {
+ throw new ArgumentException(SR.Argument_WaitHandleNameTooLong, nameof(name));
+ }
+
+ if (errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
+ {
+ throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name));
+ }
+
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode, name);
+ }
- return mutex;
+ createdNew = (errorCode != Interop.Errors.ERROR_ALREADY_EXISTS);
+
+ return CreateAndReplaceHandle(handle);
}
}
- private static void ValidateMutexHandle(SafeWaitHandle mutexHandle, string? name, out bool createdNew)
+ /// <summary>
+ /// Opens a specified named mutex, if it already exists, applying the desired access rights.
+ /// </summary>
+ /// <param name="name">The name of the mutex to be opened. If it's prefixed by "Global", it refers to a machine-wide mutex. If it's prefixed by "Local", or doesn't have a prefix, it refers to a session-wide mutex. Both prefix and name are case-sensitive.</param>
+ /// <param name="rights">The desired access rights to apply to the returned mutex.</param>
+ /// <returns>An existing named mutex.</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null" />.</exception>
+ /// <exception cref="ArgumentException"><paramref name="name"/> is an empty string.</exception>
+ /// <exception cref="WaitHandleCannotBeOpenedException">The named mutex does not exist or is invalid.</exception>
+ /// <exception cref="DirectoryNotFoundException">The path was not found.</exception>
+ /// <exception cref="IOException">A Win32 error occurred.</exception>
+ /// <exception cref="UnauthorizedAccessException">The named mutex exists, but the user does not have the security access required to use it.</exception>
+ public static Mutex OpenExisting(string name, MutexRights rights)
{
- int errorCode = Marshal.GetLastWin32Error();
+ switch (OpenExistingWorker(name, rights, out Mutex? result))
+ {
+ case OpenExistingResult.NameNotFound:
+ throw new WaitHandleCannotBeOpenedException();
- if (mutexHandle.IsInvalid)
+ case OpenExistingResult.NameInvalid:
+ throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name));
+
+ case OpenExistingResult.PathNotFound:
+ throw new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, name));
+
+ case OpenExistingResult.Success:
+ default:
+ Debug.Assert(result != null, "result should be non-null on success");
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Tries to open a specified named mutex, if it already exists, applying the desired access rights, and returns a value that indicates whether the operation succeeded.
+ /// </summary>
+ /// <param name="name">The name of the mutex to be opened. If it's prefixed by "Global", it refers to a machine-wide mutex. If it's prefixed by "Local", or doesn't have a prefix, it refers to a session-wide mutex. Both prefix and name are case-sensitive.</param>
+ /// <param name="rights">The desired access rights to apply to the returned mutex.</param>
+ /// <param name="result">When this method returns <see langword="true" />, contains an object that represents the named mutex if the call succeeded, or <see langword="null" /> otherwise. This parameter is treated as uninitialized.</param>
+ /// <returns><see langword="true" /> if the named mutex was opened successfully; otherwise, <see langword="false" />.</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null" /></exception>
+ /// <exception cref="ArgumentException"><paramref name="name"/> is an empty string.</exception>
+ /// <exception cref="IOException">A Win32 error occurred.</exception>
+ /// <exception cref="UnauthorizedAccessException">The named mutex exists, but the user does not have the security access required to use it.</exception>
+ public static bool TryOpenExisting(string name, MutexRights rights, [NotNullWhen(returnValue: true)] out Mutex? result) =>
+ OpenExistingWorker(name, rights, out result) == OpenExistingResult.Success;
+
+ private static OpenExistingResult OpenExistingWorker(string name, MutexRights rights, out Mutex? result)
+ {
+ if (name == null)
{
- mutexHandle.SetHandleAsInvalid();
+ throw new ArgumentNullException(nameof(name));
+ }
+ if (name.Length == 0)
+ {
+ throw new ArgumentException(SR.Argument_EmptyName, nameof(name));
+ }
- if (errorCode == Interop.Errors.ERROR_FILENAME_EXCED_RANGE)
- {
- // On Unix, length validation is done by CoreCLR's PAL after converting to utf-8
- throw new ArgumentException(SR.Argument_WaitHandleNameTooLong, nameof(name));
- }
+ result = null;
+ SafeWaitHandle existingHandle = Interop.Kernel32.OpenMutex((uint)rights, false, name);
- if (errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
+ int errorCode = Marshal.GetLastWin32Error();
+ if (existingHandle.IsInvalid)
+ {
+ return errorCode switch
{
- throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name));
- }
-
- throw Win32Marshal.GetExceptionForWin32Error(errorCode, name);
+ Interop.Errors.ERROR_FILE_NOT_FOUND or Interop.Errors.ERROR_INVALID_NAME => OpenExistingResult.NameNotFound,
+ Interop.Errors.ERROR_PATH_NOT_FOUND => OpenExistingResult.PathNotFound,
+ Interop.Errors.ERROR_INVALID_HANDLE => OpenExistingResult.NameInvalid,
+ _ => throw Win32Marshal.GetExceptionForWin32Error(errorCode, name)
+ };
}
- createdNew = (errorCode != Interop.Errors.ERROR_ALREADY_EXISTS);
+ result = CreateAndReplaceHandle(existingHandle);
+
+ return OpenExistingResult.Success;
+ }
+
+ private static Mutex CreateAndReplaceHandle(SafeWaitHandle replacementHandle)
+ {
+ // The value of initiallyOwned should not matter since we are replacing the
+ // handle with one from an existing Mutex, and disposing the old one
+ // We should only make sure that it is a valid value
+ Mutex mutex = new Mutex(initiallyOwned: default);
+
+ SafeWaitHandle old = mutex.SafeWaitHandle;
+ mutex.SafeWaitHandle = replacementHandle;
+ old.Dispose();
+
+ return mutex;
}
}
}
{
return new Mutex(initiallyOwned, name, out createdNew, mutexSecurity);
}
+
+ public static Mutex OpenExisting(string name, MutexRights rights)
+ {
+ return Mutex.OpenExisting(name, rights);
+ }
+
+ public static bool TryOpenExisting(string name, MutexRights rights, out Mutex result)
+ {
+ return Mutex.TryOpenExisting(name, rights, out result);
+ }
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
(uint)SemaphoreRights.FullControl // Equivalent to SEMAPHORE_ALL_ACCESS
);
- ValidateHandle(handle, name, out createdNew);
+ int errorCode = Marshal.GetLastWin32Error();
- Semaphore semaphore = new Semaphore(initialCount, maximumCount);
- SafeWaitHandle old = semaphore.SafeWaitHandle;
- semaphore.SafeWaitHandle = handle;
- old.Dispose();
+ if (handle.IsInvalid)
+ {
+ if (!string.IsNullOrEmpty(name) && errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
+ {
+ throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name));
+ }
+
+ throw Win32Marshal.GetExceptionForLastWin32Error();
+ }
+
+ createdNew = (errorCode != Interop.Errors.ERROR_ALREADY_EXISTS);
- return semaphore;
+ return CreateAndReplaceHandle(handle);
}
}
- private static void ValidateHandle(SafeWaitHandle handle, string? name, out bool createdNew)
+ /// <summary>
+ /// Opens a specified named semaphore, if it already exists, applying the desired access rights.
+ /// </summary>
+ /// <param name="name">The name of the semaphore to be opened. If it's prefixed by "Global", it refers to a machine-wide semaphore. If it's prefixed by "Local", or doesn't have a prefix, it refers to a session-wide semaphore. Both prefix and name are case-sensitive.</param>
+ /// <param name="rights">The desired access rights to apply to the returned semaphore.</param>
+ /// <returns>An existing named semaphore.</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null" />.</exception>
+ /// <exception cref="ArgumentException"><paramref name="name"/> is an empty string.</exception>
+ /// <exception cref="WaitHandleCannotBeOpenedException">The named semaphore does not exist or is invalid.</exception>
+ /// <exception cref="IOException">The path was not found.
+ /// -or-
+ /// A Win32 error occurred.</exception>
+ /// <exception cref="UnauthorizedAccessException">The named semaphore exists, but the user does not have the security access required to use it.</exception>
+ public static Semaphore OpenExisting(string name, SemaphoreRights rights)
{
- int errorCode = Marshal.GetLastWin32Error();
+ switch (OpenExistingWorker(name, rights, out Semaphore? result))
+ {
+ case OpenExistingResult.NameNotFound:
+ throw new WaitHandleCannotBeOpenedException();
+
+ case OpenExistingResult.NameInvalid:
+ throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name));
+
+ case OpenExistingResult.PathNotFound:
+ throw new IOException(SR.Format(SR.IO_PathNotFound_Path, name));
+
+ case OpenExistingResult.Success:
+ default:
+ Debug.Assert(result != null, "result should be non-null on success");
+ return result;
+ }
+ }
+
+ /// <summary>
+ /// Tries to open a specified named semaphore, if it already exists, applying the desired access rights, and returns a value that indicates whether the operation succeeded.
+ /// </summary>
+ /// <param name="name">The name of the semaphore to be opened. If it's prefixed by "Global", it refers to a machine-wide semaphore. If it's prefixed by "Local", or doesn't have a prefix, it refers to a session-wide semaphore. Both prefix and name are case-sensitive.</param>
+ /// <param name="rights">The desired access rights to apply to the returned semaphore.</param>
+ /// <param name="result">When this method returns <see langword="true" />, contains an object that represents the named semaphore if the call succeeded, or <see langword="null" /> otherwise. This parameter is treated as uninitialized.</param>
+ /// <returns><see langword="true" /> if the named semaphore was opened successfully; otherwise, <see langword="false" />.</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null" /></exception>
+ /// <exception cref="ArgumentException"><paramref name="name"/> is an empty string.</exception>
+ /// <exception cref="IOException">A Win32 error occurred.</exception>
+ /// <exception cref="UnauthorizedAccessException">The named semaphore exists, but the user does not have the security access required to use it.</exception>
+ public static bool TryOpenExisting(string name, SemaphoreRights rights, [NotNullWhen(returnValue: true)] out Semaphore? result) =>
+ OpenExistingWorker(name, rights, out result) == OpenExistingResult.Success;
+ private static OpenExistingResult OpenExistingWorker(string name, SemaphoreRights rights, out Semaphore? result)
+ {
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+ if (name.Length == 0)
+ {
+ throw new ArgumentException(SR.Argument_EmptyName, nameof(name));
+ }
+
+ result = null;
+ SafeWaitHandle handle = Interop.Kernel32.OpenSemaphore((uint)rights, false, name);
+
+ int errorCode = Marshal.GetLastWin32Error();
if (handle.IsInvalid)
{
- if (!string.IsNullOrEmpty(name) && errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
+ return errorCode switch
{
- throw new WaitHandleCannotBeOpenedException(SR.Format(SR.Threading_WaitHandleCannotBeOpenedException_InvalidHandle, name));
- }
-
- throw Win32Marshal.GetExceptionForLastWin32Error();
+ Interop.Errors.ERROR_FILE_NOT_FOUND or Interop.Errors.ERROR_INVALID_NAME => OpenExistingResult.NameNotFound,
+ Interop.Errors.ERROR_PATH_NOT_FOUND => OpenExistingResult.PathNotFound,
+ Interop.Errors.ERROR_INVALID_HANDLE => OpenExistingResult.NameInvalid,
+ _ => throw Win32Marshal.GetExceptionForLastWin32Error()
+ };
}
- createdNew = errorCode != Interop.Errors.ERROR_ALREADY_EXISTS;
+ result = CreateAndReplaceHandle(handle);
+ return OpenExistingResult.Success;
+ }
+
+ private static Semaphore CreateAndReplaceHandle(SafeWaitHandle replacementHandle)
+ {
+ // The values of initialCount and maximumCount should not matter since we are replacing the
+ // handle with one from an existing Semaphore, and disposing the old one
+ // We should only make sure that they are valid values
+ Semaphore semaphore = new Semaphore(1, 2);
+
+ SafeWaitHandle old = semaphore.SafeWaitHandle;
+ semaphore.SafeWaitHandle = replacementHandle;
+ old.Dispose();
+
+ return semaphore;
}
}
}
{
return new Semaphore(initialCount, maximumCount, name, out createdNew, semaphoreSecurity);
}
+
+ public static Semaphore OpenExisting(string name, SemaphoreRights rights)
+ {
+ return Semaphore.OpenExisting(name, rights);
+ }
+
+ public static bool TryOpenExisting(string name, SemaphoreRights rights, out Semaphore result)
+ {
+ return Semaphore.TryOpenExisting(name, rights, out result);
+ }
}
}
}
+ [Fact]
+ public void EventWaitHandle_OpenExisting()
+ {
+ string name = GetRandomName();
+ EventWaitHandleSecurity expectedSecurity = GetEventWaitHandleSecurity(WellKnownSidType.BuiltinUsersSid, EventWaitHandleRights.FullControl, AccessControlType.Allow);
+ using EventWaitHandle EventWaitHandleNew = CreateAndVerifyEventWaitHandle(initialState: true, EventResetMode.AutoReset, name, expectedSecurity, expectedCreatedNew: true);
+
+ using EventWaitHandle EventWaitHandleExisting = EventWaitHandleAcl.OpenExisting(name, EventWaitHandleRights.FullControl);
+
+ VerifyHandles(EventWaitHandleNew, EventWaitHandleExisting);
+ EventWaitHandleSecurity actualSecurity = EventWaitHandleExisting.GetAccessControl();
+ VerifyEventWaitHandleSecurity(expectedSecurity, actualSecurity);
+ }
+
+ [Fact]
+ public void EventWaitHandle_TryOpenExisting()
+ {
+ string name = GetRandomName();
+ EventWaitHandleSecurity expectedSecurity = GetEventWaitHandleSecurity(WellKnownSidType.BuiltinUsersSid, EventWaitHandleRights.FullControl, AccessControlType.Allow);
+ using EventWaitHandle EventWaitHandleNew = CreateAndVerifyEventWaitHandle(initialState: true, EventResetMode.AutoReset, name, expectedSecurity, expectedCreatedNew: true);
+
+ Assert.True(EventWaitHandleAcl.TryOpenExisting(name, EventWaitHandleRights.FullControl, out EventWaitHandle EventWaitHandleExisting));
+ Assert.NotNull(EventWaitHandleExisting);
+
+ VerifyHandles(EventWaitHandleNew, EventWaitHandleExisting);
+ EventWaitHandleSecurity actualSecurity = EventWaitHandleExisting.GetAccessControl();
+ VerifyEventWaitHandleSecurity(expectedSecurity, actualSecurity);
+
+ EventWaitHandleExisting.Dispose();
+ }
+
+ [Fact]
+ public void EventWaitHandle_OpenExisting_NameNotFound()
+ {
+ string name = "ThisShouldNotExist";
+ Assert.Throws<WaitHandleCannotBeOpenedException>(() =>
+ {
+ EventWaitHandleAcl.OpenExisting(name, EventWaitHandleRights.FullControl).Dispose();
+ });
+
+ Assert.False(EventWaitHandleAcl.TryOpenExisting(name, EventWaitHandleRights.FullControl, out _));
+ }
+
+ [Fact]
+ public void EventWaitHandle_OpenExisting_NameInvalid()
+ {
+ string name = '\0'.ToString();
+ Assert.Throws<WaitHandleCannotBeOpenedException>(() =>
+ {
+ EventWaitHandleAcl.OpenExisting(name, EventWaitHandleRights.FullControl).Dispose();
+ });
+
+ Assert.False(EventWaitHandleAcl.TryOpenExisting(name, EventWaitHandleRights.FullControl, out _));
+ }
+
+ [Fact]
+ public void EventWaitHandle_OpenExisting_PathNotFound()
+ {
+ string name = @"global\foo";
+ Assert.Throws<DirectoryNotFoundException>(() =>
+ {
+ EventWaitHandleAcl.OpenExisting(name, EventWaitHandleRights.FullControl).Dispose();
+ });
+
+ Assert.False(EventWaitHandleAcl.TryOpenExisting(name, EventWaitHandleRights.FullControl, out _));
+ }
+
+ [Fact]
+ public void EventWaitHandle_OpenExisting_BadPathName()
+ {
+ string name = @"\\?\Path";
+ Assert.Throws<System.IO.IOException>(() =>
+ {
+ EventWaitHandleAcl.OpenExisting(name, EventWaitHandleRights.FullControl).Dispose();
+ });
+
+ Assert.Throws<System.IO.IOException>(() =>
+ {
+ EventWaitHandleAcl.TryOpenExisting(name, EventWaitHandleRights.FullControl, out _);
+ });
+ }
+
+ [Fact]
+ public void EventWaitHandle_OpenExisting_NullName()
+ {
+ Assert.Throws<ArgumentNullException>(() =>
+ {
+ EventWaitHandleAcl.OpenExisting(null, EventWaitHandleRights.FullControl).Dispose();
+ });
+
+ Assert.Throws<ArgumentNullException>(() =>
+ {
+ EventWaitHandleAcl.TryOpenExisting(null, EventWaitHandleRights.FullControl, out _);
+ });
+ }
+
+ [Fact]
+ public void EventWaitHandle_OpenExisting_EmptyName()
+ {
+ Assert.Throws<ArgumentException>(() =>
+ {
+ EventWaitHandleAcl.OpenExisting(string.Empty, EventWaitHandleRights.FullControl).Dispose();
+ });
+
+ Assert.Throws<ArgumentException>(() =>
+ {
+ EventWaitHandleAcl.TryOpenExisting(string.Empty, EventWaitHandleRights.FullControl, out _);
+ });
+ }
+
private EventWaitHandleSecurity GetBasicEventWaitHandleSecurity()
{
return GetEventWaitHandleSecurity(
security.AddAccessRule(accessRule);
return security;
}
+
private EventWaitHandle CreateEventWaitHandle(bool initialState, EventResetMode mode, string name, EventWaitHandleSecurity expectedSecurity, bool expectedCreatedNew)
{
EventWaitHandle handle = EventWaitHandleAcl.Create(initialState, mode, name, out bool createdNew, expectedSecurity);
return eventHandle;
}
+ private void VerifyHandles(EventWaitHandle expected, EventWaitHandle actual)
+ {
+ Assert.NotNull(expected.SafeWaitHandle);
+ Assert.NotNull(actual.SafeWaitHandle);
+
+ Assert.False(expected.SafeWaitHandle.IsClosed);
+ Assert.False(actual.SafeWaitHandle.IsClosed);
+
+ Assert.False(expected.SafeWaitHandle.IsInvalid);
+ Assert.False(actual.SafeWaitHandle.IsInvalid);
+ }
+
private void VerifyEventWaitHandleSecurity(EventWaitHandleSecurity expectedSecurity, EventWaitHandleSecurity actualSecurity)
{
Assert.Equal(typeof(EventWaitHandleRights), expectedSecurity.AccessRightType);
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
}
}
+ public static IEnumerable<object[]> GetMutexSpecificParameters() =>
+ from initiallyOwned in new[] { false, true }
+ from rights in new[] { MutexRights.FullControl, MutexRights.Synchronize, MutexRights.Modify, MutexRights.Modify | MutexRights.Synchronize }
+ from accessControl in new[] { AccessControlType.Allow, AccessControlType.Deny }
+ select new object[] { initiallyOwned, rights, accessControl };
+
[Theory]
- [InlineData(true, MutexRights.FullControl, AccessControlType.Allow)]
- [InlineData(true, MutexRights.FullControl, AccessControlType.Deny)]
- [InlineData(true, MutexRights.Synchronize, AccessControlType.Allow)]
- [InlineData(true, MutexRights.Synchronize, AccessControlType.Deny)]
- [InlineData(true, MutexRights.Modify, AccessControlType.Allow)]
- [InlineData(true, MutexRights.Modify, AccessControlType.Deny)]
- [InlineData(true, MutexRights.Modify | MutexRights.Synchronize, AccessControlType.Allow)]
- [InlineData(true, MutexRights.Modify | MutexRights.Synchronize, AccessControlType.Deny)]
- [InlineData(false, MutexRights.FullControl, AccessControlType.Allow)]
- [InlineData(false, MutexRights.FullControl, AccessControlType.Deny)]
- [InlineData(false, MutexRights.Synchronize, AccessControlType.Allow)]
- [InlineData(false, MutexRights.Synchronize, AccessControlType.Deny)]
- [InlineData(false, MutexRights.Modify, AccessControlType.Allow)]
- [InlineData(false, MutexRights.Modify, AccessControlType.Deny)]
+ [MemberData(nameof(GetMutexSpecificParameters))]
public void Mutex_Create_SpecificParameters(bool initiallyOwned, MutexRights rights, AccessControlType accessControl)
{
MutexSecurity security = GetMutexSecurity(WellKnownSidType.BuiltinUsersSid, rights, accessControl);
CreateAndVerifyMutex(initiallyOwned, GetRandomName(), security, expectedCreatedNew: true).Dispose();
+ }
+
+ [Fact]
+ public void Mutex_OpenExisting()
+ {
+ string name = GetRandomName();
+ MutexSecurity expectedSecurity = GetMutexSecurity(WellKnownSidType.BuiltinUsersSid, MutexRights.FullControl, AccessControlType.Allow);
+ using Mutex mutexNew = CreateAndVerifyMutex(initiallyOwned: true, name, expectedSecurity, expectedCreatedNew: true);
+
+ using Mutex mutexExisting = MutexAcl.OpenExisting(name, MutexRights.FullControl);
+
+ VerifyHandles(mutexNew, mutexExisting);
+ MutexSecurity actualSecurity = mutexExisting.GetAccessControl();
+ VerifyMutexSecurity(expectedSecurity, actualSecurity);
+ }
+
+ [Fact]
+ public void Mutex_TryOpenExisting()
+ {
+ string name = GetRandomName();
+ MutexSecurity expectedSecurity = GetMutexSecurity(WellKnownSidType.BuiltinUsersSid, MutexRights.FullControl, AccessControlType.Allow);
+ using Mutex mutexNew = CreateAndVerifyMutex(initiallyOwned: true, name, expectedSecurity, expectedCreatedNew: true);
+
+ Assert.True(MutexAcl.TryOpenExisting(name, MutexRights.FullControl, out Mutex mutexExisting));
+ Assert.NotNull(mutexExisting);
+
+ VerifyHandles(mutexNew, mutexExisting);
+ MutexSecurity actualSecurity = mutexExisting.GetAccessControl();
+ VerifyMutexSecurity(expectedSecurity, actualSecurity);
+ mutexExisting.Dispose();
+ }
+
+ [Fact]
+ public void Mutex_OpenExisting_NameNotFound()
+ {
+ string name = "ThisShouldNotExist";
+ Assert.Throws<WaitHandleCannotBeOpenedException>(() =>
+ {
+ MutexAcl.OpenExisting(name, MutexRights.FullControl).Dispose();
+ });
+
+ Assert.False(MutexAcl.TryOpenExisting(name, MutexRights.FullControl, out _));
+ }
+
+ [Fact]
+ public void Mutex_OpenExisting_NameInvalid()
+ {
+ string name = '\0'.ToString();
+ Assert.Throws<WaitHandleCannotBeOpenedException>(() =>
+ {
+ MutexAcl.OpenExisting(name, MutexRights.FullControl).Dispose();
+ });
+
+ Assert.False(MutexAcl.TryOpenExisting(name, MutexRights.FullControl, out _));
+ }
+
+
+ [Fact]
+ public void Mutex_OpenExisting_PathNotFound()
+ {
+ string name = @"global\foo";
+ Assert.Throws<DirectoryNotFoundException>(() =>
+ {
+ MutexAcl.OpenExisting(name, MutexRights.FullControl).Dispose();
+ });
+
+ Assert.False(MutexAcl.TryOpenExisting(name, MutexRights.FullControl, out _));
+ }
+
+ [Fact]
+ public void Mutex_OpenExisting_BadPathName()
+ {
+ string name = @"\\?\Path";
+ Assert.Throws<System.IO.IOException>(() =>
+ {
+ MutexAcl.OpenExisting(name, MutexRights.FullControl).Dispose();
+ });
+ Assert.Throws<System.IO.IOException>(() =>
+ {
+ MutexAcl.TryOpenExisting(name, MutexRights.FullControl, out _);
+ });
+ }
+
+ [Fact]
+ public void Mutex_OpenExisting_NullName()
+ {
+ Assert.Throws<ArgumentNullException>(() =>
+ {
+ MutexAcl.OpenExisting(null, MutexRights.FullControl).Dispose();
+ });
+
+ Assert.Throws<ArgumentNullException>(() =>
+ {
+ MutexAcl.TryOpenExisting(null, MutexRights.FullControl, out _);
+ });
+ }
+
+ [Fact]
+ public void Mutex_OpenExisting_EmptyName()
+ {
+ Assert.Throws<ArgumentException>(() =>
+ {
+ MutexAcl.OpenExisting(string.Empty, MutexRights.FullControl).Dispose();
+ });
+
+ Assert.Throws<ArgumentException>(() =>
+ {
+ MutexAcl.TryOpenExisting(string.Empty, MutexRights.FullControl, out _);
+ });
}
private MutexSecurity GetBasicMutexSecurity()
return mutex;
}
+ private void VerifyHandles(Mutex expected, Mutex actual)
+ {
+ Assert.NotNull(expected.SafeWaitHandle);
+ Assert.NotNull(actual.SafeWaitHandle);
+
+ Assert.False(expected.SafeWaitHandle.IsClosed);
+ Assert.False(actual.SafeWaitHandle.IsClosed);
+
+ Assert.False(expected.SafeWaitHandle.IsInvalid);
+ Assert.False(actual.SafeWaitHandle.IsInvalid);
+ }
+
private void VerifyMutexSecurity(MutexSecurity expectedSecurity, MutexSecurity actualSecurity)
{
Assert.Equal(typeof(MutexRights), expectedSecurity.AccessRightType);
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
}
+ [Fact]
+ public void Semaphore_OpenExisting()
+ {
+ string name = GetRandomName();
+ SemaphoreSecurity expectedSecurity = GetSemaphoreSecurity(WellKnownSidType.BuiltinUsersSid, SemaphoreRights.FullControl, AccessControlType.Allow);
+ using Semaphore semaphoreNew = CreateAndVerifySemaphore(initialCount: 1, maximumCount: 2, name, expectedSecurity, expectedCreatedNew: true);
+
+ using Semaphore semaphoreExisting = SemaphoreAcl.OpenExisting(name, SemaphoreRights.FullControl);
+
+ VerifyHandles(semaphoreNew, semaphoreExisting);
+ SemaphoreSecurity actualSecurity = semaphoreExisting.GetAccessControl();
+ VerifySemaphoreSecurity(expectedSecurity, actualSecurity);
+ }
+
+ [Fact]
+ public void Semaphore_TryOpenExisting()
+ {
+ string name = GetRandomName();
+ SemaphoreSecurity expectedSecurity = GetSemaphoreSecurity(WellKnownSidType.BuiltinUsersSid, SemaphoreRights.FullControl, AccessControlType.Allow);
+ using Semaphore semaphoreNew = CreateAndVerifySemaphore(initialCount: 1, maximumCount: 2, name, expectedSecurity, expectedCreatedNew: true);
+
+ Assert.True(SemaphoreAcl.TryOpenExisting(name, SemaphoreRights.FullControl, out Semaphore semaphoreExisting));
+ Assert.NotNull(semaphoreExisting);
+
+ VerifyHandles(semaphoreNew, semaphoreExisting);
+ SemaphoreSecurity actualSecurity = semaphoreExisting.GetAccessControl();
+ VerifySemaphoreSecurity(expectedSecurity, actualSecurity);
+
+ semaphoreExisting.Dispose();
+ }
+
+ [Fact]
+ public void Semaphore_OpenExisting_NameNotFound()
+ {
+ string name = "ThisShouldNotExist";
+ Assert.Throws<WaitHandleCannotBeOpenedException>(() =>
+ {
+ SemaphoreAcl.OpenExisting(name, SemaphoreRights.FullControl).Dispose();
+ });
+
+ Assert.False(SemaphoreAcl.TryOpenExisting(name, SemaphoreRights.FullControl, out _));
+ }
+
+ [Fact]
+ public void Semaphore_OpenExisting_NameInvalid()
+ {
+ string name = '\0'.ToString();
+ Assert.Throws<WaitHandleCannotBeOpenedException>(() =>
+ {
+ SemaphoreAcl.OpenExisting(name, SemaphoreRights.FullControl).Dispose();
+ });
+
+ Assert.False(SemaphoreAcl.TryOpenExisting(name, SemaphoreRights.FullControl, out _));
+ }
+
+ [Fact]
+ public void Semaphore_OpenExisting_PathNotFound()
+ {
+ string name = @"global\foo";
+ Assert.Throws<IOException>(() =>
+ {
+ SemaphoreAcl.OpenExisting(name, SemaphoreRights.FullControl).Dispose();
+ });
+
+ Assert.False(SemaphoreAcl.TryOpenExisting(name, SemaphoreRights.FullControl, out _));
+ }
+
+ [Fact]
+ public void Semaphore_OpenExisting_BadPathName()
+ {
+ string name = @"\\?\Path";
+ Assert.Throws<IOException>(() =>
+ {
+ SemaphoreAcl.OpenExisting(name, SemaphoreRights.FullControl).Dispose();
+ });
+
+ Assert.Throws<IOException>(() =>
+ {
+ SemaphoreAcl.TryOpenExisting(name, SemaphoreRights.FullControl, out _);
+ });
+ }
+
+ [Fact]
+ public void Semaphore_OpenExisting_NullName()
+ {
+ Assert.Throws<ArgumentNullException>(() =>
+ {
+ SemaphoreAcl.OpenExisting(null, SemaphoreRights.FullControl).Dispose();
+ });
+
+ Assert.Throws<ArgumentNullException>(() =>
+ {
+ SemaphoreAcl.TryOpenExisting(null, SemaphoreRights.FullControl, out _);
+ });
+ }
+
+ [Fact]
+ public void Semaphore_OpenExisting_EmptyName()
+ {
+ Assert.Throws<ArgumentException>(() =>
+ {
+ SemaphoreAcl.OpenExisting(string.Empty, SemaphoreRights.FullControl).Dispose();
+ });
+
+ Assert.Throws<ArgumentException>(() =>
+ {
+ SemaphoreAcl.TryOpenExisting(string.Empty, SemaphoreRights.FullControl, out _);
+ });
+ }
+
private SemaphoreSecurity GetBasicSemaphoreSecurity()
{
return GetSemaphoreSecurity(
return Semaphore;
}
+ private void VerifyHandles(Semaphore expected, Semaphore actual)
+ {
+ Assert.NotNull(expected.SafeWaitHandle);
+ Assert.NotNull(actual.SafeWaitHandle);
+
+ Assert.False(expected.SafeWaitHandle.IsClosed);
+ Assert.False(actual.SafeWaitHandle.IsClosed);
+
+ Assert.False(expected.SafeWaitHandle.IsInvalid);
+ Assert.False(actual.SafeWaitHandle.IsInvalid);
+ }
+
private void VerifySemaphoreSecurity(SemaphoreSecurity expectedSecurity, SemaphoreSecurity actualSecurity)
{
Assert.Equal(typeof(SemaphoreRights), expectedSecurity.AccessRightType);