Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions src/core/IronPython.Modules/signal.PosixSignalState.cs
Original file line number Diff line number Diff line change
@@ -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,
Copy link
Contributor

@slozier slozier May 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the PosixSignal enum match up with the signalnum values? Looking at the docs the .NET enum values are negative while the signalnum are positive?

Edit: nvm, looks like they do say you can cast raw values to the PosixSignal enum in the remarks section. Guess that's why the enum is negative...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the enum values are negative and platform-independent, the positive values are actual platform-dependent signal numbers. Platform-dependent signal numbers is what CPython uses (at least on POSIX, I am still a little bit puzzled by what is going on on Windows).

I think it is a better scheme than what Mono.Unix uses with its enum values, since it is future-proof (the enum values do not cover, nor do they need to cover all available signals on a given platform).

(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
54 changes: 30 additions & 24 deletions src/core/IronPython.Modules/signal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}


Expand Down Expand Up @@ -439,12 +443,15 @@ private class PythonSignalState : IDisposable {
/// </remarks>
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
Expand Down Expand Up @@ -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));
}
}

Expand Down