|  | 
| 1 | 1 | import gc | 
| 2 | 2 | from concurrent import futures | 
|  | 3 | +from textwrap import dedent | 
| 3 | 4 | from threading import Thread | 
| 4 | 5 | 
 | 
| 5 | 6 | import pytest | 
| @@ -172,3 +173,103 @@ def target(): | 
| 172 | 173 |     assert Thread.run.__qualname__ == original_run.__qualname__ | 
| 173 | 174 |     assert t.run.__name__ == "run" | 
| 174 | 175 |     assert t.run.__qualname__ == original_run.__qualname__ | 
|  | 176 | + | 
|  | 177 | + | 
|  | 178 | +@pytest.mark.parametrize( | 
|  | 179 | +    "propagate_scope", | 
|  | 180 | +    (True, False), | 
|  | 181 | +    ids=["propagate_scope=True", "propagate_scope=False"], | 
|  | 182 | +) | 
|  | 183 | +def test_scope_data_not_leaked_in_threads(sentry_init, propagate_scope): | 
|  | 184 | +    sentry_init( | 
|  | 185 | +        integrations=[ThreadingIntegration(propagate_scope=propagate_scope)], | 
|  | 186 | +    ) | 
|  | 187 | + | 
|  | 188 | +    sentry_sdk.set_tag("initial_tag", "initial_value") | 
|  | 189 | +    initial_iso_scope = sentry_sdk.get_isolation_scope() | 
|  | 190 | + | 
|  | 191 | +    def do_some_work(): | 
|  | 192 | +        # check if we have the initial scope data propagated into the thread | 
|  | 193 | +        if propagate_scope: | 
|  | 194 | +            assert sentry_sdk.get_isolation_scope()._tags == { | 
|  | 195 | +                "initial_tag": "initial_value" | 
|  | 196 | +            } | 
|  | 197 | +        else: | 
|  | 198 | +            assert sentry_sdk.get_isolation_scope()._tags == {} | 
|  | 199 | + | 
|  | 200 | +        # change data in isolation scope in thread | 
|  | 201 | +        sentry_sdk.set_tag("thread_tag", "thread_value") | 
|  | 202 | + | 
|  | 203 | +    t = Thread(target=do_some_work) | 
|  | 204 | +    t.start() | 
|  | 205 | +    t.join() | 
|  | 206 | + | 
|  | 207 | +    # check if the initial scope data is not modified by the started thread | 
|  | 208 | +    assert initial_iso_scope._tags == { | 
|  | 209 | +        "initial_tag": "initial_value" | 
|  | 210 | +    }, "The isolation scope in the main thread should not be modified by the started thread." | 
|  | 211 | + | 
|  | 212 | + | 
|  | 213 | +@pytest.mark.parametrize( | 
|  | 214 | +    "propagate_scope", | 
|  | 215 | +    (True, False), | 
|  | 216 | +    ids=["propagate_scope=True", "propagate_scope=False"], | 
|  | 217 | +) | 
|  | 218 | +def test_spans_from_multiple_threads( | 
|  | 219 | +    sentry_init, capture_events, render_span_tree, propagate_scope | 
|  | 220 | +): | 
|  | 221 | +    sentry_init( | 
|  | 222 | +        traces_sample_rate=1.0, | 
|  | 223 | +        integrations=[ThreadingIntegration(propagate_scope=propagate_scope)], | 
|  | 224 | +    ) | 
|  | 225 | +    events = capture_events() | 
|  | 226 | + | 
|  | 227 | +    def do_some_work(number): | 
|  | 228 | +        with sentry_sdk.start_span( | 
|  | 229 | +            op=f"inner-run-{number}", name=f"Thread: child-{number}" | 
|  | 230 | +        ): | 
|  | 231 | +            pass | 
|  | 232 | + | 
|  | 233 | +    threads = [] | 
|  | 234 | + | 
|  | 235 | +    with sentry_sdk.start_transaction(op="outer-trx"): | 
|  | 236 | +        for number in range(5): | 
|  | 237 | +            with sentry_sdk.start_span( | 
|  | 238 | +                op=f"outer-submit-{number}", name="Thread: main" | 
|  | 239 | +            ): | 
|  | 240 | +                t = Thread(target=do_some_work, args=(number,)) | 
|  | 241 | +                t.start() | 
|  | 242 | +                threads.append(t) | 
|  | 243 | + | 
|  | 244 | +        for t in threads: | 
|  | 245 | +            t.join() | 
|  | 246 | + | 
|  | 247 | +    (event,) = events | 
|  | 248 | +    if propagate_scope: | 
|  | 249 | +        assert render_span_tree(event) == dedent( | 
|  | 250 | +            """\ | 
|  | 251 | +            - op="outer-trx": description=null | 
|  | 252 | +              - op="outer-submit-0": description="Thread: main" | 
|  | 253 | +                - op="inner-run-0": description="Thread: child-0" | 
|  | 254 | +              - op="outer-submit-1": description="Thread: main" | 
|  | 255 | +                - op="inner-run-1": description="Thread: child-1" | 
|  | 256 | +              - op="outer-submit-2": description="Thread: main" | 
|  | 257 | +                - op="inner-run-2": description="Thread: child-2" | 
|  | 258 | +              - op="outer-submit-3": description="Thread: main" | 
|  | 259 | +                - op="inner-run-3": description="Thread: child-3" | 
|  | 260 | +              - op="outer-submit-4": description="Thread: main" | 
|  | 261 | +                - op="inner-run-4": description="Thread: child-4"\ | 
|  | 262 | +""" | 
|  | 263 | +        ) | 
|  | 264 | + | 
|  | 265 | +    elif not propagate_scope: | 
|  | 266 | +        assert render_span_tree(event) == dedent( | 
|  | 267 | +            """\ | 
|  | 268 | +            - op="outer-trx": description=null | 
|  | 269 | +              - op="outer-submit-0": description="Thread: main" | 
|  | 270 | +              - op="outer-submit-1": description="Thread: main" | 
|  | 271 | +              - op="outer-submit-2": description="Thread: main" | 
|  | 272 | +              - op="outer-submit-3": description="Thread: main" | 
|  | 273 | +              - op="outer-submit-4": description="Thread: main"\ | 
|  | 274 | +""" | 
|  | 275 | +        ) | 
0 commit comments