Skip to content

Commit 00fdda4

Browse files
authored
Implement handling POSIX signals (#1954)
* Implement handling POSIX signals * Improve handling of exceptions in signal handlers
1 parent c6043a7 commit 00fdda4

File tree

2 files changed

+132
-24
lines changed

2 files changed

+132
-24
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#nullable enable
6+
7+
#if FEATURE_PROCESS
8+
#if NET
9+
10+
using System;
11+
using System.Runtime.InteropServices;
12+
using System.Runtime.Versioning;
13+
14+
using Mono.Unix;
15+
using Mono.Unix.Native;
16+
17+
using IronPython.Runtime;
18+
using IronPython.Runtime.Operations;
19+
using System.Threading.Tasks;
20+
using Microsoft.Scripting.Runtime;
21+
using Microsoft.Scripting.Hosting.Shell;
22+
23+
namespace IronPython.Modules {
24+
public static partial class PythonSignal {
25+
26+
[SupportedOSPlatform("linux")]
27+
[SupportedOSPlatform("macos")]
28+
private class PosixSignalState : PythonSignalState {
29+
private readonly PosixSignalRegistration?[] _signalRegistrations;
30+
private ConsoleCancelEventHandler? _consoleHandler;
31+
32+
33+
public PosixSignalState(PythonContext pc) : base(pc) {
34+
_signalRegistrations = new PosixSignalRegistration[NSIG];
35+
}
36+
37+
38+
protected override void Dispose(bool disposing) {
39+
if (disposing) {
40+
Array.ForEach(_signalRegistrations, reg => reg?.Dispose());
41+
Array.Clear(_signalRegistrations, 0, _signalRegistrations.Length);
42+
}
43+
base.Dispose(disposing);
44+
}
45+
46+
47+
public override void SetPyHandler(int signalnum, object value) {
48+
// normalize handler reference
49+
if (value is int i) {
50+
value = (i == SIG_IGN) ? sig_ign : sig_dfl;
51+
}
52+
53+
if (TryGetPyHandler(signalnum, out object? existingValue) && ReferenceEquals(existingValue, value)) {
54+
// no change
55+
return;
56+
}
57+
58+
base.SetPyHandler(signalnum, value);
59+
60+
if (signalnum == SIGINT) {
61+
// SIGINT is special, we need to disable the handler on the console so that we can handle Ctrl+C here
62+
if (DefaultContext.DefaultPythonContext.Console is BasicConsole console) {
63+
if (!ReferenceEquals(value, default_int_handler)) {
64+
// save the console handler so it can be restored later if necessary
65+
_consoleHandler ??= console.ConsoleCancelEventHandler;
66+
// disable the console handler
67+
console.ConsoleCancelEventHandler = null;
68+
} else if (_consoleHandler != null) {
69+
// default_int_handler: restore the console handler, which is de facto default_int_handler
70+
console.ConsoleCancelEventHandler = _consoleHandler;
71+
_consoleHandler = null;
72+
}
73+
}
74+
}
75+
76+
// remember to unregister any previous handler
77+
var oldReg = _signalRegistrations[signalnum];
78+
79+
if (ReferenceEquals(value, sig_dfl)) {
80+
_signalRegistrations[signalnum] = null;
81+
} else if (ReferenceEquals(value, sig_ign)) {
82+
_signalRegistrations[signalnum] = PosixSignalRegistration.Create((PosixSignal)signalnum,
83+
(PosixSignalContext psc) => {
84+
psc.Cancel = true;
85+
});
86+
} else {
87+
_signalRegistrations[signalnum] = PosixSignalRegistration.Create((PosixSignal)signalnum,
88+
(PosixSignalContext psc) => {
89+
// This is called on a thread from the thread pool for most signals,
90+
// and on a dedicated thread ".NET Signal Handler" for SIGINT, SIGQUIT, and SIGTERM.
91+
CallPythonHandler(signalnum, value);
92+
psc.Cancel = true;
93+
});
94+
}
95+
oldReg?.Dispose();
96+
}
97+
}
98+
}
99+
}
100+
101+
#endif // NET
102+
#endif // FEATURE_PROCESS

src/core/IronPython.Modules/signal.cs

+30-24
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,12 @@ private static PythonSignalState MakeNtSignalState(PythonContext context) {
7676
[SupportedOSPlatform("macos")]
7777
[MethodImpl(MethodImplOptions.NoInlining)]
7878
private static PythonSignalState MakePosixSignalState(PythonContext context) {
79-
// Use SimpleSignalState until the real Posix one is written
80-
return new SimpleSignalState(context);
79+
#if NET
80+
return new PosixSignalState(context);
81+
#else
82+
// PosixSignalState depends on PosixSignalRegistration, which is not available in Mono or .NET Standard 2.0
83+
return new SimpleSignalState(context);
84+
#endif
8185
}
8286

8387

@@ -439,12 +443,15 @@ private class PythonSignalState : IDisposable {
439443
/// </remarks>
440444
protected readonly object?[] PySignalToPyHandler;
441445

446+
protected readonly object sig_dfl;
447+
protected readonly object sig_ign;
448+
442449
public PythonSignalState(PythonContext pc) {
443450
SignalPythonContext = pc;
444451
PySignalToPyHandler = new object[NSIG];
445452

446-
object sig_dfl = ScriptingRuntimeHelpers.Int32ToObject(SIG_DFL);
447-
object sig_ign = ScriptingRuntimeHelpers.Int32ToObject(SIG_IGN);
453+
sig_dfl = ScriptingRuntimeHelpers.Int32ToObject(SIG_DFL);
454+
sig_ign = ScriptingRuntimeHelpers.Int32ToObject(SIG_IGN);
448455

449456
int[] sigs = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? _PySupportedSignals_Windows
450457
: RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? _PySupportedSignals_MacOS
@@ -488,27 +495,26 @@ public virtual void SetPyHandler(int signalnum, object value)
488495
protected void CallPythonHandler(int signum, object? handler) {
489496
if (handler is null) return;
490497

491-
if (handler == default_int_handler) {
492-
// We're dealing with the default_int_handlerImpl which we
493-
// know doesn't care about the frame parameter
494-
default_int_handlerImpl(signum, null);
495-
return;
496-
} else {
497-
// We're dealing with a callable matching PySignalHandler's signature
498-
try {
499-
PySignalHandler temp = (PySignalHandler)Converter.ConvertToDelegate(handler,
500-
typeof(PySignalHandler));
501-
502-
if (SignalPythonContext.PythonOptions.Frames) {
503-
temp.Invoke(signum, SysModule._getframeImpl(null,
504-
0,
505-
SignalPythonContext._mainThreadFunctionStack));
506-
} else {
507-
temp.Invoke(signum, null);
508-
}
509-
} catch (Exception ex) {
510-
System.Console.WriteLine(SignalPythonContext.FormatException(ex));
498+
try {
499+
if (handler == default_int_handler) {
500+
// We're dealing with the default_int_handlerImpl which we
501+
// know doesn't care about the frame parameter
502+
default_int_handlerImpl(signum, null);
503+
} else {
504+
// We're dealing with a callable matching PySignalHandler's signature
505+
PySignalHandler temp = (PySignalHandler)Converter.ConvertToDelegate(handler,
506+
typeof(PySignalHandler));
507+
508+
if (SignalPythonContext.PythonOptions.Frames) {
509+
temp.Invoke(signum, SysModule._getframeImpl(null,
510+
0,
511+
SignalPythonContext._mainThreadFunctionStack));
512+
} else {
513+
temp.Invoke(signum, null);
514+
}
511515
}
516+
} catch (Exception ex) {
517+
SignalPythonContext.DomainManager.SharedIO.ErrorWriter.WriteLine(SignalPythonContext.FormatException(ex));
512518
}
513519
}
514520

0 commit comments

Comments
 (0)