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 Doc/library/concurrent.interpreters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ objects are either directly shared or copied efficiently. For example:
* :class:`float`
* :class:`tuple` (of similarly supported objects)

There is a small number of Python types that actually share mutable
There are a small number of Python types that actually share mutable
data between interpreters:

* :class:`memoryview`
Expand Down Expand Up @@ -274,7 +274,7 @@ Interpreter objects

.. method:: call(callable, /, *args, **kwargs)

Return the result of calling running the given function in the
Return the result of running the given function in the
interpreter (in the current thread).

.. _interp-call-in-thread:
Expand Down
8 changes: 8 additions & 0 deletions Lib/tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -1601,6 +1601,14 @@ def _proc_pax(self, tarfile):
# Fetch the next header.
try:
next = self._fromtarfile(tarfile, dircheck=False)
except EOFHeaderError:
if self.type == XGLTYPE:
# If this is a global header at the end of the archive
# (no regular members follow), let the EOFHeaderError
# propagate so the caller handles end-of-archive normally.
tarfile.offset = tarfile.fileobj.tell() - BLOCKSIZE
raise
raise SubsequentHeaderError("end of file header") from None
except HeaderError as e:
raise SubsequentHeaderError(str(e)) from None

Expand Down
25 changes: 24 additions & 1 deletion Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,10 @@ async def sleep(delay, result=None):
'''a = [x async for x in (x async for x in arange(5))][1]''',
'''a, = [1 for x in {x async for x in arange(1)}]''',
'''a = [await sleep(0, x) async for x in arange(2)][1]''',
'''a = [await sleep(0, 1) for _ in [0]][0]''',
'''a = {await sleep(0, 1) for _ in [0]}.pop()''',
'''a = {0: await sleep(0, 1) for _ in [0]}[0]''',
'''a = (lambda x=[await sleep(0, 1) for _ in [0]]: x)()[0]''',
# gh-121637: Make sure we correctly handle the case where the
# async code is optimized away
'''assert not await sleep(0); a = 1''',
Expand Down Expand Up @@ -610,7 +614,26 @@ async def __aexit__(self, *exc_info):
'''def f():
async with Lock() as l:
a = 1
'''
''',
'''class C:
[await x for x in y]
''',
'''class C:
[x async for x in arange(10)]
''',
'''async def f():
class C:
[await x for x in y]
''',
'''lambda: [await x for x in y]''',
'''class C:
def f(self, x=[await y for y in z]):
pass
''',
'''type T = [await x for x in y]''',
'''async def f[T=[await x for x in y]]():
pass
''',
]
for mode, code_sample in product(modes, code_samples):
source = dedent(code_sample)
Expand Down
15 changes: 15 additions & 0 deletions Lib/test/test_capi/test_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,5 +342,20 @@ def test_pyobject_dump(self):
self.assertRegex(output, r'<object at .* is freed>')


class RefTracerTest(unittest.TestCase):
@support.requires_resource('cpu')
@support.skip_emscripten_stack_overflow()
@support.skip_wasi_stack_overflow()
def test_destroy_traced_for_trashcan_deferred_objects(self):
depth = 200_000
chain = None
for _ in range(depth):
chain = [chain]
_testcapi.start_counting_list_destroys()
del chain
destroys = _testcapi.stop_counting_list_destroys()
self.assertEqual(destroys, depth)


if __name__ == "__main__":
unittest.main()
113 changes: 113 additions & 0 deletions Lib/test/test_coroutines.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ast
import contextlib
import copy
import inspect
Expand Down Expand Up @@ -407,6 +408,118 @@ async def bar():
with self.subTest(code=code), self.assertRaises(SyntaxError):
compile(code, "<test>", "exec")

def test_async_comprehension_scope(self):
# List/set/dict comprehensions with await or async for are allowed
# only in async functions, or at module level with top-level await.
allowed = [
"async def f():\n [await x for x in y]",
"async def f():\n {await x for x in y}",
"async def f():\n {k: await x for k, x in y}",
"async def f():\n [x async for x in y]",
"async def f():\n {x async for x in y}",
"async def f():\n {k: x async for k, x in y}",
"async def f():\n [[await x for x in y] for y in z]",
# Defaults, bases, and genexp iterables are evaluated in the
# enclosing scope.
"async def outer():\n async def f(x=[await y for y in z]): pass",
"async def f():\n class C([await x for x in y]): pass",
"async def f():\n (x for x in [await y for y in z])",
"async def f():\n (x for x in [y async for y in z])",
]
for code in allowed:
with self.subTest(code=code):
compile(code, "<test>", "exec")

# Generator expressions with await are async genexps and may appear
# outside async functions. A listcomp nested in a genexp body is also
# allowed (the genexp becomes an async generator).
for code in [
"(await x for x in y)",
"def f():\n (await x for x in y)",
"class C:\n (await x for x in y)",
"lambda: (await x for x in y)",
"([await x for x in y] for y in z)",
"def f():\n ([await x for x in y] for y in z)",
"class C:\n ([await x for x in y] for y in z)",
"async def f():\n ([await x for x in y] for y in z)",
]:
with self.subTest(code=code):
compile(code, "<test>", "exec")

err = "asynchronous comprehension outside of an asynchronous function"
invalid = [
"[await x for x in y]",
"{await x for x in y}",
"{k: await x for k, x in y}",
"[x async for x in y]",
"{x async for x in y}",
"{k: x async for k, x in y}",
"[[await x for x in y] for y in z]",
"[[x async for x in y] for y in z]",
"def f():\n [await x for x in y]",
"def f():\n [x async for x in y]",
"async def f():\n def g():\n [await x for x in y]",
"class C:\n [await x for x in y]",
"class C:\n {await x for x in y}",
"class C:\n {k: await x for k, x in y}",
"class C:\n [x async for x in y]",
"class C:\n [[await x for x in y] for y in z]",
"async def f():\n class C:\n x = [await y for y in z]",
"async def f():\n class C:\n x = [y async for y in z]",
# Lambdas are never async, even inside async def.
"lambda: [await x for x in y]",
"async def f():\n lambda: [await x for x in y]",
"class C:\n f = lambda: [await x for x in y]",
# Defaults, bases, and genexp iterables run in the enclosing scope.
"(x for x in [await y for y in z])",
"(x for x in [y async for y in z])",
"def f():\n (x for x in [await y for y in z])",
"async def f(x=[await y for y in z]): pass",
"def f(x=[await y for y in z]): pass",
"class C:\n def f(self, x=[await y for y in z]): pass",
"class C([await x for x in y]): pass",
# Type aliases and type-parameter scopes.
"type T = [await x for x in y]",
"type T = [x async for x in y]",
"def f[T=[await x for x in y]](): pass",
"def f[T: [await x for x in y]](): pass",
"async def f[T=[await x for x in y]](): pass",
"async def f(x: [await y for y in z]): pass",
]
for code in invalid:
with self.subTest(code=code):
support.check_syntax_error(self, code, err)

support.check_syntax_error(
self, "await x", "'await' outside function")
support.check_syntax_error(
self, "class C:\n await x", "'await' outside function")
support.check_syntax_error(
self, "def f():\n await x", "'await' outside async function")

flags = ast.PyCF_ALLOW_TOP_LEVEL_AWAIT
for code in [
"[await x for x in y]",
"async def f(x=[await y for y in z]): pass",
"class C([await x for x in y]): pass",
"f'{[await x for x in y]}'",
"(x for x in [await y for y in z])",
"(x for x in [y async for y in z])",
]:
with self.subTest(code=code, tla=True):
compile(code, "<test>", "exec", flags=flags)
still_invalid = [
"lambda: [await x for x in y]",
"def f():\n (x for x in [await y for y in z])",
"class C:\n def f(self, x=[await y for y in z]): pass",
"type T = [await x for x in y]",
"async def f[T=[await x for x in y]](): pass",
]
for code in still_invalid:
with self.subTest(code=code, tla=True):
with self.assertRaisesRegex(SyntaxError, err):
compile(code, "<test>", "exec", flags=flags)

def test_badsyntax_2(self):
samples = [
"""def foo():
Expand Down
29 changes: 29 additions & 0 deletions Lib/test/test_tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2499,6 +2499,35 @@ def test_pax_global_header(self):
finally:
tar.close()

def test_pax_global_header_empty_archive(self):
# An archive that contains only a global header and no regular
# members should be opened successfully (gh-149578).
pax_headers = {"foo": "bar"}

# Create a PAX archive with global headers but no file entries.
with tarfile.open(tmpname, "w", format=tarfile.PAX_FORMAT,
pax_headers=pax_headers):
pass

# Reading the archive should work and preserve global headers.
with tarfile.open(tmpname) as tar:
self.assertEqual(tar.pax_headers, pax_headers)
self.assertEqual(tar.getmembers(), [])

# Appending to the archive should work.
with tarfile.open(tmpname, "a") as tar:
self.assertEqual(tar.pax_headers, pax_headers)
self.assertEqual(tar.getmembers(), [])
tar.addfile(tarfile.TarInfo("test"))

# Verify the appended member is present and global headers
# are preserved.
with tarfile.open(tmpname) as tar:
self.assertEqual(tar.pax_headers, pax_headers)
members = tar.getmembers()
self.assertEqual(len(members), 1)
self.assertEqual(members[0].name, "test")

def test_pax_extended_header(self):
# The fields from the pax header have priority over the
# TarInfo.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix missing ``PyRefTracer_DESTROY`` events for trashcan deferred objects.
Patch by Donghee Na.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix :func:`tarfile.open` failing with :exc:`~tarfile.ReadError` when
opening a PAX format tar archive that contains only global headers and
no regular members.
33 changes: 33 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ static struct PyModuleDef _testcapimodule;
// Module state
typedef struct {
PyObject *error; // _testcapi.error object
Py_ssize_t list_destroys;
} testcapistate_t;

static testcapistate_t*
Expand Down Expand Up @@ -2412,6 +2413,36 @@ test_reftracer(PyObject *ob, PyObject *Py_UNUSED(ignored))
return NULL;
}

static int
_listdestroytracer(PyObject *obj, PyRefTracerEvent event, void *data)
{
if (event == PyRefTracer_DESTROY && PyList_CheckExact(obj)) {
_Py_atomic_add_ssize((Py_ssize_t *)data, 1);
}
return 0;
}

static PyObject *
start_counting_list_destroys(PyObject *self, PyObject *Py_UNUSED(ignored))
{
testcapistate_t *state = get_testcapi_state(self);
_Py_atomic_store_ssize(&state->list_destroys, 0);
if (PyRefTracer_SetTracer(_listdestroytracer, &state->list_destroys) != 0) {
return NULL;
}
Py_RETURN_NONE;
}

static PyObject *
stop_counting_list_destroys(PyObject *self, PyObject *Py_UNUSED(ignored))
{
if (PyRefTracer_SetTracer(NULL, NULL) != 0) {
return NULL;
}
return PyLong_FromSsize_t(
_Py_atomic_load_ssize(&get_testcapi_state(self)->list_destroys));
}

static PyObject *
function_set_warning(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
{
Expand Down Expand Up @@ -3016,6 +3047,8 @@ static PyMethodDef TestMethods[] = {
{"test_buildvalue_N", test_buildvalue_N, METH_NOARGS},
{"test_buildvalue_p", test_buildvalue_p, METH_NOARGS},
{"test_reftracer", test_reftracer, METH_NOARGS},
{"start_counting_list_destroys", start_counting_list_destroys, METH_NOARGS},
{"stop_counting_list_destroys", stop_counting_list_destroys, METH_NOARGS},
{"_test_thread_state", test_thread_state, METH_VARARGS},
{"gilstate_ensure_release", gilstate_ensure_release, METH_NOARGS},
#ifndef MS_WINDOWS
Expand Down
4 changes: 4 additions & 0 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -3228,6 +3228,10 @@ _PyTrash_thread_destroy_chain(PyThreadState *tstate)
* up distorting allocation statistics.
*/
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
#ifdef Py_TRACE_REFS
_Py_ForgetReference(op);
#endif
_PyReftracerTrack(op, PyRefTracer_DESTROY);
(*dealloc)(op);
}
}
Expand Down
Loading