forked from getsentry/sentry-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy paththreading.py
More file actions
200 lines (162 loc) · 6.94 KB
/
Copy paththreading.py
File metadata and controls
200 lines (162 loc) · 6.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import sys
import warnings
from functools import wraps
from threading import Thread, current_thread
from concurrent.futures import ThreadPoolExecutor, Future
import sentry_sdk
from sentry_sdk.integrations import Integration
from sentry_sdk.scope import use_isolation_scope, use_scope
from sentry_sdk.utils import (
event_from_exception,
capture_internal_exceptions,
logger,
reraise,
)
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Any
from typing import TypeVar
from typing import Callable
from typing import Optional
from sentry_sdk._types import ExcInfo
F = TypeVar("F", bound=Callable[..., Any])
T = TypeVar("T", bound=Any)
class ThreadingIntegration(Integration):
identifier = "threading"
def __init__(
self, propagate_hub: "Optional[bool]" = None, propagate_scope: bool = True
) -> None:
if propagate_hub is not None:
logger.warning(
"Deprecated: propagate_hub is deprecated. This will be removed in the future."
)
# Note: propagate_hub did not have any effect on propagation of scope data
# scope data was always propagated no matter what the value of propagate_hub was
# This is why the default for propagate_scope is True
self.propagate_scope = propagate_scope
if propagate_hub is not None:
self.propagate_scope = propagate_hub
@staticmethod
def setup_once() -> None:
old_start = Thread.start
try:
from django import VERSION as django_version # noqa: N811
except ImportError:
django_version = None
try:
import channels # type: ignore[import-untyped]
channels_version = channels.__version__
except (ImportError, AttributeError):
channels_version = None
is_async_emulated_with_threads = (
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)
)
@wraps(old_start)
def sentry_start(self: "Thread", *a: "Any", **kw: "Any") -> "Any":
integration = sentry_sdk.get_client().get_integration(ThreadingIntegration)
if integration is None:
return old_start(self, *a, **kw)
if integration.propagate_scope:
if is_async_emulated_with_threads:
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()
else:
isolation_scope = None
current_scope = None
# Patching instance methods in `start()` creates a reference cycle if
# done in a naive way. See
# https://github.com/getsentry/sentry-python/pull/434
#
# In threading module, using current_thread API will access current thread instance
# without holding it to avoid a reference cycle in an easier way.
with capture_internal_exceptions():
new_run = _wrap_run(
isolation_scope,
current_scope,
getattr(self.run, "__func__", self.run),
)
self.run = new_run # type: ignore
return old_start(self, *a, **kw)
Thread.start = sentry_start # type: ignore
ThreadPoolExecutor.submit = _wrap_threadpool_executor_submit( # type: ignore
ThreadPoolExecutor.submit, is_async_emulated_with_threads
)
def _wrap_run(
isolation_scope_to_use: "Optional[sentry_sdk.Scope]",
current_scope_to_use: "Optional[sentry_sdk.Scope]",
old_run_func: "F",
) -> "F":
@wraps(old_run_func)
def run(*a: "Any", **kw: "Any") -> "Any":
def _run_old_run_func() -> "Any":
try:
self = current_thread()
return old_run_func(self, *a[1:], **kw)
except Exception:
reraise(*_capture_exception())
if isolation_scope_to_use is not None and current_scope_to_use is not None:
with use_isolation_scope(isolation_scope_to_use):
with use_scope(current_scope_to_use):
return _run_old_run_func()
else:
return _run_old_run_func()
return run # type: ignore
def _wrap_threadpool_executor_submit(
func: "Callable[..., Future[T]]", is_async_emulated_with_threads: bool
) -> "Callable[..., Future[T]]":
"""
Wrap submit call to propagate scopes on task submission.
"""
@wraps(func)
def sentry_submit(
self: "ThreadPoolExecutor",
fn: "Callable[..., T]",
*args: "Any",
**kwargs: "Any",
) -> "Future[T]":
integration = sentry_sdk.get_client().get_integration(ThreadingIntegration)
if integration is None:
return func(self, fn, *args, **kwargs)
if integration.propagate_scope and is_async_emulated_with_threads:
isolation_scope = sentry_sdk.get_isolation_scope()
current_scope = sentry_sdk.get_current_scope()
elif integration.propagate_scope:
isolation_scope = sentry_sdk.get_isolation_scope().fork()
current_scope = sentry_sdk.get_current_scope().fork()
else:
isolation_scope = None
current_scope = None
def wrapped_fn(*args: "Any", **kwargs: "Any") -> "Any":
if isolation_scope is not None and current_scope is not None:
with use_isolation_scope(isolation_scope):
with use_scope(current_scope):
return fn(*args, **kwargs)
return fn(*args, **kwargs)
return func(self, wrapped_fn, *args, **kwargs)
return sentry_submit
def _capture_exception() -> "ExcInfo":
exc_info = sys.exc_info()
client = sentry_sdk.get_client()
if client.get_integration(ThreadingIntegration) is not None:
event, hint = event_from_exception(
exc_info,
client_options=client.options,
mechanism={"type": "threading", "handled": False},
)
sentry_sdk.capture_event(event, hint=hint)
return exc_info