Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
- `span.containing_transaction` has been removed. Use `span.root_span` instead.
- `continue_from_headers`, `continue_from_environ` and `from_traceparent` have been removed, please use top-level API `sentry_sdk.continue_trace` instead.
- `PropagationContext` constructor no longer takes a `dynamic_sampling_context` but takes a `baggage` object instead.
- `ThreadingIntegration` no longer takes the `propagate_hub` argument.
- `ThreadingIntegration` no longer takes the `propagate_hub` and `propagate_scope` arguments. Scope data will be propagated by default.
- `Baggage.populate_from_transaction` has been removed.
- `debug.configure_debug_hub` was removed.
- `profiles_sample_rate` and `profiler_mode` were removed from options available via `_experiments`. Use the top-level `profiles_sample_rate` and `profiler_mode` options instead.
Expand Down
48 changes: 20 additions & 28 deletions sentry_sdk/integrations/threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@
class ThreadingIntegration(Integration):
identifier = "threading"

def __init__(self, propagate_scope=True):
# type: (bool) -> None
self.propagate_scope = propagate_scope

@staticmethod
def setup_once():
# type: () -> None
Expand All @@ -52,31 +48,27 @@ def sentry_start(self, *a, **kw):
if integration is None:
return old_start(self, *a, **kw)

if integration.propagate_scope:
if (
sys.version_info < (3, 9)
and channels_version is not None
and channels_version < "4.0.0"
and django_version is not None
and django_version >= (3, 0)
and django_version < (4, 0)
):
warnings.warn(
"There is a known issue with Django channels 2.x and 3.x when using Python 3.8 or older. "
"(Async support is emulated using threads and some Sentry data may be leaked between those threads.) "
"Please either upgrade to Django channels 4.0+, use Django's async features "
"available in Django 3.1+ instead of Django channels, or upgrade to Python 3.9+.",
stacklevel=2,
)
isolation_scope = sentry_sdk.get_isolation_scope()
current_scope = sentry_sdk.get_current_scope()

else:
isolation_scope = sentry_sdk.get_isolation_scope().fork()
current_scope = sentry_sdk.get_current_scope().fork()
if (
sys.version_info < (3, 9)
and channels_version is not None
and channels_version < "4.0.0"
and django_version is not None
and django_version >= (3, 0)
and django_version < (4, 0)
):
warnings.warn(
"There is a known issue with Django channels 2.x and 3.x when using Python 3.8 or older. "
"(Async support is emulated using threads and some Sentry data may be leaked between those threads.) "
"Please either upgrade to Django channels 4.0+, use Django's async features "
"available in Django 3.1+ instead of Django channels, or upgrade to Python 3.9+.",
stacklevel=2,
)
isolation_scope = sentry_sdk.get_isolation_scope()
current_scope = sentry_sdk.get_current_scope()

else:
isolation_scope = None
current_scope = None
isolation_scope = sentry_sdk.get_isolation_scope().fork()
current_scope = sentry_sdk.get_current_scope().fork()

# Patching instance methods in `start()` creates a reference cycle if
# done in a naive way. See
Expand Down
102 changes: 35 additions & 67 deletions tests/integrations/threading/test_threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,10 @@ def crash():
assert not events


@pytest.mark.parametrize("propagate_scope", (True, False))
def test_propagates_scope(sentry_init, capture_events, propagate_scope):
def test_propagates_scope(sentry_init, capture_events):
sentry_init(
default_integrations=False,
integrations=[ThreadingIntegration(propagate_scope=propagate_scope)],
integrations=[ThreadingIntegration()],
)
events = capture_events()

Expand All @@ -66,17 +65,13 @@ def stage2():
assert exception["mechanism"]["type"] == "threading"
assert not exception["mechanism"]["handled"]

if propagate_scope:
assert event["tags"]["stage1"] == "true"
else:
assert "stage1" not in event.get("tags", {})
assert event["tags"]["stage1"] == "true"


@pytest.mark.parametrize("propagate_scope", (True, False))
def test_propagates_threadpool_scope(sentry_init, capture_events, propagate_scope):
def test_propagates_threadpool_scope(sentry_init, capture_events):
sentry_init(
traces_sample_rate=1.0,
integrations=[ThreadingIntegration(propagate_scope=propagate_scope)],
integrations=[ThreadingIntegration()],
)
events = capture_events()

Expand All @@ -92,16 +87,12 @@ def double(number):

sentry_sdk.flush()

