Skip to content
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Copy deque elements directly in deque_copy
  • Loading branch information
colesbury committed Feb 20, 2026
commit 2ce845d444a7b1d0dcd590bdde3be3bcd94844bd
61 changes: 29 additions & 32 deletions Modules/_collectionsmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 (rv != NULL) {
Py_DECREF(rv);
return (PyObject *)new_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;
}
}
}
Py_DECREF(new_deque);
return NULL;
return (PyObject *)new_deque;
}
if (old_deque->maxlen < 0)
result = PyObject_CallOneArg((PyObject *)(Py_TYPE(deque)),
Expand Down Expand Up @@ -1983,22 +1996,6 @@ dequeiter_next(PyObject *op)
// It's safe to access it->deque without holding the per-object lock for it
// here; it->deque is only assigned during construction of it.
dequeobject *deque = it->deque;

#ifdef Py_GIL_DISABLED
// gh-144809: When called from deque_copy(), the deque is already
// locked. The two-object critical section below would unlock and
// re-lock the deque between calls, allowing another thread to modify
// it mid-iteration. The one-object critical section avoids this
// because it keeps the deque locked across calls when it's already
// held, due to a fast-path optimization.
if (_PyObject_IsUniquelyReferenced((PyObject *)it)) {
Py_BEGIN_CRITICAL_SECTION(deque);
result = dequeiter_next_lock_held(it, deque);
Py_END_CRITICAL_SECTION();
return result;
}
#endif

Py_BEGIN_CRITICAL_SECTION2(it, deque);
result = dequeiter_next_lock_held(it, deque);
Py_END_CRITICAL_SECTION2();
Expand Down
Loading