Skip to content

Commit 2fd21f0

Browse files
committed
Use sys.stderr as excepthook outfile
1 parent 325b442 commit 2fd21f0

File tree

5 files changed

+101
-74
lines changed

5 files changed

+101
-74
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -988,9 +988,9 @@ accepted by asctime(), mktime() and strftime(). May be considered as a
988988
989989
Type used to pass arguments to sys.unraisablehook.""")),
990990
PExceptHookArgs(
991-
"_ExceptHookArgs",
992-
PTuple,
993-
newBuilder().publishInModule(J__THREAD).slots(StructSequenceBuiltins.SLOTS, InstantiableStructSequenceBuiltins.SLOTS).doc("""
991+
"_ExceptHookArgs",
992+
PTuple,
993+
newBuilder().publishInModule(J__THREAD).slots(StructSequenceBuiltins.SLOTS, InstantiableStructSequenceBuiltins.SLOTS).doc("""
994994
_ExceptHookArgs
995995
996996
Type used to pass arguments to _thread._excepthook.""")),

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

Lines changed: 69 additions & 38 deletions
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

@@ -70,6 +72,8 @@
7072
import com.oracle.graal.python.builtins.objects.type.TypeNodes;
7173
import com.oracle.graal.python.lib.PyNumberAsSizeNode;
7274
import com.oracle.graal.python.lib.PyObjectLookupAttr;
75+
import com.oracle.graal.python.lib.PyObjectSetAttr;
76+
import com.oracle.graal.python.lib.PyObjectStrAsTruffleStringNode;
7377
import com.oracle.graal.python.nodes.ErrorMessages;
7478
import com.oracle.graal.python.nodes.PRaiseNode;
7579
import com.oracle.graal.python.nodes.WriteUnraisableNode;
@@ -109,13 +113,13 @@
109113
public final class ThreadModuleBuiltins extends PythonBuiltins {
110114

111115
public static final StructSequence.BuiltinTypeDescriptor EXCEPTHOOK_ARGS_DESC = new StructSequence.BuiltinTypeDescriptor(
112-
PythonBuiltinClassType.PExceptHookArgs,
113-
4,
114-
new String[]{
115-
"exc_type", "exc_value", "exc_traceback", "thread"},
116-
new String[]{
117-
"Exception type", "Exception value", "Exception traceback",
118-
"Exception thread"});
116+
PythonBuiltinClassType.PExceptHookArgs,
117+
4,
118+
new String[]{
119+
"exc_type", "exc_value", "exc_traceback", "thread"},
120+
new String[]{
121+
"Exception type", "Exception value", "Exception traceback",
122+
"Exception thread"});
119123

