Skip to content

Commit cb1e2fa

Browse files
committed
fix(celery): Push scope before signals run
1 parent 30eddcf commit cb1e2fa

File tree

2 files changed

+48
-23
lines changed

2 files changed

+48
-23
lines changed

sentry_sdk/integrations/celery.py

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ def setup_once():
2121
old_build_tracer = trace.build_tracer
2222

2323
def sentry_build_tracer(name, task, *args, **kwargs):
24-
task.__call__ = _wrap_task_call(task, task.__call__)
25-
task.run = _wrap_task_call(task, task.run)
26-
return old_build_tracer(name, task, *args, **kwargs)
24+
# Need to patch both methods because older celery sometimes
25+
# short-circuits to task.run if it thinks it's safe.
26+
task.__call__ = _wrap_task_call(task.__call__)
27+
task.run = _wrap_task_call(task.run)
28+
return _wrap_tracer(task, old_build_tracer(name, task, *args, **kwargs))
2729

2830
trace.build_tracer = sentry_build_tracer
2931

@@ -33,28 +35,40 @@ def sentry_build_tracer(name, task, *args, **kwargs):
3335
ignore_logger("celery.worker.job")
3436

3537

36-
def _wrap_task_call(self, f):
38+
def _wrap_tracer(task, f):
39+
# Need to wrap tracer for pushing the scope before prerun is sent, and
40+
# popping it after postrun is sent.
41+
#
42+
# This is the reason we don't use signals for hooking in the first place.
43+
# Also because in Celery 3, signal dispatch returns early if one handler
44+
# crashes.
3745
def _inner(*args, **kwargs):
3846
hub = Hub.current
3947
if hub.get_integration(CeleryIntegration) is None:
4048
return f(*args, **kwargs)
4149

42-
with hub.configure_scope() as scope:
43-
if scope._name == "celery":
44-
return f(*args, **kwargs)
45-
4650
with hub.push_scope() as scope:
4751
scope._name = "celery"
48-
scope.add_event_processor(_make_event_processor(args, kwargs, self))
49-
try:
50-
return f(*args, **kwargs)
51-
except Exception:
52-
reraise(*_capture_exception(hub))
52+
scope.add_event_processor(_make_event_processor(task, *args, **kwargs))
53+
54+
return f(*args, **kwargs)
55+
56+
return _inner
57+
58+
59+
def _wrap_task_call(f):
60+
# Need to wrap task call because the exception is caught before we get to
61+
# see it. Also celery's reported stacktrace is untrustworthy.
62+
def _inner(*args, **kwargs):
63+
try:
64+
return f(*args, **kwargs)
65+
except Exception:
66+
reraise(*_capture_exception())
5367

5468
return _inner
5569

5670

57-
def _make_event_processor(args, kwargs, task):
71+
def _make_event_processor(task, uuid, args, kwargs, request=None):
5872
def event_processor(event, hint):
5973
with capture_internal_exceptions():
6074
event["transaction"] = task.name
@@ -87,12 +101,15 @@ def event_processor(event, hint):
87101
return event_processor
88102

89103

90-
def _capture_exception(hub):
91-
exc_info = sys.exc_info()
92-
event, hint = event_from_exception(
93-
exc_info,
94-
client_options=hub.client.options,
95-
mechanism={"type": "celery", "handled": False},
96-
)
97-
hub.capture_event(event, hint=hint)
104+
def _capture_exception():
105+
hub = Hub.current
106+
if hub.get_integration(CeleryIntegration) is not None:
107+
exc_info = sys.exc_info()
108+
event, hint = event_from_exception(
109+
exc_info,
110+
client_options=hub.client.options,
111+
mechanism={"type": "celery", "handled": False},
112+
)
113+
hub.capture_event(event, hint=hint)
114+
98115
return exc_info

tests/integrations/celery/test_celery.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ def dummy_task(x, y):
7272
def test_broken_prerun(init_celery, connect_signal):
7373
from celery.signals import task_prerun
7474

75+
stack_lengths = []
76+
7577
def crash(*args, **kwargs):
78+
# scope should exist in prerun
79+
stack_lengths.append(len(Hub.current._stack))
7680
1 / 0
7781

7882
# Order here is important to reproduce the bug: In Celery 3, a crashing
@@ -85,7 +89,7 @@ def crash(*args, **kwargs):
8589

8690
@celery.task(name="dummy_task")
8791
def dummy_task(x, y):
88-
assert len(Hub.current._stack) == 2
92+
stack_lengths.append(len(Hub.current._stack))
8993
return x / y
9094

9195
try:
@@ -95,3 +99,7 @@ def dummy_task(x, y):
9599
raise
96100

97101
assert len(Hub.current._stack) == 1
102+
if VERSION < (4,):
103+
assert stack_lengths == [2]
104+
else:
105+
assert stack_lengths == [2, 2]

0 commit comments

Comments
 (0)