diff --git a/src/core/IronPython.Modules/signal.NtSignalState.cs b/src/core/IronPython.Modules/signal.NtSignalState.cs index 9392f6b9d..2066929de 100644 --- a/src/core/IronPython.Modules/signal.NtSignalState.cs +++ b/src/core/IronPython.Modules/signal.NtSignalState.cs @@ -49,51 +49,28 @@ private bool WindowsEventHandler(uint winSignal) { pySignal = SIGBREAK; break; default: - throw new Exception("unreachable"); + throw new InvalidOperationException("unreachable"); } - - lock (PySignalToPyHandler) { - if (PySignalToPyHandler[pySignal].GetType() == typeof(int)) { - int tempId = (int)PySignalToPyHandler[pySignal]; - - if (tempId == SIG_DFL) { - // 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 - 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 - 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 - retVal = false; - } - } else { - // We're dealing with a callable matching PySignalHandler's signature + object? handler = PySignalToPyHandler[pySignal]; + + if (handler is int tempId) { + if (tempId == SIG_DFL) { + // 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 retVal = true; - PySignalHandler temp = (PySignalHandler)Converter.ConvertToDelegate(PySignalToPyHandler[pySignal], - typeof(PySignalHandler)); - - try { - if (SignalPythonContext.PythonOptions.Frames) { - temp.Invoke(pySignal, SysModule._getframeImpl(null, - 0, - SignalPythonContext._mainThreadFunctionStack)); - } else { - temp.Invoke(pySignal, null); - } - } catch (Exception e) { - System.Console.WriteLine(SignalPythonContext.FormatException(e)); - } + } else { + throw new InvalidOperationException("unreachable"); } + } else if (handler == default_int_handler && pySignal == SIGINT) { + // 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 + CallPythonHandler(pySignal, handler); + retVal = true; } return retVal; diff --git a/src/core/IronPython.Modules/signal.SimpleSignalState.cs b/src/core/IronPython.Modules/signal.SimpleSignalState.cs index 28938684c..473c64151 100644 --- a/src/core/IronPython.Modules/signal.SimpleSignalState.cs +++ b/src/core/IronPython.Modules/signal.SimpleSignalState.cs @@ -15,8 +15,7 @@ namespace IronPython.Modules { public static partial class PythonSignal { private class SimpleSignalState : PythonSignalState { - public SimpleSignalState(PythonContext pc) - : base(pc) { + public SimpleSignalState(PythonContext pc) : base(pc) { Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress); } @@ -29,52 +28,27 @@ private void Console_CancelKeyPress(object? sender, ConsoleCancelEventArgs e) { _ => 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 - return; - } else if (tempId == SIG_IGN) { - // SIG_IGN - we do nothing, but tell the OS we handled the signal - e.Cancel = false; - return; - } 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 - 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 - return; - } - } else { - // We're dealing with a callable matching PySignalHandler's signature - PySignalHandler temp = (PySignalHandler)Converter.ConvertToDelegate(PySignalToPyHandler[pySignal], - typeof(PySignalHandler)); - - try { - if (SignalPythonContext.PythonOptions.Frames) { - temp.Invoke(pySignal, SysModule._getframeImpl(null, - 0, - SignalPythonContext._mainThreadFunctionStack)); - } else { - temp.Invoke(pySignal, null); - } - } catch (Exception ex) { - System.Console.WriteLine(SignalPythonContext.FormatException(ex)); - } + object? handler = PySignalToPyHandler[pySignal]; + if (handler is int tempId) { + if (tempId == SIG_DFL) { + // 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 e.Cancel = true; return; + } else { + throw new InvalidOperationException("unreachable"); } + } else if (ReferenceEquals(handler, default_int_handler) && pySignal == SIGINT) { + // Let the real interrupt handler throw a KeyboardInterrupt for SIGINT. + // It handles this far more gracefully than we can + return; + } else { + CallPythonHandler(pySignal, handler); + e.Cancel = true; + return; } } } diff --git a/src/core/IronPython.Modules/signal.cs b/src/core/IronPython.Modules/signal.cs index ea44399a5..e96b2e981 100644 --- a/src/core/IronPython.Modules/signal.cs +++ b/src/core/IronPython.Modules/signal.cs @@ -314,16 +314,13 @@ public static object default_int_handlerImpl(int signalnum, TraceBackFrame? fram 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 <= 0 || signalnum >= NSIG) { - throw PythonOps.ValueError("signal number out of range"); - } else if (GetPythonSignalState(context).PySignalToPyHandler.TryGetValue(signalnum, out object? value)) { - // Default + if (signalnum <= 0 || signalnum >= NSIG) throw PythonOps.ValueError("signal number out of range"); + + var state = GetPythonSignalState(context); + lock (state.SyncRoot) { + if (state.TryGetPyHandler(signalnum, out object? value)) { return value; } else { - // Handles the special case of SIG_IGN. This is not really a signal, - // but CPython returns null for it any ways return null; } } @@ -375,12 +372,16 @@ public static object default_int_handlerImpl(int signalnum, TraceBackFrame? fram if (signalnum == SIGKILL || signalnum == SIGSTOP) throw PythonNT.GetOsError(PythonErrno.EINVAL); } object? last_handler = null; - lock (GetPythonSignalState(context).PySignalToPyHandler) { + var state = GetPythonSignalState(context); + lock (state.SyncRoot) { // CPython returns the previous handler for the signal - last_handler = getsignal(context, signalnum); - if (last_handler is null) throw PythonNT.GetOsError(PythonErrno.EINVAL); + if (!state.TryGetPyHandler(signalnum, out last_handler) || last_handler is null) { + // null marks signals that cannot be handled or are unsupported + throw PythonNT.GetOsError(PythonErrno.EINVAL); + } + // Set the new action - GetPythonSignalState(context).PySignalToPyHandler[signalnum] = action; + state.SetPyHandler(signalnum, action); } return last_handler; @@ -417,35 +418,97 @@ private static void SetPythonSignalState(CodeContext/*!*/ context, PythonSignalS context.LanguageContext.SetModuleState(_PythonSignalStateKey, pss); } - + /// + /// This class is used to store the installed signal handlers. + /// 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 - public Dictionary PySignalToPyHandler; + public readonly PythonContext SignalPythonContext; + + public object SyncRoot => PySignalToPyHandler; + + /// + /// Map out signal identifiers to their actual handlers. + /// + /// + /// The objects in this array are either: + /// 1. Int32(SIG_DFL) - let the OS handle the signal in the default way; + /// 2. Int32(SIG_IGN) - ignore the signal; + /// 3. a callable Python object - the handler for the signal; + /// 4. null - the signal (or handling thereof) is not supported on this platform. + /// + protected readonly object?[] PySignalToPyHandler; public PythonSignalState(PythonContext pc) { SignalPythonContext = pc; + PySignalToPyHandler = new object[NSIG]; + + object sig_dfl = ScriptingRuntimeHelpers.Int32ToObject(SIG_DFL); + object sig_ign = ScriptingRuntimeHelpers.Int32ToObject(SIG_IGN); + int[] sigs = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? _PySupportedSignals_Windows : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? _PySupportedSignals_MacOS : RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? _PySupportedSignals_Linux : throw new NotSupportedException("Unsupported platform for signal module"); - PySignalToPyHandler = new Dictionary(sigs.Length); - object sig_dfl = ScriptingRuntimeHelpers.Int32ToObject(SIG_DFL); - object sig_ign = ScriptingRuntimeHelpers.Int32ToObject(SIG_IGN); + // Setting all defined signals to SIG_DFL foreach (int sig in sigs) { PySignalToPyHandler[sig] = sig_dfl; } + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { + for (int sig = SIGRTMIN; sig <= SIGRTMAX; sig++) { + PySignalToPyHandler[sig] = sig_dfl; + } + } + + // Setting exceptions to the rule PySignalToPyHandler[SIGINT] = default_int_handler; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { PySignalToPyHandler[SIGPIPE] = sig_ign; PySignalToPyHandler[SIGXFSZ] = sig_ign; } if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - PySignalToPyHandler.Remove(SIGKILL); - PySignalToPyHandler.Remove(SIGSTOP); + PySignalToPyHandler[SIGKILL] = null; + PySignalToPyHandler[SIGSTOP] = null; + } + } + + + public virtual bool TryGetPyHandler(int signalnum, out object? value) + => (value = PySignalToPyHandler[signalnum]) != null; + + + public virtual void SetPyHandler(int signalnum, object value) + => PySignalToPyHandler[signalnum] = value; + + + /// + /// Call the Python handler callable passing the given signal number as argument to the callable. + /// + protected void CallPythonHandler(int signum, object? handler) { + if (handler is null) return; + + if (handler == default_int_handler) { + // We're dealing with the default_int_handlerImpl which we + // know doesn't care about the frame parameter + default_int_handlerImpl(signum, null); + return; + } else { + // We're dealing with a callable matching PySignalHandler's signature + try { + PySignalHandler temp = (PySignalHandler)Converter.ConvertToDelegate(handler, + typeof(PySignalHandler)); + + if (SignalPythonContext.PythonOptions.Frames) { + temp.Invoke(signum, SysModule._getframeImpl(null, + 0, + SignalPythonContext._mainThreadFunctionStack)); + } else { + temp.Invoke(signum, null); + } + } catch (Exception ex) { + System.Console.WriteLine(SignalPythonContext.FormatException(ex)); + } } } } diff --git a/tests/suite/modules/system_related/test_signal.py b/tests/suite/modules/system_related/test_signal.py index 0b7a30a2f..cccb7118b 100644 --- a/tests/suite/modules/system_related/test_signal.py +++ b/tests/suite/modules/system_related/test_signal.py @@ -86,7 +86,7 @@ def a(b, c): # test that unsupported signals raise OSError on trying to set a handler for x in range(1, signal.NSIG): if x in SUPPORTED_SIGNALS: continue - if is_linux and 35 <= x <= 64: continue # Real-time signals + if is_linux and signal.SIGRTMIN <= x <= signal.SIGRTMAX: continue # Real-time signals self.assertRaisesMessage(ValueError if is_windows else OSError, "invalid signal value" if is_windows else "[Errno 22] Invalid argument", signal.signal, x, a)