120124
@Override
121125
protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFactories() {
@@ -198,57 +202,84 @@ static long getStackSize(VirtualFrame frame, Object stackSizeObj,
198202
@GenerateNodeFactory
199203
abstract static class GetThreadExceptHookNode extends PythonBinaryBuiltinNode {
200204
@Specialization
201-
@TruffleBoundary
202-
Object getExceptHook(PythonModule self,
203-
Object exceptHookArgs,
204-
@Cached PRaiseNode raiseNode) {
205+
Object getExceptHook(@SuppressWarnings("unused") PythonModule self,
206+
Object exceptHookArgs,
207+
@Bind Node inliningTarget,
208+
@Cached PRaiseNode raiseNode,
209+
@Cached CallNode callNode,
210+
@Cached PyObjectLookupAttr lookupAttr,
211+
@Cached PyObjectSetAttr setAttr,
212+
@Cached PyObjectStrAsTruffleStringNode strNode) {
205213

206214
Object argsType = GetClassNode.GetPythonObjectClassNode.executeUncached((PythonObject) exceptHookArgs);
207-
if (!TypeNodes.IsSameTypeNode.executeUncached(argsType, PythonBuiltinClassType.PExceptHookArgs))
215+
if (!TypeNodes.IsSameTypeNode.executeUncached(argsType, PythonBuiltinClassType.PExceptHookArgs)) {
208216
throw PRaiseNode.getUncached().raise(raiseNode, PythonBuiltinClassType.TypeError, ErrorMessages.ARG_TYPE_MUST_BE, "_thread.excepthook", "ExceptHookArgs");
209-
217+
}
210218
SequenceStorage seq = ((PTuple) exceptHookArgs).getSequenceStorage();
211-
if (seq.length() != 4)
219+
if (seq.length() != 4) {
212220
throw PRaiseNode.getUncached().raise(raiseNode, PythonBuiltinClassType.TypeError, ErrorMessages.TAKES_EXACTLY_D_ARGUMENTS_D_GIVEN, 4, seq.length());
221+
}
213222

214223
Object excType = SequenceStorageNodes.GetItemScalarNode.executeUncached(seq, 0);
215224

216-
if (TypeNodes.IsSameTypeNode.executeUncached(excType, PythonBuiltinClassType.SystemExit))
225+
if (TypeNodes.IsSameTypeNode.executeUncached(excType, PythonBuiltinClassType.SystemExit)) {
217226
return PNone.NONE;
218-
227+
}
219228
Object excValue = SequenceStorageNodes.GetItemScalarNode.executeUncached(seq, 1);
220229
Object excTraceback = SequenceStorageNodes.GetItemScalarNode.executeUncached(seq, 2);
221230
Object thread = SequenceStorageNodes.GetItemScalarNode.executeUncached(seq, 3);
222231

223-
CallNode callNode = CallNode.create();
224-
Object name = null;
232+
TruffleString name;
225233

226-
Object nameAttr = PyObjectLookupAttr.executeUncached(thread, tsLiteral("_name"));
234+
Object nameAttr = lookupAttr.execute(null, inliningTarget, thread, tsLiteral("_name"));
227235
if (nameAttr != null && nameAttr != PNone.NONE && nameAttr != PNone.NO_VALUE) {
228-
name = nameAttr.toString();
229-
}
230-
231-
if (name == null) {
232-
Object getIdentBuiltin = PyObjectLookupAttr.executeUncached(thread, tsLiteral("get_ident"));
236+
name = strNode.execute(null, inliningTarget, nameAttr);
237+
} else {
238+
Object getIdentBuiltin = lookupAttr.execute(null, inliningTarget, thread, tsLiteral("get_ident"));
233239
Object ident = callNode.executeWithoutFrame(getIdentBuiltin);
234-
name = ident != null ? ident.toString() : "<unknown>";
240+
name = ident != null ? strNode.execute(null, inliningTarget, ident) : tsLiteral("<unknown>");
235241
}
236242

237-
PrintWriter pw = new PrintWriter(getContext().getEnv().err(), true);
238-
pw.printf("Exception in thread %s:\n", name);
243+
Object sysMod = getContext().getSysModule();
244+
Object stdErr = lookupAttr.execute(null, inliningTarget, sysMod, T_STDERR);
239245

240-
PException pException;
241-
if (excValue instanceof PException)
242-
pException = (PException) excValue;
243-
else if (excValue instanceof PBaseException base) {
244-
pException = PException.fromObject(base, base.getException().getLocation(), false);
245-
pException.materializeMessage();
246-
} else {
247-
pw.println(excTraceback.toString());
248-
return PNone.NONE;
246+
boolean stdErrInvalid = stdErr == null || stdErr == PNone.NONE || stdErr == PNone.NO_VALUE;
247+
248+
if (stdErrInvalid) {
249+
if (thread != null && thread != PNone.NONE && thread != PNone.NO_VALUE) {
250+
stdErr = lookupAttr.execute(null, inliningTarget, thread, tsLiteral("_stderr"));
251+
}
252+
if (stdErr == null || stdErr == PNone.NONE || stdErr == PNone.NO_VALUE) {
253+
return PNone.NONE;
254+
}
249255
}
250256

251-
ExceptionUtils.printPythonLikeStackTrace(getContext(), pException);
257+
Object write = lookupAttr.execute(null, inliningTarget, stdErr, tsLiteral("write"));
258+
Object flush = lookupAttr.execute(null, inliningTarget, stdErr, tsLiteral("flush"));
259+
260+
callNode.executeWithoutFrame(write, tsLiteral("Exception in thread "));
261+
callNode.executeWithoutFrame(write, name);
262+
callNode.executeWithoutFrame(write, tsLiteral(":\n"));
263+
callNode.executeWithoutFrame(flush);
264+
265+
Object sysExcepthook = lookupAttr.execute(null, inliningTarget, sysMod, T___EXCEPTHOOK__);
266+
if (sysExcepthook != PNone.NO_VALUE && sysExcepthook != PNone.NONE) {
267+
if (!stdErrInvalid) {
268+
callNode.executeWithoutFrame(sysExcepthook, excType, excValue, excTraceback);
269+
} else {
270+
Object oldStdErr = lookupAttr.execute(null, inliningTarget, sysMod, T_STDERR);
271+
try {
272+
setAttr.execute(inliningTarget, sysMod, T_STDERR, stdErr);
273+
callNode.executeWithoutFrame(sysExcepthook, excType, excValue, excTraceback);
274+
} finally {
275+
setAttr.execute(inliningTarget, sysMod, T_STDERR, oldStdErr == PNone.NO_VALUE ? PNone.NONE : oldStdErr);
276+
}
277+
}
278+
callNode.executeWithoutFrame(flush);
279+
} else if (excValue instanceof PBaseException) {
280+
callNode.executeWithoutFrame(write, strNode.execute(null, inliningTarget, excValue));
281+
callNode.executeWithoutFrame(flush);
282+
}
252283
return PNone.NONE;
253284
}
254285
}

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

Lines changed: 1 addition & 1 deletion
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

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

Lines changed: 1 addition & 1 deletion
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

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

Lines changed: 27 additions & 31 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)
@@ -1812,9 +1808,9 @@ def setUp(self):
18121808

18131809
def test_excepthook(self):
18141810
with support.captured_output("stderr") as stderr:
1815-
thread = ThreadRunFail(name="excepthook thread")
1816-
thread.start()
1817-
thread.join()
1811+
thread = ThreadRunFail(name="excepthook thread")
1812+
thread.start()
1813+
thread.join()
18181814

18191815
stderr = stderr.getvalue().strip()
18201816
self.assertIn(f'Exception in thread {thread.name}:\n', stderr)

0 commit comments

Comments
 (0)