GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — `collections.deque` is not atomic during copy · Issue #144809 · python/cpython

collections.deque is not atomic during copy #144809

New issue
Copy link
New issue
Copy link
Closed
Closed
collections.deque is not atomic during copy#144809
Copy link
Labels
extension-modulesC modules in the Modules dirC modules in the Modules dirtopic-free-threadingtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@yilei
yilei
opened on Feb 13, 2026
Issue body actions

[Edit: This bug was originally reported as "not thread safe", and updated to "not atomic"]

Bug report

Bug description:

In free-threaded mode, calling copy on collections.deque is not thread safe atomic and can raise RuntimeError: deque mutated during iteration errors.

Example code:

import sys
import threading
from collections import deque
from copy import copy

print(f"Python {sys.version}")
print(f"GIL enabled: {sys._is_gil_enabled()}")

d = deque(range(100))
stop = threading.Event()


def mutate():
    i = 0
    while not stop.is_set():
        d.append(i)
        if len(d) > 200:
            d.popleft()
        i += 1


def copy_loop():
    while not stop.is_set():
        copy(d)


barrier = threading.Barrier(5)


def run(fn):
    barrier.wait()
    fn()


threads = [
    threading.Thread(target=run, args=(f,), daemon=True)
    for f in [mutate, mutate, copy_loop, copy_loop]
]
for t in threads:
    t.start()

barrier.wait()

stop.wait(timeout=5)
stop.set()
for t in threads:
    t.join(timeout=5)

print("Done")

Running under free-threaded build:

❯ ./python /tmp/deque.py
Python 3.15.0a6+ free-threading build (heads/main:945bf8ce1bf, Feb 13 2026, 22:04:08) [GCC 11.4.0]
GIL enabled: False
Exception in thread Thread-4 (run):
Exception in thread Thread-3 (run):
Traceback (most recent call last):
Traceback (most recent call last):
  File "cpython/Lib/threading.py", line 1075, in _bootstrap_inner
    self._context.run(self.run)
    ~~~~~~~~~~~~~~~~~^^^^^^^^^^
  File "cpython/Lib/threading.py", line 1017, in run
    self._target(*self._args, **self._kwargs)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/deque.py", line 32, in run
    fn()
    ~~^^
  File "/tmp/deque.py", line 24, in copy_loop
    copy(d)
    ~~~~^^^
  File "cpython/Lib/copy.py", line 82, in copy
    return copier(x)
RuntimeError: deque mutated during iteration
  File "cpython/Lib/threading.py", line 1075, in _bootstrap_inner
    self._context.run(self.run)
    ~~~~~~~~~~~~~~~~~^^^^^^^^^^
  File "cpython/Lib/threading.py", line 1017, in run
    self._target(*self._args, **self._kwargs)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/deque.py", line 32, in run
    fn()
    ~~^^
  File "/tmp/deque.py", line 24, in copy_loop
    copy(d)
    ~~~~^^^
  File "cpython/Lib/copy.py", line 82, in copy
    return copier(x)
RuntimeError: deque mutated during iteration
Done

CPython versions tested on:

CPython main branch, 3.14

Operating systems tested on:

Linux

Linked PRs

  • gh-144809: Make deque copy atomic in free-threaded build #144966
  • [3.14] gh-144809: Make deque copy atomic in free-threaded build (gh-144966) #145053

