private static async Task <bool> SendSignalFromCurrentProcess(int processId, NativeMethods.CtrlType signal)
        {
            await SignalFromCurrentProcessLock.WaitAsync().ConfigureAwait(false);

            try
            {
                using (var waitForSignalSemaphore = new SemaphoreSlim(initialCount: 0, maxCount: 1))
                {
                    NativeMethods.ConsoleCtrlDelegate handler = receivedSignal =>
                    {
                        if (receivedSignal == signal)
                        {
                            waitForSignalSemaphore.Release();
                            // if we're signaling another process on the same console, we return true
                            // to prevent the signal from bubbling. If we're signaling ourselves, we
                            // allow it to bubble since presumably that's what the caller wanted
                            return(processId != ProcessHelper.CurrentProcessId);
                        }
                        return(false);
                    };
                    if (!NativeMethods.SetConsoleCtrlHandler(handler, add: true))
                    {
                        Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                    }
                    try
                    {
                        if (!NativeMethods.GenerateConsoleCtrlEvent(signal, NativeMethods.AllProcessesWithCurrentConsoleGroup))
                        {
                            return(false);
                        }

                        // Wait until the signal has reached our handler and been handled to know that it is safe to
                        // remove the handler.
                        // Timeout here just to ensure we don't hang forever if something weird happens (e. g. someone
                        // else registers a handler concurrently with us).
                        return(await waitForSignalSemaphore.WaitAsync(TimeSpan.FromSeconds(30)).ConfigureAwait(false));
                    }
                    finally
                    {
                        if (!NativeMethods.SetConsoleCtrlHandler(handler, add: false))
                        {
                            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                        }
                    }
                }
            }
            finally
            {
                SignalFromCurrentProcessLock.Release();
            }
        }
        public async Task CanSendSignalToSelf(int signal)
        {
            if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                Assert.Pass("Tests windows-specific behavior");
            }

            Command.TryAttachToProcess(ProcessHelper.CurrentProcessId, out var thisCommand).ShouldEqual(true);

            using (var manualResetEvent = new ManualResetEventSlim(initialState: false))
            {
                NativeMethods.ConsoleCtrlDelegate handler = receivedSignal =>
                {
                    if ((int)receivedSignal == signal)
                    {
                        manualResetEvent.Set();
                        return(true); // handled
                    }
                    return(false);
                };
                if (!NativeMethods.SetConsoleCtrlHandler(handler, add: true))
                {
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                }

                try
                {
                    (await thisCommand.TrySignalAsync(CommandSignal.FromSystemValue(signal))).ShouldEqual(true);
                    manualResetEvent.Wait(TimeSpan.FromSeconds(5)).ShouldEqual(true);
                }
                finally
                {
                    if (!NativeMethods.SetConsoleCtrlHandler(handler, add: false))
                    {
                        Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                    }
                }
            }
        }