diff --git a/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.EventWaitHandle.cs b/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.EventWaitHandle.cs
index dabe8c2dc2bd..c3adfadb53d5 100644
--- a/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.EventWaitHandle.cs
+++ b/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.EventWaitHandle.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+#nullable enable
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
@@ -13,16 +14,16 @@ internal static partial class Kernel32
internal const uint CREATE_EVENT_INITIAL_SET = 0x2;
internal const uint CREATE_EVENT_MANUAL_RESET = 0x1;
- [DllImport(Interop.Libraries.Kernel32, SetLastError = true)]
+ [DllImport(Libraries.Kernel32, SetLastError = true)]
internal static extern bool SetEvent(SafeWaitHandle handle);
- [DllImport(Interop.Libraries.Kernel32, SetLastError = true)]
+ [DllImport(Libraries.Kernel32, SetLastError = true)]
internal static extern bool ResetEvent(SafeWaitHandle handle);
- [DllImport(Interop.Libraries.Kernel32, EntryPoint = "CreateEventExW", SetLastError = true, CharSet = CharSet.Unicode)]
+ [DllImport(Libraries.Kernel32, EntryPoint = "CreateEventExW", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern SafeWaitHandle CreateEventEx(IntPtr lpSecurityAttributes, string? name, uint flags, uint desiredAccess);
- [DllImport(Interop.Libraries.Kernel32, EntryPoint = "OpenEventW", SetLastError = true, CharSet = CharSet.Unicode)]
+ [DllImport(Libraries.Kernel32, EntryPoint = "OpenEventW", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern SafeWaitHandle OpenEvent(uint desiredAccess, bool inheritHandle, string name);
}
}
diff --git a/src/System.Threading.AccessControl/ref/System.Threading.AccessControl.cs b/src/System.Threading.AccessControl/ref/System.Threading.AccessControl.cs
index 0298eb449501..a4b5d31fb177 100644
--- a/src/System.Threading.AccessControl/ref/System.Threading.AccessControl.cs
+++ b/src/System.Threading.AccessControl/ref/System.Threading.AccessControl.cs
@@ -147,6 +147,10 @@ public static void SetAccessControl(this System.Threading.EventWaitHandle handle
public static void SetAccessControl(this System.Threading.Mutex mutex, System.Security.AccessControl.MutexSecurity mutexSecurity) { }
public static void SetAccessControl(this System.Threading.Semaphore semaphore, System.Security.AccessControl.SemaphoreSecurity semaphoreSecurity) { }
}
+ public static 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 class MutexAcl
{
public static System.Threading.Mutex Create(bool initiallyOwned, string name, out bool createdNew, System.Security.AccessControl.MutexSecurity mutexSecurity) { throw null; }
diff --git a/src/System.Threading.AccessControl/src/System.Threading.AccessControl.csproj b/src/System.Threading.AccessControl/src/System.Threading.AccessControl.csproj
index 23200dd3920c..44aa39c63825 100644
--- a/src/System.Threading.AccessControl/src/System.Threading.AccessControl.csproj
+++ b/src/System.Threading.AccessControl/src/System.Threading.AccessControl.csproj
@@ -13,6 +13,7 @@
+
@@ -22,6 +23,7 @@
+
@@ -29,6 +31,7 @@
+
diff --git a/src/System.Threading.AccessControl/src/System/Threading/EventWaitHandleAcl.cs b/src/System.Threading.AccessControl/src/System/Threading/EventWaitHandleAcl.cs
new file mode 100644
index 000000000000..f6a15ff7576d
--- /dev/null
+++ b/src/System.Threading.AccessControl/src/System/Threading/EventWaitHandleAcl.cs
@@ -0,0 +1,88 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Security.AccessControl;
+using Microsoft.Win32.SafeHandles;
+
+namespace System.Threading
+{
+ public static class EventWaitHandleAcl
+ {
+ /// Gets or creates an instance, allowing a instance to be optionally specified to set it during the event creation.
+ /// to set the initial state to signaled if the named event is created as a result of this call; to set it to non-signaled.
+ /// One of the enum values that determines whether the event resets automatically or manually.
+ /// The name, if the event is a system-wide synchronization event; otherwise, or an empty string.
+ /// When this method returns, this argument is always set to if a local event is created; that is, when is or . If has a valid, non-empty value, this argument is set to when the system event is created, or it is set to if an existing system event is found with that name. This parameter is passed uninitialized.
+ /// The optional Windows access control security to apply.
+ /// An object that represents a system event wait handle, if named, or a local event wait handle, if nameless.
+ /// .NET Framework only: The length is beyond MAX_PATH (260 characters).
+ /// The enum value was out of legal range.
+ /// Could not find a part of the path specified in .
+ /// A system-wide synchronization event with the provided was not found.
+ /// -or-
+ /// An with system-wide name cannot be created. An of a different type might have the same name.
+ /// If a `name` is passed and the system event already exists, the existing event is returned. If `name` is `null` or , a new local event is always created.
+ public static unsafe EventWaitHandle Create(bool initialState, EventResetMode mode, string name, out bool createdNew, EventWaitHandleSecurity eventSecurity)
+ {
+ if (eventSecurity == null)
+ {
+ return new EventWaitHandle(initialState, mode, name, out createdNew);
+ }
+
+ if (mode != EventResetMode.AutoReset && mode != EventResetMode.ManualReset)
+ {
+ throw new ArgumentOutOfRangeException(nameof(mode));
+ }
+
+ uint eventFlags = initialState ? Interop.Kernel32.CREATE_EVENT_INITIAL_SET : 0;
+ if (mode == EventResetMode.ManualReset)
+ {
+ eventFlags |= Interop.Kernel32.CREATE_EVENT_MANUAL_RESET;
+ }
+
+ fixed (byte* pSecurityDescriptor = eventSecurity.GetSecurityDescriptorBinaryForm())
+ {
+ var secAttrs = new Interop.Kernel32.SECURITY_ATTRIBUTES
+ {
+ nLength = (uint)sizeof(Interop.Kernel32.SECURITY_ATTRIBUTES),
+ lpSecurityDescriptor = (IntPtr)pSecurityDescriptor
+ };
+
+ SafeWaitHandle handle = Interop.Kernel32.CreateEventEx(
+ (IntPtr)(&secAttrs),
+ name,
+ eventFlags,
+ (uint)EventWaitHandleRights.FullControl);
+
+ ValidateHandle(handle, name, out createdNew);
+
+ EventWaitHandle ewh = new EventWaitHandle(initialState, mode);
+ SafeWaitHandle old = ewh.SafeWaitHandle;
+ ewh.SafeWaitHandle = handle;
+ old.Dispose();
+
+ return ewh;
+ }
+ }
+
+ private static void ValidateHandle(SafeWaitHandle handle, string name, out bool createdNew)
+ {
+ int errorCode = Marshal.GetLastWin32Error();
+
+ 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);
+ }
+ }
+}
diff --git a/src/System.Threading.AccessControl/src/System/Threading/EventWaitHandleAcl.net46.cs b/src/System.Threading.AccessControl/src/System/Threading/EventWaitHandleAcl.net46.cs
new file mode 100644
index 000000000000..2b8e65d72448
--- /dev/null
+++ b/src/System.Threading.AccessControl/src/System/Threading/EventWaitHandleAcl.net46.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.AccessControl;
+
+namespace System.Threading
+{
+ public static class EventWaitHandleAcl
+ {
+ public static EventWaitHandle Create(
+ bool initialState,
+ EventResetMode mode,
+ string name,
+ out bool createdNew,
+ EventWaitHandleSecurity eventSecurity)
+ {
+ return new EventWaitHandle(initialState, mode, name, out createdNew, eventSecurity);
+ }
+ }
+}
diff --git a/src/System.Threading.AccessControl/tests/EventWaitHandleAclTests.cs b/src/System.Threading.AccessControl/tests/EventWaitHandleAclTests.cs
new file mode 100644
index 000000000000..ca02c306cff2
--- /dev/null
+++ b/src/System.Threading.AccessControl/tests/EventWaitHandleAclTests.cs
@@ -0,0 +1,247 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.AccessControl;
+using System.Security.Principal;
+using Xunit;
+
+namespace System.Threading.Tests
+{
+ public class EventWaitHandleAclTests : AclTests
+ {
+ [Fact]
+ public void EventWaitHandle_Create_NullSecurity()
+ {
+ CreateAndVerifyEventWaitHandle(
+ initialState: true,
+ mode: EventResetMode.AutoReset,
+ name: GetRandomName(),
+ expectedSecurity: null,
+ expectedCreatedNew: true).Dispose();
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ public void EventWaitHandle_Create_NameMultipleNew(string name)
+ {
+ EventWaitHandleSecurity security = GetBasicEventWaitHandleSecurity();
+
+ using EventWaitHandle handle1 = CreateAndVerifyEventWaitHandle(
+ initialState: true,
+ mode: EventResetMode.AutoReset,
+ name,
+ security,
+ expectedCreatedNew: true);
+
+ using EventWaitHandle handle2 = CreateAndVerifyEventWaitHandle(
+ initialState: true,
+ mode: EventResetMode.AutoReset,
+ name,
+ security,
+ expectedCreatedNew: true);
+ }
+
+ [Fact]
+ public void EventWaitHandle_Create_CreateNewExisting()
+ {
+ string name = GetRandomName();
+ EventWaitHandleSecurity security = GetBasicEventWaitHandleSecurity();
+
+ using EventWaitHandle handle1 = CreateAndVerifyEventWaitHandle(
+ initialState: true,
+ mode: EventResetMode.AutoReset,
+ name,
+ security,
+ expectedCreatedNew: true);
+
+ using EventWaitHandle handle2 = CreateAndVerifyEventWaitHandle(
+ initialState: true,
+ mode: EventResetMode.AutoReset,
+ name,
+ security,
+ expectedCreatedNew: false);
+ }
+
+ [Fact]
+ public void EventWaitHandle_Create_GlobalPrefixNameNotFound()
+ {
+ string prefixedName = @"GLOBAL\" + GetRandomName();
+
+ Assert.Throws(() =>
+ {
+ CreateEventWaitHandle(
+ initialState: true,
+ mode: EventResetMode.AutoReset,
+ prefixedName,
+ expectedSecurity: GetBasicEventWaitHandleSecurity(),
+ expectedCreatedNew: true).Dispose();
+ });
+ }
+
+ // The documentation says MAX_PATH is the length limit for name, but it won't throw any errors:
+ // https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createeventexw
+ // The .NET Core constructors for EventWaitHandle do not throw on name longer than MAX_LENGTH, so the extension method should match the behavior:
+ // https://source.dot.net/#System.Private.CoreLib/shared/System/Threading/EventWaitHandle.Windows.cs,20
+ // The .NET Framework constructor throws:
+ // https://referencesource.microsoft.com/#mscorlib/system/threading/eventwaithandle.cs,59
+ [Fact]
+ public void EventWaitHandle_Create_BeyondMaxPathLength()
+ {
+ string name = new string('x', Interop.Kernel32.MAX_PATH + 100);
+ EventWaitHandleSecurity security = GetBasicEventWaitHandleSecurity();
+ EventResetMode mode = EventResetMode.AutoReset;
+
+ if (PlatformDetection.IsFullFramework)
+ {
+ Assert.Throws(() =>
+ {
+ CreateEventWaitHandle(
+ initialState: true,
+ mode,
+ name,
+ security,
+ expectedCreatedNew: true).Dispose();
+ });
+ }
+ else
+ {
+ using EventWaitHandle created = CreateAndVerifyEventWaitHandle(
+ initialState: true,
+ mode,
+ name,
+ security,
+ expectedCreatedNew: true);
+
+ using EventWaitHandle openedByName = EventWaitHandle.OpenExisting(name);
+ Assert.NotNull(openedByName);
+ }
+ }
+
+ [Theory]
+ [InlineData((EventResetMode)int.MinValue)]
+ [InlineData((EventResetMode)(-1))]
+ [InlineData((EventResetMode)2)]
+ [InlineData((EventResetMode)int.MaxValue)]
+ public void EventWaitHandle_Create_InvalidMode(EventResetMode mode)
+ {
+ if (PlatformDetection.IsFullFramework)
+ {
+ Assert.Throws(() =>
+ {
+ CreateEventWaitHandle(
+ initialState: true,
+ mode,
+ GetRandomName(),
+ GetBasicEventWaitHandleSecurity(),
+ expectedCreatedNew: true).Dispose();
+ });
+ }
+ else
+ {
+ Assert.Throws("mode", () =>
+ {
+ CreateEventWaitHandle(
+ initialState: true,
+ mode,
+ GetRandomName(),
+ GetBasicEventWaitHandleSecurity(),
+ expectedCreatedNew: true).Dispose();
+ });
+ }
+ }
+
+ public static IEnumerable