Activity

  1. yilei
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on Feb 13, 2026
  2. johnslavik
    added
    topic-free-threading
    extension-modulesC modules in the Modules dirC modules in the Modules dir
    on Feb 13, 2026
  3. serhiy-storchaka commented on Feb 15, 2026

    @serhiy-storchaka
    serhiy-storchaka
    on Feb 15, 2026
    Member
    More actions

    I do not think it is not thread safe. Copying does not crash Python, does not corrupt memory, and does not left a deque in an inconsistent state. It is not atomic -- but you cannot in general expect that an operation with non-constant complexity on collection is atomic.

    cc @rhettinger

  4. yilei commented on Feb 16, 2026

    @yilei
    yilei
    on Feb 16, 2026
    ContributorAuthor
    More actions

    Thanks for taking a look at this!

    Hmm, my mental model of thread safety is that I'd expect copy(d) would reasonably give me a copy when there are other concurrent mutations in parallel, without using e.g. a lock to guard the copy and mutations. And perhaps that's a wrong interpretation? And non-atomic is the better term. My assumption was largely based on:

    1. Seeing earlier free-threading work from gh-112050: Make collections.deque thread-safe in free-threaded builds #113830
    2. Similar code for dict and list succeeds without errors

    Btw, additional context for this bug report is getsentry/sentry-python#5465 where we encountered this issue.

    I'm fine if we couldn't make copy(d) work here without users adding guards around calls to deque methods (though IMO it hurts usability), it would be great if we could document this expectation somewhere.

  5. colesbury commented on Feb 17, 2026

    @colesbury
    colesbury
    on Feb 17, 2026
    Contributor
    More actions

    I think we should make this behave atomically because:

    1. it's written so that it behaves atomically in the GIL-enabled build.
    2. it should be relatively easy (... famous last words)
    3. the motivation is a real bug report in user code

    The problem is a combination of:

    1. We lock the to-be-copied deque when __copy__ is called (good!)
    2. We iterate over the to-be-copied deque, which tries to lock both the iterator and the deque

    That triggers the deadlock avoidance routine, which unlocks the deque before re-locking both the iterator and the deque. The time in between the unlock and re-locking allows another thread to modify the deque (which would not be allowed in the GIL-enabled build).

    Reacted by Yilei
  6. Yhg1s commented on Feb 18, 2026

    @Yhg1s
    Yhg1s
    on Feb 18, 2026
    Member
    More actions

    Given that we already have an optimization where we skip locking (directly) nested critical sections of the same object, and we order CRITICAL_SECTION2 locks by mutex address to avoid deadlocks (so the order of it/deque locking is already opaque to the caller) -- couldn't we just skip locking whichever lock in a CRITICAL_SECTION2 is currently locked in the (previous) top-most critical section?

    Reacted by Yilei
  7. yilei commented on Feb 18, 2026

    @yilei
    yilei
    on Feb 18, 2026
    ContributorAuthor
    More actions

    couldn't we just skip locking whichever lock in a CRITICAL_SECTION2 is currently locked in the (previous) top-most critical section?

    I think this works, and it's a good optimization to have anyway. I'll clean up my draft code, add some tests, and send a PR.

  8. colesbury commented on Feb 18, 2026

    @colesbury
    colesbury
    on Feb 18, 2026
    Contributor
    More actions

    couldn't we just skip locking whichever lock in a CRITICAL_SECTION2 is currently locked in the (previous) top-most critical section?

    That would work for the specific code in deque, but it's not safe in general because if the thread state is detached and then attached, only the (new) top-most critical section is locked. For example:

    Py_BEGIN_CRITICAL_SECTION(a);
    Py_BEGIN_CRITICAL_SECTION2(a, b); // only locks b?
    
    _PyThreadState_Detach(tstate);
    _PyThreadState_Attach(tstate);
    
    // it's important that both a and b are locked here.
    // if the Py_BEGIN_CRITICAL_SECTION2 only pushed
    // a critical section containing b, then only b will be locked
    
    Py_END_CRITICAL_SECTION2();
    Py_END_CRITICAL_SECTION();

    I think we could probably get something to work safely along the lines of "only lock b", but I think it'll be complicated.

    Reacted by Yilei
  9. colesbury
    added a commit that references this issue on Feb 18, 2026

    pythongh-144809: Make deque copy atomic in free-threaded build

    dd05e67
  10. colesbury commented on Feb 18, 2026

    @colesbury
    colesbury
    on Feb 18, 2026
    Contributor
    More actions

    #144966 is a small change that I'd be comfortable backporting to 3.14.

    It'd be great to add an optimization for CRITICAL_SECTION2, but I think that will be a lot more complicated.

  11. yilei commented on Feb 18, 2026

    @yilei
    yilei
    on Feb 18, 2026
    ContributorAuthor
    More actions

    Makes sense, thanks @colesbury!

  12. yilei
    changed the title [-]`collections.deque` is not thread safe during copy[/-] [+]`collections.deque` is not atomic during copy[/+] on Feb 18, 2026
  13. colesbury
    added a commit that references this issue on Feb 20, 2026

    gh-144809: Make deque copy atomic in free-threaded build (gh-144966)

    70da972
  14. miss-islington
    added a commit that references this issue on Feb 20, 2026

    pythongh-144809: Make deque copy atomic in free-threaded build (pytho…

    8d399d0
  15. colesbury
    added a commit that references this issue on Feb 20, 2026

    [3.14] gh-144809: Make deque copy atomic in free-threaded build (gh-1…

    07dbda5
  16. colesbury
    closed this as completedon Feb 20, 2026
  17. brijkapadia
    added a commit that references this issue on Feb 28, 2026

    pythongh-144809: Make deque copy atomic in free-threaded build (pytho…

    82db0b0
  18. ljfp
    added a commit that references this issue on Apr 25, 2026

    pythongh-144809: Make deque copy atomic in free-threaded build (pytho…

    d5aff99
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirC modules in the Modules dirtopic-free-threadingtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.