Skip to content

Commit 918a9e8

Browse files
authored
Merge branch 'main' into gc-debug-ft-race
2 parents 1c5f986 + e29cce5 commit 918a9e8

6 files changed

Lines changed: 196 additions & 12 deletions

File tree

‎Doc/library/concurrent.interpreters.rst‎

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,15 @@ This module defines the following functions:
224224
Initialize a new (idle) Python interpreter
225225
and return a :class:`Interpreter` object for it.
226226

227-
.. function:: create_queue()
227+
.. function:: create_queue(maxsize=0)
228228

229229
Initialize a new cross-interpreter queue and return a :class:`Queue`
230230
object for it.
231231

232+
*maxsize* is an integer that sets the upperbound limit on the number of
233+
items that can be placed in the queue. Insertion will be blocked once this
234+
size has been reached, until queue items are consumed. If *maxsize* is
235+
less than or equal to zero, the queue size is infinite.
232236

233237
Interpreter objects
234238
^^^^^^^^^^^^^^^^^^^
@@ -268,7 +272,7 @@ Interpreter objects
268272
Some objects are actually shared and some are copied efficiently,
269273
but most are copied via :mod:`pickle`. See :ref:`interp-object-sharing`.
270274

271-
.. method:: exec(code, /, dedent=True)
275+
.. method:: exec(code, /)
272276

273277
Run the given source code in the interpreter (in the current thread).
274278

@@ -319,7 +323,9 @@ Communicating Between Interpreters
319323
.. class:: Queue(id)
320324

321325
A wrapper around a low-level, cross-interpreter queue, which
322-
implements the :class:`queue.Queue` interface. The underlying queue
326+
implements all the methods of :class:`queue.Queue` except for
327+
:meth:`~queue.Queue.task_done`, :meth:`~queue.Queue.join`
328+
and :meth:`~queue.Queue.shutdown`. The underlying queue
323329
can only be created through :func:`create_queue`.
324330

325331
Some objects are actually shared and some are copied efficiently,
@@ -331,17 +337,59 @@ Communicating Between Interpreters
331337

332338
The queue's ID.
333339

340+
.. method:: qsize()
334341

335-
.. exception:: QueueEmptyError
342+
Return the number of items in the queue.
343+
344+
.. method:: empty()
345+
346+
Return ``True`` if the queue is empty, ``False`` otherwise.
347+
348+
.. method:: full()
349+
350+
Return ``True`` if the queue is full, ``False`` otherwise.
351+
352+
.. method:: put(obj, block=True, timeout=None)
353+
354+
Put *obj* into the queue. If optional args *block* is true and
355+
*timeout* is ``None`` (the default), block if necessary until a free
356+
slot is available. If *timeout* is a positive number, it blocks at
357+
most *timeout* seconds and raises the :exc:`QueueFull` exception if
358+
no free slot was available within that time. Otherwise (*block* is
359+
false), put an object on the queue if a free slot is immediately
360+
available, else raise the :exc:`QueueFull` exception (*timeout* is
361+
ignored in that case).
362+
363+
.. method:: put_nowait(obj)
364+
365+
Equivalent to ``put(obj, block=False)``.
366+
367+
.. method:: get(block=True, timeout=None)
368+
369+
Remove and return an object from the queue. If optional args *block*
370+
is true and *timeout* is ``None`` (the default), block if necessary
371+
until an object is available. If *timeout* is a positive number, it
372+
blocks at most *timeout* seconds and raises the :exc:`QueueEmpty`
373+
exception if no object was available within that time. Otherwise
374+
(*block* is false), return an object if one is immediately available,
375+
else raise the :exc:`QueueEmpty` exception (*timeout* is ignored in
376+
that case).
377+
378+
.. method:: get_nowait()
379+
380+
Equivalent to ``get(block=False)``.
381+
382+
383+
.. exception:: QueueEmpty
336384

337385
This exception, a subclass of :exc:`queue.Empty`, is raised from
338-
:meth:`!Queue.get` and :meth:`!Queue.get_nowait` when the queue
386+
:meth:`Queue.get` and :meth:`Queue.get_nowait` when the queue
339387
is empty.
340388

341-
.. exception:: QueueFullError
389+
.. exception:: QueueFull
342390

343391
This exception, a subclass of :exc:`queue.Full`, is raised from
344-
:meth:`!Queue.put` and :meth:`!Queue.put_nowait` when the queue
392+
:meth:`Queue.put` and :meth:`Queue.put_nowait` when the queue
345393
is full.
346394

347395

@@ -382,3 +430,17 @@ Creating an interpreter and running code in it::
382430

383431
t = interp.call_in_thread(run)
384432
t.join()
433+
434+
Communicating between interpreters with a queue::
435+
436+
from concurrent import interpreters
437+
438+
interp = interpreters.create()
439+
queue = interpreters.create_queue()
440+
441+
def worker(queue):
442+
queue.put('spam!')
443+
444+
t = interp.call_in_thread(worker, queue)
445+
print(queue.get())
446+
t.join()

‎Doc/library/tarfile.rst‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1067,7 +1067,8 @@ can be:
10671067

10681068
* the string ``'tar'``: Honor most *tar*-specific features (i.e. features of
10691069
UNIX-like filesystems), but block features that are very likely to be
1070-
surprising or malicious. See :func:`tar_filter` for details.
1070+
surprising or malicious. Note that this filter does not limit the targets of
1071+
symbolic links. See :func:`tar_filter` for details.
10711072

10721073
* the string ``'data'``: Ignore or block most features specific to UNIX-like
10731074
filesystems. Intended for extracting cross-platform data archives.
@@ -1135,6 +1136,17 @@ reused in custom filters:
11351136

