diff --git a/src/core/IronPython.Modules/signal.PosixSignalState.cs b/src/core/IronPython.Modules/signal.PosixSignalState.cs new file mode 100644 index 000000000..35b474e41 --- /dev/null +++ b/src/core/IronPython.Modules/signal.PosixSignalState.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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 + +#if FEATURE_PROCESS +#if NET + +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +using Mono.Unix; +using Mono.Unix.Native; + +using IronPython.Runtime; +using IronPython.Runtime.Operations; +using System.Threading.Tasks; +using Microsoft.Scripting.Runtime; +using Microsoft.Scripting.Hosting.Shell; + +namespace IronPython.Modules { + public static partial class PythonSignal { + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private class PosixSignalState : PythonSignalState { + private readonly PosixSignalRegistration?[] _signalRegistrations; + private ConsoleCancelEventHandler? _consoleHandler; + + + public PosixSignalState(PythonContext pc) : base(pc) { + _signalRegistrations = new PosixSignalRegistration[NSIG]; + } + + + protected override void Dispose(bool disposing) { + if (disposing) { + Array.ForEach(_signalRegistrations, reg => reg?.Dispose()); + Array.Clear(_signalRegistrations, 0, _signalRegistrations.Length); + } + base.Dispose(disposing); + } + + + public override void SetPyHandler(int signalnum, object value) { + // normalize handler reference + if (value is int i) { + value = (i == SIG_IGN) ? sig_ign : sig_dfl; + } + + if (TryGetPyHandler(signalnum, out object? existingValue) && ReferenceEquals(existingValue, value)) { + // no change + return; + } + + base.SetPyHandler(signalnum, value); + + if (signalnum == SIGINT) { + // SIGINT is special, we need to disable the handler on the console so that we can handle Ctrl+C here + if (DefaultContext.DefaultPythonContext.Console is BasicConsole console) { + if (!ReferenceEquals(value, default_int_handler)) { + // save the console handler so it can be restored later if necessary + _consoleHandler ??= console.ConsoleCancelEventHandler; + // disable the console handler + console.ConsoleCancelEventHandler = null; + } else if (_consoleHandler != null) { + // default_int_handler: restore the console handler, which is de facto default_int_handler + console.ConsoleCancelEventHandler = _consoleHandler; + _consoleHandler = null; + } + } + } + + // remember to unregister any previous handler + var oldReg = _signalRegistrations[signalnum]; + + if (ReferenceEquals(value, sig_dfl)) { + _signalRegistrations[signalnum] = null; + } else if (ReferenceEquals(value, sig_ign)) { + _signalRegistrations[signalnum] = PosixSignalRegistration.Create((PosixSignal)signalnum, + (PosixSignalContext psc) => { + psc.Cancel = true; + }); + } else { + _signalRegistrations[signalnum] = PosixSignalRegistration.Create((PosixSignal)signalnum, + (PosixSignalContext psc) => { + // This is called on a thread from the thread pool for most signals, + // and on a dedicated thread ".NET Signal Handler" for SIGINT, SIGQUIT, and SIGTERM. + CallPythonHandler(signalnum, value); + psc.Cancel = true; + }); + } + oldReg?.Dispose(); + } + } + } +} + +#endif // NET +#endif // FEATURE_PROCESS diff --git a/src/core/IronPython.Modules/signal.cs b/src/core/IronPython.Modules/signal.cs index 3d80d8a54..4369e034b 100644 --- a/src/core/IronPython.Modules/signal.cs +++ b/src/core/IronPython.Modules/signal.cs @@ -76,8 +76,12 @@ private static PythonSignalState MakeNtSignalState(PythonContext context) { [SupportedOSPlatform("macos")] [MethodImpl(MethodImplOptions.NoInlining)] private static PythonSignalState MakePosixSignalState(PythonContext context) { - // Use SimpleSignalState until the real Posix one is written - return new SimpleSignalState(context); + #if NET + return new PosixSignalState(context); + #else + // PosixSignalState depends on PosixSignalRegistration, which is not available in Mono or .NET Standard 2.0 + return new SimpleSignalState(context); + #endif } @@ -439,12 +443,15 @@ private class PythonSignalState : IDisposable { /// protected readonly object?[] PySignalToPyHandler; + protected readonly object sig_dfl; + protected readonly object sig_ign; + public PythonSignalState(PythonContext pc) { SignalPythonContext = pc; PySignalToPyHandler = new object[NSIG]; - object sig_dfl = ScriptingRuntimeHelpers.Int32ToObject(SIG_DFL); - object sig_ign = ScriptingRuntimeHelpers.Int32ToObject(SIG_IGN); + sig_dfl = ScriptingRuntimeHelpers.Int32ToObject(SIG_DFL); + sig_ign = ScriptingRuntimeHelpers.Int32ToObject(SIG_IGN); int[] sigs = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? _PySupportedSignals_Windows : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? _PySupportedSignals_MacOS @@ -488,27 +495,26 @@ public virtual void SetPyHandler(int signalnum, object value) 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)); + try { + 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); + } else { + // We're dealing with a callable matching PySignalHandler's signature + 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) { + SignalPythonContext.DomainManager.SharedIO.ErrorWriter.WriteLine(SignalPythonContext.FormatException(ex)); } }