Skip to content

Commit 1eafc08

Browse files
committed
[GR-74243] Add faulthandler self-signaling functions.
PullRequest: graalpython/4347
2 parents 2ebc27d + b0c319c commit 1eafc08

File tree

11 files changed

+225
-7
lines changed

11 files changed

+225
-7
lines changed

graalpython/com.oracle.graal.python.test/src/tests/test_traceback.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
# SOFTWARE.
3939
import ast
40+
import subprocess
4041
import sys
4142

4243

@@ -659,6 +660,34 @@ def test_faulthandler_many_threads():
659660
assert len(ids) == 1, f"Interleaved output detected in block {header!r} with multiple thread func ids: {ids}"
660661

661662

663+
def test_faulthandler_sigsegv_builtin():
664+
import faulthandler
665+
666+
try:
667+
if not __graalpython__.is_native or __graalpython__.posix_module_backend() == "java":
668+
return
669+
except NameError:
670+
pass # CPython
671+
672+
def assert_fatal_faulthandler_call(name, *args):
673+
assert hasattr(faulthandler, name)
674+
code = f"import faulthandler; faulthandler.{name}({', '.join(map(repr, args))})"
675+
proc = subprocess.run(
676+
[sys.executable, "-c", code],
677+
stdout=subprocess.DEVNULL,
678+
stderr=subprocess.DEVNULL,
679+
)
680+
assert proc.returncode != 0
681+
assert proc.returncode != 1, (
682+
f"expected fatal signal path for faulthandler.{name}{args}, "
683+
f"got regular Python error exit {proc.returncode}"
684+
)
685+
686+
for release_gil in (False, True):
687+
assert_fatal_faulthandler_call("_sigsegv", release_gil)
688+
assert_fatal_faulthandler_call("_sigabrt")
689+
690+
662691
def test_location_from_ast():
663692
m = compile("a = 1\nx", "<stdin>", "exec", flags=ast.PyCF_ONLY_AST)
664693

graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_concurrent_futures.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ test.test_concurrent_futures.test_as_completed.ThreadPoolAsCompletedTest.test_co
77
test.test_concurrent_futures.test_as_completed.ThreadPoolAsCompletedTest.test_duplicate_futures @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github
88
test.test_concurrent_futures.test_as_completed.ThreadPoolAsCompletedTest.test_future_times_out @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github
99
test.test_concurrent_futures.test_as_completed.ThreadPoolAsCompletedTest.test_no_timeout @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github
10-
test.test_concurrent_futures.test_deadlock.ProcessPoolSpawnExecutorDeadlockTest.test_crash_at_task_unpickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github
1110
test.test_concurrent_futures.test_deadlock.ProcessPoolSpawnExecutorDeadlockTest.test_error_at_task_pickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github
1211
test.test_concurrent_futures.test_deadlock.ProcessPoolSpawnExecutorDeadlockTest.test_error_at_task_unpickle @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github
1312
test.test_concurrent_futures.test_deadlock.ProcessPoolSpawnExecutorDeadlockTest.test_error_during_func_exec_on_worker @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github
@@ -18,6 +17,7 @@ test.test_concurrent_futures.test_deadlock.ProcessPoolSpawnExecutorDeadlockTest.
1817
test.test_concurrent_futures.test_deadlock.ProcessPoolSpawnExecutorDeadlockTest.test_exit_during_result_pickle_on_worker @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github
1918
test.test_concurrent_futures.test_deadlock.ProcessPoolSpawnExecutorDeadlockTest.test_exit_during_result_unpickle_in_result_handler @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github
2019
# Transiently times out GR-65714
20+
!test.test_concurrent_futures.test_deadlock.ProcessPoolSpawnExecutorDeadlockTest.test_crash_at_task_unpickle
2121
!test.test_concurrent_futures.test_deadlock.ProcessPoolSpawnExecutorDeadlockTest.test_gh105829_should_not_deadlock_if_wakeup_pipe_full
2222
!test.test_concurrent_futures.test_deadlock.ProcessPoolSpawnExecutorDeadlockTest.test_shutdown_deadlock_pickle
2323
!test.test_concurrent_futures.test_future.FutureTests.test_cancel
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
test.test_faulthandler.FaultHandlerTests.test_cancel_later_without_dump_traceback_later @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github
2-
test.test_faulthandler.FaultHandlerTests.test_disable @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github
32
test.test_faulthandler.FaultHandlerTests.test_disabled_by_default @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github
43
test.test_faulthandler.FaultHandlerTests.test_is_enabled @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github
54
# Disabled since signaling isn't stable during parallel tests
5+
!test.test_faulthandler.FaultHandlerTests.test_disable
66
!test.test_faulthandler.FaultHandlerTests.test_sigbus
77
!test.test_faulthandler.FaultHandlerTests.test_sigill

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/FaulthandlerModuleBuiltins.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,21 @@
6969
import com.oracle.graal.python.lib.PyTimeFromObjectNode;
7070
import com.oracle.graal.python.lib.PyTimeFromObjectNode.RoundType;
7171
import com.oracle.graal.python.nodes.ErrorMessages;
72+
import com.oracle.graal.python.nodes.PConstructAndRaiseNode;
7273
import com.oracle.graal.python.nodes.PRaiseNode;
7374
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
7475
import com.oracle.graal.python.nodes.function.PythonBuiltinNode;
7576
import com.oracle.graal.python.nodes.function.builtins.PythonClinicBuiltinNode;
7677
import com.oracle.graal.python.nodes.function.builtins.PythonTernaryBuiltinNode;
78+
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryClinicBuiltinNode;
7779
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
7880
import com.oracle.graal.python.nodes.function.builtins.clinic.ArgumentClinicProvider;
7981
import com.oracle.graal.python.runtime.ExecutionContext.BoundaryCallContext;
82+
import com.oracle.graal.python.runtime.GilNode;
8083
import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData;
8184
import com.oracle.graal.python.runtime.PosixSupportLibrary;
85+
import com.oracle.graal.python.runtime.PosixSupportLibrary.PosixException;
86+
import com.oracle.graal.python.runtime.PosixSupportLibrary.UnsupportedPosixFeatureException;
8287
import com.oracle.graal.python.runtime.PythonContext;
8388
import com.oracle.graal.python.runtime.PythonOptions;
8489
import com.oracle.graal.python.runtime.exception.ExceptionUtils;
@@ -95,6 +100,7 @@
95100
import com.oracle.truffle.api.dsl.Specialization;
96101
import com.oracle.truffle.api.exception.AbstractTruffleException;
97102
import com.oracle.truffle.api.frame.VirtualFrame;
103+
import com.oracle.truffle.api.library.CachedLibrary;
98104
import com.oracle.truffle.api.nodes.Node;
99105
import com.oracle.truffle.api.strings.TruffleString;
100106

@@ -235,6 +241,62 @@ static int fileno(VirtualFrame frame, Node inliningTarget, Object file,
235241
}
236242
}
237243