11361137
Return the modified ``TarInfo`` member.
11371138

1139+
.. warning::
1140+
This filter does not check :attr:`TarInfo.linkname`. This means it does
1141+
not limit where a symbolic link member points. An absolute symbolic-link
1142+
target will remain absolute, and a relative target may resolve outside the
1143+
extraction destination. The destination path protection described above
1144+
concerns only where the archive member itself is written, not where a
1145+
symbolic link resolves. The stricter :func:`data_filter` rejects these
1146+
unsafe link targets, raising :class:`~tarfile.AbsoluteLinkError` for
1147+
absolute link targets or :class:`~tarfile.LinkOutsideDestinationError`
1148+
for link targets outside the destination.
1149+
11381150
.. versionchanged:: next
11391151

11401152
Filenames containing ``..`` components are now normalized.

‎Doc/tools/removed-ids.txt‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,7 @@ library/turtle.html: changes-since-python-3-0
114114
# 'turtle' documentation reorganisation (gh-157847)
115115
library/turtle.html: note
116116
library/turtle.html: introduction
117+
118+
# Wrong exception names in 'concurrent.interpreters' docs (gh-136563)
119+
library/concurrent.interpreters.html: concurrent.interpreters.QueueEmptyError
120+
library/concurrent.interpreters.html: concurrent.interpreters.QueueFullError
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""Regression tests for weakref races in subtype_getweakref() (gh-149816)."""
2+
3+
import gc
4+
import sys
5+
import unittest
6+
import weakref
7+
8+
from test.support import threading_helper
9+
10+
11+
@threading_helper.requires_working_threading()
12+
class TestWeakrefRaces(unittest.TestCase):
13+
14+
ITERATIONS = 5_000
15+
16+
def test_getweakref_no_crash(self):
17+
"""obj.__weakref__ racing with concurrent weakref creation/destruction."""
18+
class Target:
19+
pass
20+
21+
obj = Target()
22+
23+
def reader():
24+
for _ in range(self.ITERATIONS):
25+
ref = obj.__weakref__
26+
self.assertIn(type(ref), (type(None), weakref.ref))
27+
if ref is not None:
28+
self.assertIn(ref(), (None, obj))
29+
30+
def mutator():
31+
for _ in range(self.ITERATIONS):
32+
weakref.ref(obj, lambda _: None)
33+
34+
threading_helper.run_concurrently([reader, mutator])
35+
36+
def test_getweakref_return_type(self):
37+
"""__weakref__ must return None or a live weakref.ref."""
38+
class Target:
39+
pass
40+
41+
obj = Target()
42+
43+
self.assertIsNone(obj.__weakref__)
44+
45+
ref = weakref.ref(obj)
46+
result = obj.__weakref__
47+
self.assertIsInstance(result, weakref.ref)
48+
self.assertIs(result(), obj)
49+
50+
del ref, result
51+
gc.collect()
52+
self.assertIsNone(obj.__weakref__)
53+
54+
def test_getweakref_no_refcount_leak(self):
55+
"""Each __weakref__ access must not inflate the weakref's refcount."""
56+
class Target:
57+
pass
58+
59+
obj = Target()
60+
ref = weakref.ref(obj)
61+
before = sys.getrefcount(ref)
62+
63+
for _ in range(1_000):
64+
r = obj.__weakref__
65+
del r
66+
67+
gc.collect()
68+
self.assertEqual(sys.getrefcount(ref), before)
69+
70+
def test_getweakref_many_readers(self):
71+
"""Multiple concurrent readers and mutators must not crash."""
72+
class Target:
73+
pass
74+
75+
obj = Target()
76+
n = 4
77+
iters = self.ITERATIONS // n
78+
79+
def reader():
80+
for _ in range(iters):
81+
ref = obj.__weakref__
82+
self.assertIn(type(ref), (type(None), weakref.ref))
83+
84+
def mutator():
85+
for _ in range(iters):
86+
weakref.ref(obj, lambda _: None)
87+
88+
threading_helper.run_concurrently([reader] * n + [mutator] * n)
89+
90+
91+
if __name__ == "__main__":
92+
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a Use-After-Free race condition in ``subtype_getweakref()`` during
2+
concurrent weakref creation and destruction in the free-threading build.

‎Objects/typeobject.c‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4058,12 +4058,24 @@ subtype_getweakref(PyObject *obj, void *context)
40584058
_PyObject_ASSERT((PyObject *)type,
40594059
((type->tp_weaklistoffset + (Py_ssize_t)sizeof(PyObject *))
40604060
<= type->tp_basicsize));
4061+
4062+
/* Synchronize with weakref creation/destruction so the list head cannot
4063+
* be removed and freed between loading it and taking a reference. */
4064+
LOCK_WEAKREFS(obj);
40614065
weaklistptr = (PyObject **)((char *)obj + type->tp_weaklistoffset);
4062-
if (*weaklistptr == NULL)
4063-
result = Py_None;
4064-
else
4066+
if (*weaklistptr == NULL) {
4067+
result = Py_NewRef(Py_None);
4068+
}
4069+
else if (_Py_TryIncref(*weaklistptr)) {
40654070
result = *weaklistptr;
4066-
return Py_NewRef(result);
4071+
}
4072+
else {
4073+
result = Py_NewRef(Py_None);
4074+
}
4075+
4076+
UNLOCK_WEAKREFS(obj);
4077+
4078+
return result;
40674079
}
40684080

40694081
/* getset definitions for common descriptors */

0 commit comments

Comments
 (0)