-
Notifications
You must be signed in to change notification settings - Fork 583
Open
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 :
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?
Reactions are currently unavailable
Metadata
Metadata
Assignees
Projects
Status
No status