Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Include/internal/pycore_dict.h
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ _PyDict_NotifyEvent(PyDict_WatchEvent event,
PyObject *value)
{
assert(Py_REFCNT((PyObject*)mp) > 0);
int watcher_bits = mp->_ma_watcher_tag & DICT_WATCHER_MASK;
int watcher_bits = FT_ATOMIC_LOAD_UINT64_RELAXED(mp->_ma_watcher_tag) & DICT_WATCHER_MASK;
if (watcher_bits) {
RARE_EVENT_STAT_INC(watched_dict_modification);
_PyDict_SendEvent(watcher_bits, event, mp, key, value);
Expand Down Expand Up @@ -368,7 +368,7 @@ PyDictObject *_PyObject_MaterializeManagedDict_LockHeld(PyObject *);
static inline Py_ssize_t
_PyDict_UniqueId(PyDictObject *mp)
{
return (Py_ssize_t)(mp->_ma_watcher_tag >> DICT_UNIQUE_ID_SHIFT);
return (Py_ssize_t)(FT_ATOMIC_LOAD_UINT64_RELAXED(mp->_ma_watcher_tag) >> DICT_UNIQUE_ID_SHIFT);
}

static inline void
Expand Down
12 changes: 12 additions & 0 deletions Include/internal/pycore_pyatomic_ft_wrappers.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ extern "C" {
_Py_atomic_load_uint16_relaxed(&value)
#define FT_ATOMIC_LOAD_UINT32_RELAXED(value) \
_Py_atomic_load_uint32_relaxed(&value)
#define FT_ATOMIC_LOAD_UINT64_RELAXED(value) \
_Py_atomic_load_uint64_relaxed(&value)
#define FT_ATOMIC_LOAD_ULONG_RELAXED(value) \
_Py_atomic_load_ulong_relaxed(&value)
#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) \
Expand All @@ -71,6 +73,12 @@ extern "C" {
_Py_atomic_store_uint16_relaxed(&value, new_value)
#define FT_ATOMIC_STORE_UINT32_RELAXED(value, new_value) \
_Py_atomic_store_uint32_relaxed(&value, new_value)
#define FT_ATOMIC_AND_UINT64(value, new_value) \
(void)_Py_atomic_and_uint64(&value, new_value)
#define FT_ATOMIC_OR_UINT64(value, new_value) \
(void)_Py_atomic_or_uint64(&value, new_value)
#define FT_ATOMIC_ADD_UINT64(value, new_value) \
(void)_Py_atomic_add_uint64(&value, new_value)
#define FT_ATOMIC_STORE_CHAR_RELAXED(value, new_value) \
_Py_atomic_store_char_relaxed(&value, new_value)
#define FT_ATOMIC_LOAD_CHAR_RELAXED(value) \
Expand Down Expand Up @@ -146,6 +154,7 @@ extern "C" {
#define FT_ATOMIC_LOAD_UINT8_RELAXED(value) value
#define FT_ATOMIC_LOAD_UINT16_RELAXED(value) value
#define FT_ATOMIC_LOAD_UINT32_RELAXED(value) value
#define FT_ATOMIC_LOAD_UINT64_RELAXED(value) value
#define FT_ATOMIC_LOAD_ULONG_RELAXED(value) value
#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) value = new_value
#define FT_ATOMIC_STORE_PTR_RELEASE(value, new_value) value = new_value
Expand All @@ -157,6 +166,9 @@ extern "C" {
#define FT_ATOMIC_STORE_UINT8_RELAXED(value, new_value) value = new_value
#define FT_ATOMIC_STORE_UINT16_RELAXED(value, new_value) value = new_value
#define FT_ATOMIC_STORE_UINT32_RELAXED(value, new_value) value = new_value
#define FT_ATOMIC_AND_UINT64(value, new_value) (void)(value &= new_value)
#define FT_ATOMIC_OR_UINT64(value, new_value) (void)(value |= new_value)
#define FT_ATOMIC_ADD_UINT64(value, new_value) (void)(value += new_value)
#define FT_ATOMIC_LOAD_CHAR_RELAXED(value) value
#define FT_ATOMIC_STORE_CHAR_RELAXED(value, new_value) value = new_value
#define FT_ATOMIC_LOAD_UCHAR_RELAXED(value) value
Expand Down
93 changes: 93 additions & 0 deletions Lib/idlelib/idle_test/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,99 @@ def test_get_multiple_message(self, mock):
subtests += 1
self.assertEqual(subtests, len(data2)) # All subtests ran?

def _capture_exception(self):
"""Call run.print_exception() and return its stderr output."""
with captured_stderr() as output:
with mock.patch.object(run, 'cleanup_traceback') as ct:
ct.side_effect = lambda t, e: t
run.print_exception()
return output.getvalue()

@force_not_colorized
def test_print_exception_group_nested(self):
try:
try:
raise ExceptionGroup('inner', [ValueError('v1')])
except ExceptionGroup as inner:
raise ExceptionGroup('outer', [inner, TypeError('t1')])
except ExceptionGroup:
tb = self._capture_exception()

self.assertIn('ExceptionGroup: outer (2 sub-exceptions)', tb)
self.assertIn('ExceptionGroup: inner', tb)
self.assertIn('ValueError: v1', tb)
self.assertIn('TypeError: t1', tb)
# Verify tree structure characters.
self.assertIn('+-+---------------- 1 ----------------', tb)
self.assertIn('+---------------- 2 ----------------', tb)
self.assertIn('+------------------------------------', tb)

@force_not_colorized
def test_print_exception_group_chaining(self):
# __cause__ on a sub-exception exercises the prefixed
# chaining-message path (margin chars on separator lines).
sub = TypeError('t1')
sub.__cause__ = ValueError('original')
try:
raise ExceptionGroup('eg1', [sub])
except ExceptionGroup:
tb = self._capture_exception()
self.assertIn('ValueError: original', tb)
self.assertIn('| The above exception was the direct cause', tb)
self.assertIn('ExceptionGroup: eg1', tb)

# __context__ (implicit chaining) on a sub-exception.
sub = TypeError('t2')
sub.__context__ = ValueError('first')
try:
raise ExceptionGroup('eg2', [sub])
except ExceptionGroup:
tb = self._capture_exception()
self.assertIn('ValueError: first', tb)
self.assertIn('| During handling of the above exception', tb)
self.assertIn('ExceptionGroup: eg2', tb)

@force_not_colorized
def test_print_exception_group_seen(self):
shared = ValueError('shared')
try:
raise ExceptionGroup('eg', [shared, shared])
except ExceptionGroup:
tb = self._capture_exception()

self.assertIn('ValueError: shared', tb)
self.assertIn('<exception ValueError has printed>', tb)

@force_not_colorized
def test_print_exception_group_max_width(self):
excs = [ValueError(f'v{i}') for i in range(20)]
try:
raise ExceptionGroup('eg', excs)
except ExceptionGroup:
tb = self._capture_exception()

self.assertIn('+---------------- 15 ----------------', tb)
self.assertIn('+---------------- ... ----------------', tb)
self.assertIn('and 5 more exceptions', tb)
self.assertNotIn('+---------------- 16 ----------------', tb)

@force_not_colorized
def test_print_exception_group_max_depth(self):
def make_nested(depth):
if depth == 0:
return ValueError('leaf')
return ExceptionGroup(f'level{depth}',
[make_nested(depth - 1)])

try:
raise make_nested(15)
except ExceptionGroup:
tb = self._capture_exception()

self.assertIn('... (max_group_depth is 10)', tb)
self.assertIn('ExceptionGroup: level15', tb)
self.assertNotIn('ValueError: leaf', tb)

# StdioFile tests.

class S(str):
Expand Down
97 changes: 80 additions & 17 deletions Lib/idlelib/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,31 +249,94 @@ def print_exception():
sys.last_type, sys.last_value, sys.last_traceback = excinfo
sys.last_exc = val
seen = set()
exclude = ("run.py", "rpc.py", "threading.py", "queue.py",
"debugger_r.py", "bdb.py")
max_group_width = 15
max_group_depth = 10
group_depth = 0

def print_exc_group(typ, exc, tb, prefix=""):
nonlocal group_depth
group_depth += 1
prefix2 = prefix or " "
if group_depth > max_group_depth:
print(f"{prefix2}| ... (max_group_depth is {max_group_depth})",
file=efile)
group_depth -= 1
return
if tb:
if not prefix:
print(" + Exception Group Traceback (most recent call last):", file=efile)
else:
print(f"{prefix}| Exception Group Traceback (most recent call last):", file=efile)
tbe = traceback.extract_tb(tb)
cleanup_traceback(tbe, exclude)
for line in traceback.format_list(tbe):
for subline in line.rstrip().splitlines():
print(f"{prefix2}| {subline}", file=efile)
lines = get_message_lines(typ, exc, tb)
for line in lines:
print(f"{prefix2}| {line}", end="", file=efile)
num_excs = len(exc.exceptions)
if num_excs <= max_group_width:
n = num_excs
else:
n = max_group_width + 1
for i, sub in enumerate(exc.exceptions[:n], 1):
truncated = (i > max_group_width)
first_line_pre = "+-" if i == 1 else " "
title = str(i) if not truncated else '...'
print(f"{prefix2}{first_line_pre}+---------------- {title} ----------------", file=efile)
if truncated:
remaining = num_excs - max_group_width
plural = 's' if remaining > 1 else ''
print(f"{prefix2} | and {remaining} more exception{plural}",
file=efile)
need_print_underline = True
elif id(sub) not in seen:
if not prefix:
print_exc(type(sub), sub, sub.__traceback__, " ")
else:
print_exc(type(sub), sub, sub.__traceback__, prefix + " ")
need_print_underline = not isinstance(sub, BaseExceptionGroup)
else:
print(f"{prefix2} | <exception {type(sub).__name__} has printed>", file=efile)
need_print_underline = True
if need_print_underline and i == n:
print(f"{prefix2} +------------------------------------", file=efile)
group_depth -= 1

def print_exc(typ, exc, tb):
def print_exc(typ, exc, tb, prefix=""):
seen.add(id(exc))
context = exc.__context__
cause = exc.__cause__
prefix2 = f"{prefix}| " if prefix else ""
if cause is not None and id(cause) not in seen:
print_exc(type(cause), cause, cause.__traceback__)
print("\nThe above exception was the direct cause "
"of the following exception:\n", file=efile)
print_exc(type(cause), cause, cause.__traceback__, prefix)
print(f"{prefix2}\n{prefix2}The above exception was the direct cause "
f"of the following exception:\n{prefix2}", file=efile)
elif (context is not None and
not exc.__suppress_context__ and
id(context) not in seen):
print_exc(type(context), context, context.__traceback__)
print("\nDuring handling of the above exception, "
"another exception occurred:\n", file=efile)
if tb:
tbe = traceback.extract_tb(tb)
print('Traceback (most recent call last):', file=efile)
exclude = ("run.py", "rpc.py", "threading.py", "queue.py",
"debugger_r.py", "bdb.py")
cleanup_traceback(tbe, exclude)
traceback.print_list(tbe, file=efile)
lines = get_message_lines(typ, exc, tb)
for line in lines:
print(line, end='', file=efile)
print_exc(type(context), context, context.__traceback__, prefix)
print(f"{prefix2}\n{prefix2}During handling of the above exception, "
f"another exception occurred:\n{prefix2}", file=efile)
if isinstance(exc, BaseExceptionGroup):
print_exc_group(typ, exc, tb, prefix=prefix)
else:
if tb:
print(f"{prefix2}Traceback (most recent call last):", file=efile)
tbe = traceback.extract_tb(tb)
cleanup_traceback(tbe, exclude)
if prefix:
for line in traceback.format_list(tbe):
for subline in line.rstrip().splitlines():
print(f"{prefix}| {subline}", file=efile)
else:
traceback.print_list(tbe, file=efile)
lines = get_message_lines(typ, exc, tb)
for line in lines:
print(f"{prefix2}{line}", end="", file=efile)

print_exc(typ, val, tb)

Expand Down
12 changes: 11 additions & 1 deletion Lib/multiprocessing/popen_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,17 @@ def _launch(self, process_obj):
code = 1
parent_r, child_w = os.pipe()
child_r, parent_w = os.pipe()
self.pid = os.fork()
# gh-146313: Tell the resource tracker's at-fork handler to keep
# the inherited pipe fd so this child reuses the parent's tracker
# (gh-80849) rather than closing it and launching its own.
from .resource_tracker import _fork_intent
_fork_intent.preserve_fd = True
try:
self.pid = os.fork()
finally:
# Reset in both parent and child so the flag does not leak
# into a subsequent raw os.fork() or nested Process launch.
_fork_intent.preserve_fd = False
if self.pid == 0:
try:
atexit._clear()
Expand Down
Loading
Loading