if propagate_scope:
assert len(events) == 1
(event,) = events
assert event["spans"][0]["trace_id"] == event["spans"][1]["trace_id"]
assert event["spans"][1]["trace_id"] == event["spans"][2]["trace_id"]
assert event["spans"][2]["trace_id"] == event["spans"][3]["trace_id"]
assert event["spans"][3]["trace_id"] == event["spans"][0]["trace_id"]
else:
(event,) = events
assert len(event["spans"]) == 0
assert len(events) == 1
(event,) = events
assert event["spans"][0]["trace_id"] == event["spans"][1]["trace_id"]
assert event["spans"][1]["trace_id"] == event["spans"][2]["trace_id"]
assert event["spans"][2]["trace_id"] == event["spans"][3]["trace_id"]
assert event["spans"][3]["trace_id"] == event["spans"][0]["trace_id"]


def test_circular_references(sentry_init, request):
Expand Down Expand Up @@ -174,27 +165,19 @@ def target():
assert t.run.__qualname__ == original_run.__qualname__


@pytest.mark.parametrize(
"propagate_scope",
(True, False),
ids=["propagate_scope=True", "propagate_scope=False"],
)
def test_scope_data_not_leaked_in_threads(sentry_init, propagate_scope):
def test_scope_data_not_leaked_in_threads(sentry_init):
sentry_init(
integrations=[ThreadingIntegration(propagate_scope=propagate_scope)],
integrations=[ThreadingIntegration()],
)

sentry_sdk.set_tag("initial_tag", "initial_value")
initial_iso_scope = sentry_sdk.get_isolation_scope()

def do_some_work():
# check if we have the initial scope data propagated into the thread
if propagate_scope:
assert sentry_sdk.get_isolation_scope()._tags == {
"initial_tag": "initial_value"
}
else:
assert sentry_sdk.get_isolation_scope()._tags == {}
assert sentry_sdk.get_isolation_scope()._tags == {
"initial_tag": "initial_value"
}

# change data in isolation scope in thread
sentry_sdk.set_tag("thread_tag", "thread_value")
Expand All @@ -209,17 +192,14 @@ def do_some_work():
}, "The isolation scope in the main thread should not be modified by the started thread."


@pytest.mark.parametrize(
"propagate_scope",
(True, False),
ids=["propagate_scope=True", "propagate_scope=False"],
)
def test_spans_from_multiple_threads(
sentry_init, capture_events, render_span_tree, propagate_scope
sentry_init,
capture_events,
render_span_tree,
):
sentry_init(
traces_sample_rate=1.0,
integrations=[ThreadingIntegration(propagate_scope=propagate_scope)],
integrations=[ThreadingIntegration()],
)
events = capture_events()

Expand All @@ -244,31 +224,19 @@ def do_some_work(number):
t.join()

(event,) = events
if propagate_scope:
assert render_span_tree(event) == dedent(
"""\
- op="outer-trx": description=null
- op="outer-submit-0": description="Thread: main"
- op="inner-run-0": description="Thread: child-0"
- op="outer-submit-1": description="Thread: main"
- op="inner-run-1": description="Thread: child-1"
- op="outer-submit-2": description="Thread: main"
- op="inner-run-2": description="Thread: child-2"
- op="outer-submit-3": description="Thread: main"
- op="inner-run-3": description="Thread: child-3"
- op="outer-submit-4": description="Thread: main"
- op="inner-run-4": description="Thread: child-4"\
"""
)

elif not propagate_scope:
assert render_span_tree(event) == dedent(
"""\
- op="outer-trx": description=null
- op="outer-submit-0": description="Thread: main"
- op="outer-submit-1": description="Thread: main"
- op="outer-submit-2": description="Thread: main"
- op="outer-submit-3": description="Thread: main"
- op="outer-submit-4": description="Thread: main"\

assert render_span_tree(event) == dedent(
"""\
- op="outer-trx": description=null
- op="outer-submit-0": description="Thread: main"
- op="inner-run-0": description="Thread: child-0"
- op="outer-submit-1": description="Thread: main"
- op="inner-run-1": description="Thread: child-1"
- op="outer-submit-2": description="Thread: main"
- op="inner-run-2": description="Thread: child-2"
- op="outer-submit-3": description="Thread: main"
- op="inner-run-3": description="Thread: child-3"
- op="outer-submit-4": description="Thread: main"
- op="inner-run-4": description="Thread: child-4"\
"""
)
)
Loading