Skip to content

Commit a7adaa7

Browse files
committed
[GR-73126] Fix excepthook builtin
PullRequest: graalpython/4269
2 parents f5f7e67 + d529c1e commit a7adaa7

6 files changed

Lines changed: 147 additions & 34 deletions

File tree

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,7 +989,13 @@ accepted by asctime(), mktime() and strftime(). May be considered as a
989989
UnraisableHookArgs
990990
991991
Type used to pass arguments to sys.unraisablehook.""")),
992+
PExceptHookArgs(
993+
"_ExceptHookArgs",
994+
PTuple,
995+
newBuilder().publishInModule(J__THREAD).slots(StructSequenceBuiltins.SLOTS, InstantiableStructSequenceBuiltins.SLOTS).doc("""
996+
_ExceptHookArgs
992997
998+
Type used to pass arguments to _thread._excepthook.""")),
993999
PSSLSession("SSLSession", PythonObject, newBuilder().publishInModule(J__SSL).disallowInstantiation()),
9941000
PSSLContext("_SSLContext", PythonObject, newBuilder().publishInModule(J__SSL).basetype().slots(SSLContextBuiltins.SLOTS)),
9951001
PSSLSocket("_SSLSocket", PythonObject, newBuilder().publishInModule(J__SSL).basetype()),

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

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 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
@@ -43,6 +43,8 @@
4343
import static com.oracle.graal.python.builtins.objects.thread.AbstractPythonLock.TIMEOUT_MAX;
4444
import static com.oracle.graal.python.nodes.BuiltinNames.J_EXIT;
4545
import static com.oracle.graal.python.nodes.BuiltinNames.J__THREAD;
46+
import static com.oracle.graal.python.nodes.BuiltinNames.T_STDERR;
47+
import static com.oracle.graal.python.nodes.BuiltinNames.T___EXCEPTHOOK__;
4648
import static com.oracle.graal.python.nodes.BuiltinNames.T__THREAD;
4749
import static com.oracle.graal.python.util.PythonUtils.tsLiteral;
4850

@@ -57,11 +59,20 @@
5759
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
5860
import com.oracle.graal.python.builtins.PythonBuiltins;
5961
import com.oracle.graal.python.builtins.objects.PNone;
62+
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes;
63+
import com.oracle.graal.python.builtins.objects.exception.PBaseException;
6064
import com.oracle.graal.python.builtins.objects.function.PKeyword;
6165
import com.oracle.graal.python.builtins.objects.module.PythonModule;
66+
import com.oracle.graal.python.builtins.objects.object.PythonObject;
6267
import com.oracle.graal.python.builtins.objects.thread.PLock;
6368
import com.oracle.graal.python.builtins.objects.thread.PThread;
69+
import com.oracle.graal.python.builtins.objects.tuple.PTuple;
70+
import com.oracle.graal.python.builtins.objects.tuple.StructSequence;
71+
import com.oracle.graal.python.builtins.objects.type.TypeNodes;
6472
import com.oracle.graal.python.lib.PyNumberAsSizeNode;
73+
import com.oracle.graal.python.lib.PyObjectLookupAttr;
74+
import com.oracle.graal.python.lib.PyObjectSetAttr;
75+
import com.oracle.graal.python.lib.PyObjectStrAsTruffleStringNode;
6576
import com.oracle.graal.python.nodes.ErrorMessages;
6677
import com.oracle.graal.python.nodes.PRaiseNode;
6778
import com.oracle.graal.python.nodes.WriteUnraisableNode;
@@ -76,11 +87,13 @@
7687
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryClinicBuiltinNode;
7788
import com.oracle.graal.python.nodes.function.builtins.clinic.ArgumentClinicProvider;
7889
import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinObjectProfile;
90+
import com.oracle.graal.python.nodes.object.GetClassNode;
7991
import com.oracle.graal.python.runtime.GilNode;
8092
import com.oracle.graal.python.runtime.PythonContext;
8193
import com.oracle.graal.python.runtime.exception.PException;
8294
import com.oracle.graal.python.runtime.exception.PythonThreadKillException;
8395
import com.oracle.graal.python.runtime.object.PFactory;
96+
import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage;
8497
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
8598
import com.oracle.truffle.api.TruffleLanguage;
8699
import com.oracle.truffle.api.TruffleThreadBuilder;
@@ -97,6 +110,15 @@
97110
@CoreFunctions(defineModule = J__THREAD)
98111
public final class ThreadModuleBuiltins extends PythonBuiltins {
99112

113+
public static final StructSequence.BuiltinTypeDescriptor EXCEPTHOOK_ARGS_DESC = new StructSequence.BuiltinTypeDescriptor(
114+
PythonBuiltinClassType.PExceptHookArgs,
115+
4,
116+
new String[]{
117+
"exc_type", "exc_value", "exc_traceback", "thread"},
118+
new String[]{
119+
"Exception type", "Exception value", "Exception traceback",
120+
"Exception thread"});
121+
100122
@Override
101123
protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFactories() {
102124
return ThreadModuleBuiltinsFactory.getFactories();
@@ -106,6 +128,7 @@ protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFa
106128
public void initialize(Python3Core core) {
107129
addBuiltinConstant("error", core.lookupType(PythonBuiltinClassType.RuntimeError));
108130
addBuiltinConstant("TIMEOUT_MAX", TIMEOUT_MAX);
131+
StructSequence.initType(core, EXCEPTHOOK_ARGS_DESC);
109132
core.lookupBuiltinModule(T__THREAD).setModuleState(0);
110133
super.initialize(core);
111134
}
@@ -173,6 +196,92 @@ static long getStackSize(VirtualFrame frame, Object stackSizeObj,
173196
}
174197
}
175198

199+
@Builtin(name = "_excepthook", minNumOfPositionalArgs = 2, declaresExplicitSelf = true)
200+
@GenerateNodeFactory
201+
abstract static class GetThreadExceptHookNode extends PythonBinaryBuiltinNode {
202+
@Specialization
203+
Object getExceptHook(@SuppressWarnings("unused") PythonModule self,
204+
Object exceptHookArgs,
205+
@Bind Node inliningTarget,
206+
@Cached PRaiseNode raiseNode,
207+
@Cached CallNode callNode,
208+
@Cached PyObjectLookupAttr lookupAttr,
209+
@Cached PyObjectSetAttr setAttr,
210+
@Cached PyObjectStrAsTruffleStringNode strNode) {
211+
212+
Object argsType = GetClassNode.GetPythonObjectClassNode.executeUncached((PythonObject) exceptHookArgs);
213+
if (!TypeNodes.IsSameTypeNode.executeUncached(argsType, PythonBuiltinClassType.PExceptHookArgs)) {
214+
throw PRaiseNode.getUncached().raise(raiseNode, PythonBuiltinClassType.TypeError, ErrorMessages.ARG_TYPE_MUST_BE, "_thread.excepthook", "ExceptHookArgs");
215+
}
216+
SequenceStorage seq = ((PTuple) exceptHookArgs).getSequenceStorage();
217+
if (seq.length() != 4) {
218+
throw PRaiseNode.getUncached().raise(raiseNode, PythonBuiltinClassType.TypeError, ErrorMessages.TAKES_EXACTLY_D_ARGUMENTS_D_GIVEN, 4, seq.length());
219+
}
220+
221+
Object excType = SequenceStorageNodes.GetItemScalarNode.executeUncached(seq, 0);
222+
223+
if (TypeNodes.IsSameTypeNode.executeUncached(excType, PythonBuiltinClassType.SystemExit)) {
224+
return PNone.NONE;
225+
}
226+
Object excValue = SequenceStorageNodes.GetItemScalarNode.executeUncached(seq, 1);
227+
Object excTraceback = SequenceStorageNodes.GetItemScalarNode.executeUncached(seq, 2);
228+
Object thread = SequenceStorageNodes.GetItemScalarNode.executeUncached(seq, 3);
229+
230+
TruffleString name;
231+
232+
Object nameAttr = lookupAttr.execute(null, inliningTarget, thread, tsLiteral("_name"));
233+
if (nameAttr != null && nameAttr != PNone.NONE && nameAttr != PNone.NO_VALUE) {
234+
name = strNode.execute(null, inliningTarget, nameAttr);
235+
} else {
236+
Object getIdentBuiltin = lookupAttr.execute(null, inliningTarget, thread, tsLiteral("get_ident"));
237+
Object ident = callNode.executeWithoutFrame(getIdentBuiltin);
238+
name = ident != null ? strNode.execute(null, inliningTarget, ident) : tsLiteral("<unknown>");
239+
}
240+
241+
Object sysMod = getContext().getSysModule();
242+
Object stdErr = lookupAttr.execute(null, inliningTarget, sysMod, T_STDERR);
243+
244+
boolean stdErrInvalid = stdErr == null || stdErr == PNone.NONE || stdErr == PNone.NO_VALUE;
245+
246+
if (stdErrInvalid) {
247+
if (thread != null && thread != PNone.NONE && thread != PNone.NO_VALUE) {
248+
stdErr = lookupAttr.execute(null, inliningTarget, thread, tsLiteral("_stderr"));
249+
}
250+
if (stdErr == null || stdErr == PNone.NONE || stdErr == PNone.NO_VALUE) {
251+
return PNone.NONE;
252+
}
253+
}
254+
255+
Object write = lookupAttr.execute(null, inliningTarget, stdErr, tsLiteral("write"));
256+
Object flush = lookupAttr.execute(null, inliningTarget, stdErr, tsLiteral("flush"));
257+
258+
callNode.executeWithoutFrame(write, tsLiteral("Exception in thread "));
259+
callNode.executeWithoutFrame(write, name);
260+
callNode.executeWithoutFrame(write, tsLiteral(":\n"));
261+
callNode.executeWithoutFrame(flush);
262+
263+
Object sysExcepthook = lookupAttr.execute(null, inliningTarget, sysMod, T___EXCEPTHOOK__);
264+
if (sysExcepthook != PNone.NO_VALUE && sysExcepthook != PNone.NONE) {
265+
if (!stdErrInvalid) {
266+
callNode.executeWithoutFrame(sysExcepthook, excType, excValue, excTraceback);
267+
} else {
268+
Object oldStdErr = lookupAttr.execute(null, inliningTarget, sysMod, T_STDERR);
269+
try {
270+
setAttr.execute(inliningTarget, sysMod, T_STDERR, stdErr);
271+
callNode.executeWithoutFrame(sysExcepthook, excType, excValue, excTraceback);
272+
} finally {
273+
setAttr.execute(inliningTarget, sysMod, T_STDERR, oldStdErr == PNone.NO_VALUE ? PNone.NONE : oldStdErr);
274+
}
275+
}
276+
callNode.executeWithoutFrame(flush);
277+
} else if (excValue instanceof PBaseException) {
278+
callNode.executeWithoutFrame(write, strNode.execute(null, inliningTarget, excValue));
279+
callNode.executeWithoutFrame(flush);
280+
}
281+
return PNone.NONE;
282+
}
283+
}
284+
176285
@Builtin(name = "start_new_thread", minNumOfPositionalArgs = 2, maxNumOfPositionalArgs = 3)
177286
@Builtin(name = "start_new", minNumOfPositionalArgs = 2, maxNumOfPositionalArgs = 3)
178287
@GenerateNodeFactory

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/tuple/InstantiableStructSequenceBuiltins.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 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
@@ -90,7 +90,8 @@
9090
PythonBuiltinClassType.PIntInfo,
9191
PythonBuiltinClassType.PHashInfo,
9292
PythonBuiltinClassType.PThreadInfo,
93-
PythonBuiltinClassType.PUnraisableHookArgs})
93+
PythonBuiltinClassType.PUnraisableHookArgs,
94+
PythonBuiltinClassType.PExceptHookArgs})
9495
public class InstantiableStructSequenceBuiltins extends PythonBuiltins {
9596

9697
public static final TpSlots SLOTS = InstantiableStructSequenceBuiltinsSlotsGen.SLOTS;

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/tuple/StructSequenceBuiltins.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 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
@@ -116,7 +116,8 @@
116116
PythonBuiltinClassType.PIntInfo,
117117
PythonBuiltinClassType.PHashInfo,
118118
PythonBuiltinClassType.PThreadInfo,
119-
PythonBuiltinClassType.PUnraisableHookArgs})
119+
PythonBuiltinClassType.PUnraisableHookArgs,
120+
PythonBuiltinClassType.PExceptHookArgs})
120121
public final class StructSequenceBuiltins extends PythonBuiltins {
121122

122123
public static final TpSlots SLOTS = StructSequenceBuiltinsSlotsGen.SLOTS;

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/TypeNodes.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2603,7 +2603,7 @@ private static int getBuiltinTypeItemsize(PythonBuiltinClassType cls) {
26032603
case PInt, Boolean -> 4;
26042604
case PAsyncGenerator, PFlags, PHashInfo, PTuple, PCoroutine, PGenerator, PThreadInfo, PMemoryView,
26052605
PStatResult, PUnameResult, PStructTime, PFloatInfo, PStatvfsResult, PIntInfo, PFrame,
2606-
PTerminalSize, PUnraisableHookArgs -> 8;
2606+
PTerminalSize, PUnraisableHookArgs, PExceptHookArgs -> 8;
26072607
case PythonClass -> 40;
26082608
default -> 0;
26092609
};

graalpython/lib-python/3/test/test_threading.py

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,20 +1653,19 @@ def outer():
16531653
def test_print_exception(self):
16541654
script = r"""if True:
16551655
import threading
1656-
import time
16571656
1658-
running = False
1657+
started = threading.Event()
1658+
stop = threading.Event()
1659+
16591660
def run():
1660-
global running
1661-
running = True
1662-
while running:
1663-
time.sleep(0.01)
1661+
started.set()
1662+
stop.wait()
16641663
1/0
1664+
16651665
t = threading.Thread(target=run)
16661666
t.start()
1667-
while not running:
1668-
time.sleep(0.01)
1669-
running = False
1667+
started.wait()
1668+
stop.set()
16701669
t.join()
16711670
"""
16721671
rc, out, err = assert_python_ok("-c", script)
@@ -1681,25 +1680,23 @@ def test_print_exception_stderr_is_none_1(self):
16811680
script = r"""if True:
16821681
import sys
16831682
import threading
1684-
import time
16851683
1686-
running = False
1684+
started = threading.Event()
1685+
stop = threading.Event()
1686+
16871687
def run():
1688-
global running
1689-
running = True
1690-
while running:
1691-
time.sleep(0.01)
1688+
started.set()
1689+
stop.wait()
16921690
1/0
1691+
16931692
t = threading.Thread(target=run)
16941693
t.start()
1695-
while not running:
1696-
time.sleep(0.01)
1694+
started.wait()
16971695
sys.stderr = None
1698-
running = False
1696+
stop.set()
16991697
t.join()
17001698
"""
17011699
rc, out, err = assert_python_ok("-c", script)
1702-
self.assertEqual(out, b'')
17031700
err = err.decode()
17041701
self.assertIn("Exception in thread", err)
17051702
self.assertIn("Traceback (most recent call last):", err)
@@ -1710,21 +1707,20 @@ def test_print_exception_stderr_is_none_2(self):
17101707
script = r"""if True:
17111708
import sys
17121709
import threading
1713-
import time
17141710
1715-
running = False
1711+
started = threading.Event()
1712+
stop = threading.Event()
1713+
17161714
def run():
1717-
global running
1718-
running = True
1719-
while running:
1720-
time.sleep(0.01)
1715+
started.set()
1716+
stop.wait()
17211717
1/0
1718+
17221719
sys.stderr = None
17231720
t = threading.Thread(target=run)
17241721
t.start()
1725-
while not running:
1726-
time.sleep(0.01)
1727-
running = False
1722+
started.wait()
1723+
stop.set()
17281724
t.join()
17291725
"""
17301726
rc, out, err = assert_python_ok("-c", script)

0 commit comments

Comments
 (0)