2
2
import warnings
3
3
from functools import wraps
4
4
from threading import Thread , current_thread
5
+ from concurrent .futures import ThreadPoolExecutor , Future
5
6
6
7
import sentry_sdk
7
8
from sentry_sdk .integrations import Integration
24
25
from sentry_sdk ._types import ExcInfo
25
26
26
27
F = TypeVar ("F" , bound = Callable [..., Any ])
28
+ T = TypeVar ("T" , bound = Any )
27
29
28
30
29
31
class ThreadingIntegration (Integration ):
@@ -59,6 +61,15 @@ def setup_once():
59
61
django_version = None
60
62
channels_version = None
61
63
64
+ is_async_emulated_with_threads = (
65
+ sys .version_info < (3 , 9 )
66
+ and channels_version is not None
67
+ and channels_version < "4.0.0"
68
+ and django_version is not None
69
+ and django_version >= (3 , 0 )
70
+ and django_version < (4 , 0 )
71
+ )
72
+
62
73
@wraps (old_start )
63
74
def sentry_start (self , * a , ** kw ):
64
75
# type: (Thread, *Any, **Any) -> Any
@@ -67,14 +78,7 @@ def sentry_start(self, *a, **kw):
67
78
return old_start (self , * a , ** kw )
68
79
69
80
if integration .propagate_scope :
70
- if (
71
- sys .version_info < (3 , 9 )
72
- and channels_version is not None
73
- and channels_version < "4.0.0"
74
- and django_version is not None
75
- and django_version >= (3 , 0 )
76
- and django_version < (4 , 0 )
77
- ):
81
+ if is_async_emulated_with_threads :
78
82
warnings .warn (
79
83
"There is a known issue with Django channels 2.x and 3.x when using Python 3.8 or older. "
80
84
"(Async support is emulated using threads and some Sentry data may be leaked between those threads.) "
@@ -109,6 +113,9 @@ def sentry_start(self, *a, **kw):
109
113
return old_start (self , * a , ** kw )
110
114
111
115
Thread .start = sentry_start # type: ignore
116
+ ThreadPoolExecutor .submit = _wrap_threadpool_executor_submit ( # type: ignore
117
+ ThreadPoolExecutor .submit , is_async_emulated_with_threads
118
+ )
112
119
113
120
114
121
def _wrap_run (isolation_scope_to_use , current_scope_to_use , old_run_func ):
@@ -134,6 +141,43 @@ def _run_old_run_func():
134
141
return run # type: ignore
135
142
136
143
144
+ def _wrap_threadpool_executor_submit (func , is_async_emulated_with_threads ):
145
+ # type: (Callable[..., Future[T]], bool) -> Callable[..., Future[T]]
146
+ """
147
+ Wrap submit call to propagate scopes on task submission.
148
+ """
149
+
150
+ @wraps (func )
151
+ def sentry_submit (self , fn , * args , ** kwargs ):
152
+ # type: (ThreadPoolExecutor, Callable[..., T], *Any, **Any) -> Future[T]
153
+ integration = sentry_sdk .get_client ().get_integration (ThreadingIntegration )
154
+ if integration is None :
155
+ return func (self , fn , * args , ** kwargs )
156
+
157
+ if integration .propagate_scope and is_async_emulated_with_threads :
158
+ isolation_scope = sentry_sdk .get_isolation_scope ()
159
+ current_scope = sentry_sdk .get_current_scope ()
160
+ elif integration .propagate_scope :
161
+ isolation_scope = sentry_sdk .get_isolation_scope ().fork ()
162
+ current_scope = sentry_sdk .get_current_scope ().fork ()
163
+ else :
164
+ isolation_scope = None
165
+ current_scope = None
166
+
167
+ def wrapped_fn (* args , ** kwargs ):
168
+ # type: (*Any, **Any) -> Any
169
+ if isolation_scope is not None and current_scope is not None :
170
+ with use_isolation_scope (isolation_scope ):
171
+ with use_scope (current_scope ):
172
+ return fn (* args , ** kwargs )
173
+
174
+ return fn (* args , ** kwargs )
175
+
176
+ return func (self , wrapped_fn , * args , ** kwargs )
177
+
178
+ return sentry_submit
179
+
180
+
137
181
def _capture_exception ():
138
182
# type: () -> ExcInfo
139
183
exc_info = sys .exc_info ()
0 commit comments