diff --git a/src/core/IronPython.Modules/NtSignalState.cs b/src/core/IronPython.Modules/NtSignalState.cs index 2bc1ec812..9392f6b9d 100644 --- a/src/core/IronPython.Modules/NtSignalState.cs +++ b/src/core/IronPython.Modules/NtSignalState.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +#nullable enable + using System; using System.Runtime.InteropServices; using System.Runtime.Versioning; @@ -13,17 +15,19 @@ namespace IronPython.Modules { public static partial class PythonSignal { [SupportedOSPlatform("windows")] - internal class NtSignalState : PythonSignalState { - //We use a single Windows event handler to process all signals. This handler simply - //delegates the work out to PySignalToPyHandler. - public NativeSignal.WinSignalsHandler WinAllSignalsHandlerDelegate; + private class NtSignalState : PythonSignalState { + // We use a single Windows event handler to process all signals. This handler simply + // delegates the work out to PySignalToPyHandler. + public NativeWindowsSignal.WinSignalsHandler WinAllSignalsHandlerDelegate; + public NtSignalState(PythonContext pc) : base(pc) { - WinAllSignalsHandlerDelegate = new NativeSignal.WinSignalsHandler(WindowsEventHandler); - NativeSignal.SetConsoleCtrlHandler(this.WinAllSignalsHandlerDelegate, true); + WinAllSignalsHandlerDelegate = new NativeWindowsSignal.WinSignalsHandler(WindowsEventHandler); + NativeWindowsSignal.SetConsoleCtrlHandler(this.WinAllSignalsHandlerDelegate, true); } - //Our implementation of WinSignalsHandler + + // Our implementation of WinSignalsHandler private bool WindowsEventHandler(uint winSignal) { bool retVal; int pySignal; @@ -53,27 +57,27 @@ private bool WindowsEventHandler(uint winSignal) { int tempId = (int)PySignalToPyHandler[pySignal]; if (tempId == SIG_DFL) { - //SIG_DFL - we let Windows do whatever it normally would + // SIG_DFL - we let Windows do whatever it normally would retVal = false; } else if (tempId == SIG_IGN) { - //SIG_IGN - we do nothing, but tell Windows we handled the signal + // SIG_IGN - we do nothing, but tell Windows we handled the signal retVal = true; } else { throw new Exception("unreachable"); } } else if (PySignalToPyHandler[pySignal] == default_int_handler) { if (pySignal != SIGINT) { - //We're dealing with the default_int_handlerImpl which we - //know doesn't care about the frame parameter + // We're dealing with the default_int_handlerImpl which we + // know doesn't care about the frame parameter retVal = true; default_int_handlerImpl(pySignal, null); } else { - //Let the real interrupt handler throw a KeyboardInterrupt for SIGINT. - //It handles this far more gracefully than we can + // Let the real interrupt handler throw a KeyboardInterrupt for SIGINT. + // It handles this far more gracefully than we can retVal = false; } } else { - //We're dealing with a callable matching PySignalHandler's signature + // We're dealing with a callable matching PySignalHandler's signature retVal = true; PySignalHandler temp = (PySignalHandler)Converter.ConvertToDelegate(PySignalToPyHandler[pySignal], typeof(PySignalHandler)); @@ -96,7 +100,9 @@ private bool WindowsEventHandler(uint winSignal) { } } - internal static class NativeSignal { + + [SupportedOSPlatform("windows")] + internal static class NativeWindowsSignal { // Windows API expects to be given a function pointer like this to handle signals internal delegate bool WinSignalsHandler(uint winSignal); diff --git a/src/core/IronPython.Modules/SimpleSignalState.cs b/src/core/IronPython.Modules/SimpleSignalState.cs index bf5215835..b2eda3fd2 100644 --- a/src/core/IronPython.Modules/SimpleSignalState.cs +++ b/src/core/IronPython.Modules/SimpleSignalState.cs @@ -12,36 +12,30 @@ namespace IronPython.Modules { public static partial class PythonSignal { - internal class SimpleSignalState : PythonSignalState { + private class SimpleSignalState : PythonSignalState { + public SimpleSignalState(PythonContext pc) : base(pc) { Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress); } - private void Console_CancelKeyPress(object? sender, ConsoleCancelEventArgs e) { - int pySignal; - switch (e.SpecialKey) { - case ConsoleSpecialKey.ControlC: - pySignal = SIGINT; - break; - - case ConsoleSpecialKey.ControlBreak: - pySignal = SIGBREAK; - break; - default: - throw new InvalidOperationException("unreachable"); - } + private void Console_CancelKeyPress(object? sender, ConsoleCancelEventArgs e) { + int pySignal = e.SpecialKey switch { + ConsoleSpecialKey.ControlC => SIGINT, + ConsoleSpecialKey.ControlBreak => SIGBREAK, + _ => throw new InvalidOperationException("unreachable"), + }; lock (PySignalToPyHandler) { if (PySignalToPyHandler[pySignal].GetType() == typeof(int)) { int tempId = (int)PySignalToPyHandler[pySignal]; if (tempId == SIG_DFL) { - //SIG_DFL - do whatever it normally would + // SIG_DFL - do whatever it normally would return; } else if (tempId == SIG_IGN) { - //SIG_IGN - we do nothing, but tell the OS we handled the signal + // SIG_IGN - we do nothing, but tell the OS we handled the signal e.Cancel = false; return; } else { @@ -49,18 +43,18 @@ private void Console_CancelKeyPress(object? sender, ConsoleCancelEventArgs e) { } } else if (PySignalToPyHandler[pySignal] == default_int_handler) { if (pySignal != SIGINT) { - //We're dealing with the default_int_handlerImpl which we - //know doesn't care about the frame parameter + // We're dealing with the default_int_handlerImpl which we + // know doesn't care about the frame parameter e.Cancel = true; default_int_handlerImpl(pySignal, null); return; } else { - //Let the real interrupt handler throw a KeyboardInterrupt for SIGINT. - //It handles this far more gracefully than we can + // Let the real interrupt handler throw a KeyboardInterrupt for SIGINT. + // It handles this far more gracefully than we can return; } } else { - //We're dealing with a callable matching PySignalHandler's signature + // We're dealing with a callable matching PySignalHandler's signature PySignalHandler temp = (PySignalHandler)Converter.ConvertToDelegate(PySignalToPyHandler[pySignal], typeof(PySignalHandler)); @@ -86,4 +80,3 @@ private void Console_CancelKeyPress(object? sender, ConsoleCancelEventArgs e) { } #endif - diff --git a/src/core/IronPython.Modules/nt.cs b/src/core/IronPython.Modules/nt.cs index 0dc3998d0..9061e36f4 100644 --- a/src/core/IronPython.Modules/nt.cs +++ b/src/core/IronPython.Modules/nt.cs @@ -1870,13 +1870,15 @@ public static void kill(CodeContext/*!*/ context, int pid, int sig) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { killUnix(pid, sig); - } else { - if (PythonSignal.NativeSignal.GenerateConsoleCtrlEvent((uint)sig, (uint)pid)) return; + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + if (PythonSignal.NativeWindowsSignal.GenerateConsoleCtrlEvent((uint)sig, (uint)pid)) return; // If the calls to GenerateConsoleCtrlEvent didn't work, simply // forcefully kill the process. Process toKill = Process.GetProcessById(pid); toKill.Kill(); + } else { + throw new PlatformNotSupportedException(); } } diff --git a/src/core/IronPython.Modules/signal.cs b/src/core/IronPython.Modules/signal.cs index 1ae2de2f8..47d54f675 100644 --- a/src/core/IronPython.Modules/signal.cs +++ b/src/core/IronPython.Modules/signal.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +#nullable enable + using System; using System.Collections.Generic; using System.Diagnostics; @@ -24,50 +26,67 @@ [assembly: PythonModule("signal", typeof(IronPython.Modules.PythonSignal))] namespace IronPython.Modules { public static partial class PythonSignal { - public const string __doc__ = @"This module provides mechanisms to use signal handlers in Python. + public const string __doc__ = """ + This module provides mechanisms to use signal handlers in Python. + + Functions: -Functions: + signal() -- set the action for a given signal + getsignal() -- get the signal action for a given signal + default_int_handler() -- default SIGINT handler -signal() -- set the action for a given signal -getsignal() -- get the signal action for a given signal -default_int_handler() -- default SIGINT handler + signal constants: + SIG_DFL -- used to refer to the system default handler + SIG_IGN -- used to ignore the signal + NSIG -- number of defined signals + SIGINT, SIGTERM, etc. -- signal numbers -signal constants: -SIG_DFL -- used to refer to the system default handler -SIG_IGN -- used to ignore the signal -NSIG -- number of defined signals -SIGINT, SIGTERM, etc. -- signal numbers + *** IMPORTANT NOTICE *** + A signal handler function is called with two arguments: + the first is the signal number, the second is the interrupted stack frame. + """; -*** IMPORTANT NOTICE *** -A signal handler function is called with two arguments: -the first is the signal number, the second is the interrupted stack frame."; [SpecialName] public static void PerformModuleReload(PythonContext/*!*/ context, PythonDictionary/*!*/ dict) { context.SetModuleState(_PythonSignalStateKey, MakeSignalState(context)); } + private static PythonSignalState MakeSignalState(PythonContext context) { - if (Environment.OSVersion.Platform == PlatformID.Unix - || Environment.OSVersion.Platform == PlatformID.MacOSX) { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + return MakeNtSignalState(context); + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { return MakePosixSignalState(context); } else { - return MakeNtSignalState(context); + return MakeSimpleSignalState(context); } } + + [SupportedOSPlatform("windows")] [MethodImpl(MethodImplOptions.NoInlining)] private static PythonSignalState MakeNtSignalState(PythonContext context) { Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); return new NtSignalState(context); } + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] [MethodImpl(MethodImplOptions.NoInlining)] private static PythonSignalState MakePosixSignalState(PythonContext context) { // Use SimpleSignalState until the real Posix one is written return new SimpleSignalState(context); } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static PythonSignalState MakeSimpleSignalState(PythonContext context) { + return new SimpleSignalState(context); + } + + //Python signals public const int NSIG = 23; public const int SIGABRT = 22; @@ -92,119 +111,141 @@ private static PythonSignalState MakePosixSignalState(PythonContext context) { [SupportedOSPlatform("windows"), PythonHidden(PlatformsAttribute.PlatformFamily.Unix)] public const int CTRL_SHUTDOWN_EVENT = 6; + public static BuiltinFunction default_int_handler = BuiltinFunction.MakeFunction("default_int_handler", ArrayUtils.ConvertAll(typeof(PythonSignal).GetMember("default_int_handlerImpl"), (x) => (MethodBase)x), typeof(PythonSignal) ); - //This must be kept public, but hidden from Python for the __doc__ member to show up on default_int_handler + + // This must be kept public, but hidden from Python for the __doc__ member to show up on default_int_handler [PythonHidden] - [Documentation(@"default_int_handler(...) + [Documentation(""" + default_int_handler(...) -The default handler for SIGINT installed by Python. -It raises KeyboardInterrupt.")] - public static object default_int_handlerImpl(int signalnum, TraceBackFrame frame) { + The default handler for SIGINT installed by Python. + It raises KeyboardInterrupt. + """)] + public static object default_int_handlerImpl(int signalnum, TraceBackFrame? frame) { throw new KeyboardInterruptException(""); } - [Documentation(@"getsignal(sig) -> action -Return the current action for the given signal. The return value can be: -SIG_IGN -- if the signal is being ignored -SIG_DFL -- if the default action for the signal is in effect -None -- if an unknown handler is in effect -anything else -- the callable Python object used as a handler")] - public static object getsignal(CodeContext/*!*/ context, int signalnum) { + [Documentation(""" + getsignal(signalnum) -> action + + Return the current action for the given signal. + + The return value can be: + SIG_IGN -- if the signal is being ignored + SIG_DFL -- if the default action for the signal is in effect + None -- if an unknown handler is in effect + anything else -- the callable Python object used as a handler + """)] + public static object? getsignal(CodeContext/*!*/ context, int signalnum) { lock (GetPythonSignalState(context).PySignalToPyHandler) { - //Negative Scenarios - if (signalnum < 1 || signalnum > 22) { + // Negative Scenarios + if (signalnum <= 0 || signalnum >= NSIG) { throw PythonOps.ValueError("signal number out of range"); - } else if (GetPythonSignalState(context).PySignalToPyHandler.TryGetValue(signalnum, out object value)) { - //Default + } else if (GetPythonSignalState(context).PySignalToPyHandler.TryGetValue(signalnum, out object? value)) { + // Default return value; } else { - //Handles the special case of SIG_IGN. This is not really a signal, - //but CPython returns null for it any ways + // Handles the special case of SIG_IGN. This is not really a signal, + // but CPython returns null for it any ways return null; } } } - [Documentation(@"signal(sig, action) -> action -Set the action for the given signal. The action can be SIG_DFL, -SIG_IGN, or a callable Python object. The previous action is -returned. See getsignal() for possible return values. + [Documentation(""" + signal(signalnum, action) -> action -*** IMPORTANT NOTICE *** -A signal handler function is called with two arguments: -the first is the signal number, the second is the interrupted stack frame.")] - public static object signal(CodeContext/*!*/ context, int sig, object action) { - //Negative scenarios - sig - if (sig < 1 || sig >= NSIG) { + Set the action for the given signal. + + The action can be SIG_DFL, SIG_IGN, or a callable Python object. + The previous action is returned. See getsignal() for possible return values. + + *** IMPORTANT NOTICE *** + A signal handler function is called with two arguments: + the first is the signal number, the second is the interrupted stack frame. + """)] + public static object? signal(CodeContext/*!*/ context, int signalnum, object? action) { + // Negative scenarios - signalnum + if (signalnum <= 0 || signalnum >= NSIG) { throw PythonOps.ValueError("signal number out of range"); - } else if (Array.IndexOf(_PySupportedSignals, sig) == -1) { + } else if (Array.IndexOf(_PySupportedSignals, signalnum) == -1) { throw new RuntimeException("no IronPython support for given signal"); } - //Negative scenarios - action + // Negative scenarios - action if (action == null) { - throw PythonOps.TypeError("signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object"); + throw SignalHandlerError(); } else if (action.GetType() == typeof(int)) { int tempAction = (int)action; if (tempAction != SIG_DFL && tempAction != SIG_IGN) { - throw PythonOps.TypeError("signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object"); + throw SignalHandlerError(); } } else if (action == default_int_handler) { - //no-op + // no-op } else { - //Must match the signature of PySignalHandler - PythonFunction result = action as PythonFunction; + // Must match the signature of PySignalHandler + PythonFunction? result = action as PythonFunction; if (result == null) { - //It could still be something like a type that implements __call__ + // It could still be something like a type that implements __call__ if (! PythonOps.IsCallable(context, action)) { - throw PythonOps.TypeError("signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object"); + throw SignalHandlerError(); } } } - object last_handler = null; + object? last_handler = null; lock (GetPythonSignalState(context).PySignalToPyHandler) { - //CPython returns the previous handler for the signal - last_handler = getsignal(context, sig); - //Set the new action - GetPythonSignalState(context).PySignalToPyHandler[sig] = action; + // CPython returns the previous handler for the signal + last_handler = getsignal(context, signalnum); + // Set the new action + GetPythonSignalState(context).PySignalToPyHandler[signalnum] = action; } return last_handler; + + static Exception SignalHandlerError() => PythonOps.TypeError("signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object"); } - [Documentation(@"NOT YET IMPLEMENTED -set_wakeup_fd(fd) -> fd + [Documentation(""" + NOT YET IMPLEMENTED + + set_wakeup_fd(fd) -> fd -Sets the fd to be written to (with '\0') when a signal -comes in. A library can use this to wakeup select or poll. -The previous fd is returned. + Sets the fd to be written to (with '\0') when a signal + comes in. A library can use this to wakeup select or poll. + The previous fd is returned. -The fd must be non-blocking.")] + The fd must be non-blocking. + """)] public static void set_wakeup_fd(CodeContext/*!*/ context, uint fd) { throw new NotImplementedException(); //TODO } - private static readonly object _PythonSignalStateKey = new object(); - + + private static readonly object _PythonSignalStateKey = new(); + + private static PythonSignalState GetPythonSignalState(CodeContext/*!*/ context) { return (PythonSignalState)context.LanguageContext.GetModuleState(_PythonSignalStateKey); } - + + private static void SetPythonSignalState(CodeContext/*!*/ context, PythonSignalState pss) { context.LanguageContext.SetModuleState(_PythonSignalStateKey, pss); } - internal class PythonSignalState { - //this provides us with access to the Main thread's stack + + private class PythonSignalState { + // this provides us with access to the Main thread's stack public PythonContext SignalPythonContext; - //Map out signal identifiers to their actual handlers + // Map out signal identifiers to their actual handlers public Dictionary PySignalToPyHandler; public PythonSignalState(PythonContext pc) { @@ -221,11 +262,11 @@ public PythonSignalState(PythonContext pc) { } } - //List of all Signals CPython supports on Windows. Notice the addition of '6' + // List of all Signals CPython supports on Windows. Notice the addition of '6' private static readonly int[] _PySupportedSignals = { SIGABRT, SIGBREAK, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM, 6 }; - //Signature of Python functions that signal.signal(...) expects to be given - private delegate object PySignalHandler(int signalnum, TraceBackFrame frame); + // Signature of Python functions that signal.signal(...) expects to be given + private delegate object PySignalHandler(int signalnum, TraceBackFrame? frame); } }