getsentry / getsentry/sentry-python

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

Aperta
#5,465 3 commenti 0 reazioni 1 assegnatario Rivendicata da @sl0thentr0py Vedi su GitHub
Bug Errors Python
Lingua principale
Python
Stelle
2.2k
Fork
669
Merge medio
1g 1h
PR unite (30g)
213

Descrizione

### 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?

Guida per i contributori

Apri la guida per i contributori

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.