mirror of
https://github.com/python/cpython.git
synced 2025-12-10 11:00:14 +00:00
gh-129967: Fix race condition in repr(set) (gh-129978)
The call to `PySequence_List()` could temporarily unlock and relock the
set, allowing the items to be cleared and return the incorrect
notation `{}` for a empty set (it should be `set()`).
Co-authored-by: T. Wouters <thomas@python.org>
This commit is contained in:
parent
1f233f56d6
commit
a7427f2db9
3 changed files with 54 additions and 2 deletions
41
Lib/test/test_free_threading/test_set.py
Normal file
41
Lib/test/test_free_threading/test_set.py
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from threading import Thread, Barrier
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from test.support import threading_helper
|
||||||
|
|
||||||
|
|
||||||
|
@threading_helper.requires_working_threading()
|
||||||
|
class TestSet(TestCase):
|
||||||
|
def test_repr_clear(self):
|
||||||
|
"""Test repr() of a set while another thread is calling clear()"""
|
||||||
|
NUM_ITERS = 10
|
||||||
|
NUM_REPR_THREADS = 10
|
||||||
|
barrier = Barrier(NUM_REPR_THREADS + 1)
|
||||||
|
s = {1, 2, 3, 4, 5, 6, 7, 8}
|
||||||
|
|
||||||
|
def clear_set():
|
||||||
|
barrier.wait()
|
||||||
|
s.clear()
|
||||||
|
|
||||||
|
def repr_set():
|
||||||
|
barrier.wait()
|
||||||
|
set_reprs.append(repr(s))
|
||||||
|
|
||||||
|
for _ in range(NUM_ITERS):
|
||||||
|
set_reprs = []
|
||||||
|
threads = [Thread(target=clear_set)]
|
||||||
|
for _ in range(NUM_REPR_THREADS):
|
||||||
|
threads.append(Thread(target=repr_set))
|
||||||
|
for t in threads:
|
||||||
|
t.start()
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
|
||||||
|
for set_repr in set_reprs:
|
||||||
|
self.assertIn(set_repr, ("set()", "{1, 2, 3, 4, 5, 6, 7, 8}"))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fix a race condition in the :term:`free threading` build when ``repr(set)``
|
||||||
|
is called concurrently with ``set.clear()``.
|
||||||
|
|
@ -535,9 +535,18 @@ set_repr_lock_held(PySetObject *so)
|
||||||
return PyUnicode_FromFormat("%s()", Py_TYPE(so)->tp_name);
|
return PyUnicode_FromFormat("%s()", Py_TYPE(so)->tp_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
keys = PySequence_List((PyObject *)so);
|
// gh-129967: avoid PySequence_List because it might re-lock the object
|
||||||
if (keys == NULL)
|
// lock or the GIL and allow something to clear the set from underneath us.
|
||||||
|
keys = PyList_New(so->used);
|
||||||
|
if (keys == NULL) {
|
||||||
goto done;
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_ssize_t pos = 0, idx = 0;
|
||||||
|
setentry *entry;
|
||||||
|
while (set_next(so, &pos, &entry)) {
|
||||||
|
PyList_SET_ITEM(keys, idx++, Py_NewRef(entry->key));
|
||||||
|
}
|
||||||
|
|
||||||
/* repr(keys)[1:-1] */
|
/* repr(keys)[1:-1] */
|
||||||
listrepr = PyObject_Repr(keys);
|
listrepr = PyObject_Repr(keys);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue