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)