244+
@Builtin(name = "_sigsegv", minNumOfPositionalArgs = 0, parameterNames = {"release_gil"})
245+
@ArgumentClinic(name = "release_gil", conversion = ArgumentClinic.ClinicConversion.Boolean, defaultValue = "false")
246+
@GenerateNodeFactory
247+
abstract static class SigSegvNode extends PythonUnaryClinicBuiltinNode {
248+
@Specialization
249+
static PNone doIt(VirtualFrame frame, boolean releaseGil,
250+
@Bind PythonContext context,
251+
@Bind Node inliningTarget,
252+
@CachedLibrary("context.getPosixSupport()") PosixSupportLibrary posixLib,
253+
@Cached GilNode gil,
254+
@Cached PConstructAndRaiseNode.Lazy constructAndRaiseNode) {
255+
return raiseFatalSignal(frame, context, inliningTarget, posixLib, gil, constructAndRaiseNode, "SEGV", releaseGil);
256+
}
257+
258+
@Override
259+
protected ArgumentClinicProvider getArgumentClinic() {
260+
return FaulthandlerModuleBuiltinsClinicProviders.SigSegvNodeClinicProviderGen.INSTANCE;
261+
}
262+
}
263+
264+
@Builtin(name = "_sigabrt", minNumOfPositionalArgs = 0)
265+
@GenerateNodeFactory
266+
abstract static class SigAbrtNode extends PythonBuiltinNode {
267+
@Specialization
268+
static PNone doIt(VirtualFrame frame,
269+
@Bind PythonContext context,
270+
@Bind Node inliningTarget,
271+
@CachedLibrary("context.getPosixSupport()") PosixSupportLibrary posixLib,
272+
@Cached GilNode gil,
273+
@Cached PConstructAndRaiseNode.Lazy constructAndRaiseNode) {
274+
return raiseFatalSignal(frame, context, inliningTarget, posixLib, gil, constructAndRaiseNode, "ABRT", false);
275+
}
276+
}
277+
278+
private static PNone raiseFatalSignal(VirtualFrame frame, PythonContext context, Node inliningTarget, PosixSupportLibrary posixLib, GilNode gil,
279+
PConstructAndRaiseNode.Lazy constructAndRaiseNode, String signalName, boolean releaseGil) {
280+
try {
281+
int signum = SignalModuleBuiltins.signalFromName(context, signalName);
282+
if (releaseGil) {
283+
gil.release(true);
284+
}
285+
try {
286+
posixLib.signalSelf(context.getPosixSupport(), signum);
287+
} finally {
288+
if (releaseGil) {
289+
gil.acquire();
290+
}
291+
}
292+
} catch (PosixException e) {
293+
throw constructAndRaiseNode.get(inliningTarget).raiseOSErrorFromPosixException(frame, e);
294+
} catch (UnsupportedPosixFeatureException e) {
295+
throw constructAndRaiseNode.get(inliningTarget).raiseOSErrorUnsupported(frame, e);
296+
}
297+
return PNone.NONE;
298+
}
299+
238300
@Builtin(name = "dump_traceback_later", minNumOfPositionalArgs = 2, declaresExplicitSelf = true, parameterNames = {"$mod", "timeout", "repeat", "file", "exit"})
239301
@ArgumentClinic(name = "repeat", conversion = ArgumentClinic.ClinicConversion.IntToBoolean, defaultValue = "false")
240302
@ArgumentClinic(name = "exit", conversion = ArgumentClinic.ClinicConversion.IntToBoolean, defaultValue = "false")

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/EmulatedPosixSupport.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -2014,6 +2014,12 @@ public void kill(long pid, int signal,
20142014
}
20152015
}
20162016

2017+
@ExportMessage
2018+
@SuppressWarnings("static-method")
2019+
public void signalSelf(int signal) {
2020+
throw createUnsupportedFeature("signalSelf");
2021+
}
2022+
20172023
@ExportMessage
20182024
@SuppressWarnings("static-method")
20192025
public long killpg(long pgid, int signal) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/LoggingPosixSupport.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -752,6 +752,17 @@ final void kill(long pid, int signal,
752752
}
753753
}
754754

