mirror of
https://github.com/python/cpython.git
synced 2026-04-13 23:31:02 +00:00
[3.14] gh-144809: Make deque copy atomic in free-threaded build (gh-144966) (#145053)
(cherry picked from commit 70da972f97)
Co-authored-by: Sam Gross <colesbury@gmail.com>
This commit is contained in:
parent
e69501969b
commit
07dbda5a57
3 changed files with 59 additions and 16 deletions
29
Lib/test/test_free_threading/test_collections.py
Normal file
29
Lib/test/test_free_threading/test_collections.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import unittest
|
||||
from collections import deque
|
||||
from copy import copy
|
||||
from test.support import threading_helper
|
||||
|
||||
threading_helper.requires_working_threading(module=True)
|
||||
|
||||
|
||||
class TestDeque(unittest.TestCase):
|
||||
def test_copy_race(self):
|
||||
# gh-144809: Test that deque copy is thread safe. It previously
|
||||
# could raise a "deque mutated during iteration" error.
|
||||
d = deque(range(100))
|
||||
|
||||
def mutate():
|
||||
for i in range(1000):
|
||||
d.append(i)
|
||||
if len(d) > 200:
|
||||
d.popleft()
|
||||
|
||||
def copy_loop():
|
||||
for _ in range(1000):
|
||||
copy(d)
|
||||
|
||||
threading_helper.run_concurrently([mutate, copy_loop])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
@ -0,0 +1 @@
|
|||
Make :class:`collections.deque` copy atomic in the :term:`free-threaded build`.
|
||||
|
|
@ -605,29 +605,42 @@ deque_copy_impl(dequeobject *deque)
|
|||
collections_state *state = find_module_state_by_def(Py_TYPE(deque));
|
||||
if (Py_IS_TYPE(deque, state->deque_type)) {
|
||||
dequeobject *new_deque;
|
||||
PyObject *rv;
|
||||
Py_ssize_t n = Py_SIZE(deque);
|
||||
|
||||
new_deque = (dequeobject *)deque_new(state->deque_type, NULL, NULL);
|
||||
if (new_deque == NULL)
|
||||
return NULL;
|
||||
new_deque->maxlen = old_deque->maxlen;
|
||||
/* Fast path for the deque_repeat() common case where len(deque) == 1
|
||||
*
|
||||
* It's safe to not acquire the per-object lock for new_deque; it's
|
||||
* invisible to other threads.
|
||||
|
||||
/* Copy elements directly by walking the block structure.
|
||||
* This is safe because the caller holds the deque lock and
|
||||
* the new deque is not yet visible to other threads.
|
||||
*/
|
||||
if (Py_SIZE(deque) == 1) {
|
||||
PyObject *item = old_deque->leftblock->data[old_deque->leftindex];
|
||||
rv = deque_append_impl(new_deque, item);
|
||||
} else {
|
||||
rv = deque_extend_impl(new_deque, (PyObject *)deque);
|
||||
if (n > 0) {
|
||||
block *b = old_deque->leftblock;
|
||||
Py_ssize_t index = old_deque->leftindex;
|
||||
|
||||
/* Space saving heuristic. Start filling from the left */
|
||||
assert(new_deque->leftblock == new_deque->rightblock);
|
||||
assert(new_deque->leftindex == new_deque->rightindex + 1);
|
||||
new_deque->leftindex = 1;
|
||||
new_deque->rightindex = 0;
|
||||
|
||||
for (Py_ssize_t i = 0; i < n; i++) {
|
||||
PyObject *item = b->data[index];
|
||||
if (deque_append_lock_held(new_deque, Py_NewRef(item),
|
||||
new_deque->maxlen) < 0) {
|
||||
Py_DECREF(new_deque);
|
||||
return NULL;
|
||||
}
|
||||
index++;
|
||||
if (index == BLOCKLEN) {
|
||||
b = b->rightlink;
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rv != NULL) {
|
||||
Py_DECREF(rv);
|
||||
return (PyObject *)new_deque;
|
||||
}
|
||||
Py_DECREF(new_deque);
|
||||
return NULL;
|
||||
return (PyObject *)new_deque;
|
||||
}
|
||||
if (old_deque->maxlen < 0)
|
||||
result = PyObject_CallOneArg((PyObject *)(Py_TYPE(deque)),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue