Skip to content

Commit d529c1e

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

5 files changed

Lines changed: 98 additions & 73 deletions

File tree

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 & 40 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,10 +43,11 @@
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

49-
import java.io.PrintWriter;
5051
import java.lang.ref.WeakReference;
5152
import java.util.List;
5253

@@ -70,6 +71,8 @@
7071
import com.oracle.graal.python.builtins.objects.type.TypeNodes;
7172
import com.oracle.graal.python.lib.PyNumberAsSizeNode;
7273
import com.oracle.graal.python.lib.PyObjectLookupAttr;
74+
import com.oracle.graal.python.lib.PyObjectSetAttr;
75+
import com.oracle.graal.python.lib.PyObjectStrAsTruffleStringNode;
7376
import com.oracle.graal.python.nodes.ErrorMessages;
7477
import com.oracle.graal.python.nodes.PRaiseNode;
7578
import com.oracle.graal.python.nodes.WriteUnraisableNode;
@@ -87,7 +90,6 @@
8790
import com.oracle.graal.python.nodes.object.GetClassNode;
8891
import com.oracle.graal.python.runtime.GilNode;
8992
import com.oracle.graal.python.runtime.PythonContext;
90-
import com.oracle.graal.python.runtime.exception.ExceptionUtils;
9193
import com.oracle.graal.python.runtime.exception.PException;
9294
import com.oracle.graal.python.runtime.exception.PythonThreadKillException;
9395
import com.oracle.graal.python.runtime.object.PFactory;
@@ -109,13 +111,13 @@
109111
public final class ThreadModuleBuiltins extends PythonBuiltins {
110112

111113
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"});
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"});
119121

120122
@Override
121123
protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFactories() {
@@ -198,57 +200,84 @@ static long getStackSize(VirtualFrame frame, Object stackSizeObj,
198200
@GenerateNodeFactory
199201
abstract static class GetThreadExceptHookNode extends PythonBinaryBuiltinNode {
200202
@Specialization
201-
@TruffleBoundary
202-
Object getExceptHook(PythonModule self,
203-
Object exceptHookArgs,
204-
@Cached PRaiseNode raiseNode) {
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) {
205211

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

214221
Object excType = SequenceStorageNodes.GetItemScalarNode.executeUncached(seq, 0);
215222

216-
if (TypeNodes.IsSameTypeNode.executeUncached(excType, PythonBuiltinClassType.SystemExit))
223+
if (TypeNodes.IsSameTypeNode.executeUncached(excType, PythonBuiltinClassType.SystemExit)) {
217224
return PNone.NONE;
218-
225+
}
219226
Object excValue = SequenceStorageNodes.GetItemScalarNode.executeUncached(seq, 1);
220227
Object excTraceback = SequenceStorageNodes.GetItemScalarNode.executeUncached(seq, 2);
221228
Object thread = SequenceStorageNodes.GetItemScalarNode.executeUncached(seq, 3);
222229

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

226-
Object nameAttr = PyObjectLookupAttr.executeUncached(thread, tsLiteral("_name"));
232+
Object nameAttr = lookupAttr.execute(null, inliningTarget, thread, tsLiteral("_name"));
227233
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"));
234+
name = strNode.execute(null, inliningTarget, nameAttr);
235+
} else {
236+
Object getIdentBuiltin = lookupAttr.execute(null, inliningTarget, thread, tsLiteral("get_ident"));
233237
Object ident = callNode.executeWithoutFrame(getIdentBuiltin);
234-
name = ident != null ? ident.toString() : "<unknown>";
238+
name = ident != null ? strNode.execute(null, inliningTarget, ident) : tsLiteral("<unknown>");
235239
}
236240

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

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;
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+
}
249253
}
250254

251-
ExceptionUtils.printPythonLikeStackTrace(getContext(), pException);
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+
}
252281
return PNone.NONE;
253282
}
254283
}

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: 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)