755+
@ExportMessage
756+
final void signalSelf(int signal,
757+
@CachedLibrary("this.delegate") PosixSupportLibrary lib) throws PosixException {
758+
logEnter("signalSelf", "%d", signal);
759+
try {
760+
lib.signalSelf(delegate, signal);
761+
} catch (PosixException e) {
762+
throw logException("signalSelf", e);
763+
}
764+
}
765+
755766
@ExportMessage
756767
final void killpg(long pgid, int signal,
757768
@CachedLibrary("this.delegate") PosixSupportLibrary lib) throws PosixException {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/NFIPosixSupport.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
import java.util.concurrent.atomic.AtomicReferenceArray;
9090
import java.util.logging.Level;
9191

92+
import org.graalvm.nativeimage.ImageInfo;
9293
import com.oracle.graal.python.PythonLanguage;
9394
import com.oracle.graal.python.annotations.PythonOS;
9495
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
@@ -229,6 +230,7 @@ private enum PosixNativeFunction {
229230
get_blocking("(sint32):sint32"),
230231
set_blocking("(sint32, sint32):sint32"),
231232
get_terminal_size("(sint32, [sint32]):sint32"),
233+
signal_self("(sint32):sint32"),
232234
call_kill("(sint64, sint32):sint32"),
233235
call_killpg("(sint64, sint32):sint32"),
234236
call_waitpid("(sint64, [sint32], sint32):sint64"),
@@ -1164,6 +1166,18 @@ public void kill(long pid, int signal,
11641166
}
11651167
}
11661168

1169+
@ExportMessage
1170+
public void signalSelf(int signal,
1171+
@Shared("invoke") @Cached InvokeNativeFunction invokeNode) throws PosixException {
1172+
if (!ImageInfo.inImageRuntimeCode()) {
1173+
throw new UnsupportedPosixFeatureException("self-signals are only supported in native standalone");
1174+
}
1175+
int res = invokeNode.callInt(this, PosixNativeFunction.signal_self, signal);
1176+
if (res == -1) {
1177+
throw getErrnoAndThrowPosixException(invokeNode);
1178+
}
1179+
}
1180+
11671181
@ExportMessage
11681182
public void killpg(long pgid, int signal,
11691183
@Shared("invoke") @Cached InvokeNativeFunction invokeNode) throws PosixException {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PosixSupportLibrary.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -258,6 +258,8 @@ public abstract class PosixSupportLibrary extends Library {
258258

259259
public abstract Object readlinkat(Object receiver, int dirFd, Object path) throws PosixException;
260260

261+
public abstract void signalSelf(Object receiver, int signal) throws PosixException;
262+
261263
public abstract void kill(Object receiver, long pid, int signal) throws PosixException;
262264

263265
public abstract void killpg(Object receiver, long pid, int signal) throws PosixException;

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PreInitPosixSupport.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -588,6 +588,13 @@ final Object readlinkat(int dirFd, Object path,
588588
return nativeLib.readlinkat(nativePosixSupport, dirFd, path);
589589
}
590590

591+
@ExportMessage
592+
final void signalSelf(int signal,
593+
@CachedLibrary("this.nativePosixSupport") PosixSupportLibrary nativeLib) throws PosixException {
594+
checkNotInPreInitialization();
595+
nativeLib.signalSelf(nativePosixSupport, signal);
596+
}
597+
591598
@ExportMessage
592599
final void kill(long pid, int signal,
593600
@CachedLibrary("this.nativePosixSupport") PosixSupportLibrary nativeLib) throws PosixException {

graalpython/python-libposix/src/posix.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -77,6 +77,8 @@
7777
#include <unistd.h>
7878
#include <pwd.h>
7979

80+
int32_t signal_self_segv(void);
81+
8082
#ifdef __APPLE__
8183
#include <util.h>
8284
#else
@@ -602,6 +604,20 @@ int32_t call_kill(int64_t pid, int32_t signal) {
602604
return kill(pid, signal);
603605
}
604606

607+
int32_t signal_self(int32_t signal) {
608+
switch (signal) {
609+
case SIGABRT:
610+
abort();
611+
break;
612+
case SIGSEGV:
613+
return signal_self_segv();
614+
default:
615+
errno = EINVAL;
616+
return -1;
617+
}
618+
_exit(128 + signal);
619+
}
620+
605621
int32_t call_killpg(int64_t pgid, int32_t signal) {
606622
return killpg(pgid, signal);
607623
}

0 commit comments

Comments
 (0)