getsentry / getsentry/sentry-python

` RuntimeError`: deque mutated during iteration in `Scope.fork()` on Python 3.14 free-threaded

Open
#5,465 3 comments 0 reactions 1 assignee Claimed by @sl0thentr0py View on GitHub
Bug Errors Python
Dominant language
Python
Stars
2.2k
Forks
669
Avg merge
1d 1h
Merged PRs (30d)
213

Description

### How do you use Sentry?

Sentry Saas (sentry.io)

### Version

2.52.0

### Steps to Reproduce

Use `ThreadingIntegration(propagate_scope=True)` in the following example code :

```python
import sys
import threading

import sentry_sdk
from sentry_sdk.integrations.threading import ThreadingIntegration

def main():
print(f"Python {sys.version}")
print(f"sentry-sdk {sentry_sdk.VERSION}")
print(f"GIL enabled: {sys._is_gil_enabled()}")

sentry_sdk.init(
dsn="https://examplePublicKey@o0.ingest.sentry.io/0",
integrations=[ThreadingIntegration(propagate_scope=True)],
traces_sample_rate=0,
)

# Grab the main thread's isolation scope. The race requires multiple
# threads to operate on the same Scope object — one mutating
# _breadcrumbs, another copying it via fork().
shared_scope = sentry_sdk.get_isolation_scope()

stop = threading.Event()

def mutate_breadcrumbs():
"""Appends breadcrumbs to the shared scope's _breadcrumbs deque."""
i = 0
while not stop.is_set():
shared_scope.add_breadcrumb(
{"category": "test", "message": f"crumb-{i}", "level": "info"},
hint={},
)
i += 1

def fork_shared_scope():
"""Forks the shared scope, triggering copy.copy(deque)."""
while not stop.is_set():
shared_scope.fork()

print()

# Use a barrier so all threads start racing simultaneously.
num_workers = 4
barrier = threading.Barrier(num_workers + 1)

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

threads: list[threading.Thread] = []
for _ in range(2):
t = threading.Thread(
target=run_with_barrier, args=(mutate_breadcrumbs,), daemon=True
)
t.start()
threads.append(t)
for _ in range(2):
t = threading.Thread(
target=run_with_barrier, args=(fork_shared_scope,), daemon=True
)
t.start()
threads.append(t)

# Release all threads at once
barrier.wait()

try:
stop.wait(timeout=5)
except KeyboardInterrupt:
pass
finally:
stop.set()
for t in threads:
t.join(timeout=5)

if __name__ == "__main__":
main()
```

### Expected Result

The program should finish without errors in Python 3.14 free-threaded mode.

### Actual Result

```
❯ python sentry_free_threaded_repro.py
Python 3.14.3 free-threading build (main, Feb 13 2026, 18:56:10) [GCC 11.4.0]
sentry-sdk 2.52.0
GIL enabled: False

Exception in thread Thread-3 (run_with_barrier):
Exception in thread Thread-4 (run_with_barrier):
Traceback (most recent call last):
Traceback (most recent call last):
File "lib/python3.14t/threading.py", line 1082, in _bootstrap_inner
self._context.run(self.run)
~~~~~~~~~~~~~~~~~^^^^^^^^^^
File "lib/python3.14t/site-packages/sentry_sdk/integrations/threading.py", line 140, in run
return _run_old_run_func()
File "lib/python3.14t/site-packages/sentry_sdk/integrations/threading.py", line 135, in _run_old_run_func
reraise(*_capture_exception())
~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "lib/python3.14t/site-packages/sentry_sdk/utils.py", line 1784, in reraise
raise value
File "lib/python3.14t/site-packages/sentry_sdk/integrations/threading.py", line 133, in _run_old_run_func
return old_run_func(self, *a[1:], **kw)
File "lib/python3.14t/threading.py", line 1024, in run
self._target(*self._args, **self._kwargs)
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "sentry_free_threaded_repro.py", line 49, in run_with_barrier
fn()
~~^^
File "sentry_free_threaded_repro.py", line 39, in fork_shared_scope
shared_scope.fork()
~~~~~~~~~~~~~~~~~^^
File "lib/python3.14t/site-packages/sentry_sdk/scope.py", line 511, in fork
forked_scope = copy(self)
File "lib/python3.14t/copy.py", line 82, in copy
return copier(x)
File "lib/python3.14t/site-packages/sentry_sdk/scope.py", line 288, in __copy__
rv._breadcrumbs = copy(self._breadcrumbs)
~~~~^^^^^^^^^^^^^^^^^^^
File "lib/python3.14t/copy.py", line 82, in copy
return copier(x)
RuntimeError: deque mutated during iteration
File "lib/python3.14t/threading.py", line 1082, in _bootstrap_inner
self._context.run(self.run)
~~~~~~~~~~~~~~~~~^^^^^^^^^^
File "lib/python3.14t/site-packages/sentry_sdk/integrations/threading.py", line 140, in run
return _run_old_run_func()
File "lib/python3.14t/site-packages/sentry_sdk/integrations/threading.py", line 135, in _run_old_run_func
reraise(*_capture_exception())
~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "lib/python3.14t/site-packages/sentry_sdk/utils.py", line 1784, in reraise
raise value
File "lib/python3.14t/site-packages/sentry_sdk/integrations/threading.py", line 133, in _run_old_run_func
return old_run_func(self, *a[1:], **kw)
File "lib/python3.14t/threading.py", line 1024, in run
self._target(*self._args, **self._kwargs)
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "sentry_free_threaded_repro.py", line 49, in run_with_barrier
fn()
~~^^
File "sentry_free_threaded_repro.py", line 39, in fork_shared_scope
shared_scope.fork()
~~~~~~~~~~~~~~~~~^^
File "lib/python3.14t/site-packages/sentry_sdk/scope.py", line 511, in fork
forked_scope = copy(self)
File "lib/python3.14t/copy.py", line 82, in copy
return copier(x)
File "lib/python3.14t/site-packages/sentry_sdk/scope.py", line 288, in __copy__
rv._breadcrumbs = copy(self._breadcrumbs)
~~~~^^^^^^^^^^^^^^^^^^^
File "lib/python3.14t/copy.py", line 82, in copy
return copier(x)
RuntimeError: deque mutated during iteration
```

As a workaround, we should be able to use a lock to protect calls to Scope's mutating methods. But it would be ideal if we could make the Scope class thread safe. What do you think?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.