Skip to content

Commit 076ee82

Browse files
markstoryandrewshie-sentry
authored andcommitted
feat(taskworker) Add support for headers to Task.apply_async() (#88689)
We use task headers in a few places and supporting it is simple. Refs #88462
1 parent 20d5e42 commit 076ee82

File tree

3 files changed

+52
-30
lines changed

3 files changed

+52
-30
lines changed

src/sentry/taskworker/task.py

+23-17
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
import datetime
4-
from collections.abc import Callable
4+
from collections.abc import Callable, Collection, Mapping
55
from functools import update_wrapper
66
from typing import TYPE_CHECKING, Any, Generic, ParamSpec, TypeVar
77
from uuid import uuid4
@@ -81,30 +81,32 @@ def delay(self, *args: P.args, **kwargs: P.kwargs) -> None:
8181
The provided parameters will be JSON encoded and stored within
8282
a `TaskActivation` protobuf that is appended to kafka
8383
"""
84-
if settings.TASK_WORKER_ALWAYS_EAGER:
85-
self._func(*args, **kwargs)
86-
else:
87-
# TODO(taskworker) promote parameters to headers
88-
self._namespace.send_task(
89-
self.create_activation(*args, **kwargs), wait_for_delivery=self.wait_for_delivery
90-
)
84+
self.apply_async(args=args, kwargs=kwargs)
9185

92-
def apply_async(self, args: Any = None, kwargs: Any = None) -> None:
86+
def apply_async(
87+
self, args: Any = None, kwargs: Any = None, headers: Mapping[str, Any] | None = None
88+
) -> None:
9389
"""
9490
Schedule a task to run later with a set of arguments.
9591
9692
The provided parameters will be JSON encoded and stored within
9793
a `TaskActivation` protobuf that is appended to kafka
98-
99-
Prefer using `delay()` instead of `apply_async()`.
10094
"""
101-
if args is None:
102-
args = []
103-
if kwargs is None:
104-
kwargs = {}
105-
self.delay(*args, **kwargs)
95+
if settings.TASK_WORKER_ALWAYS_EAGER:
96+
self._func(*args, **kwargs)
97+
else:
98+
# TODO(taskworker) promote parameters to headers
99+
self._namespace.send_task(
100+
self.create_activation(args=args, kwargs=kwargs, headers=headers),
101+
wait_for_delivery=self.wait_for_delivery,
102+
)
106103

107-
def create_activation(self, *args: P.args, **kwargs: P.kwargs) -> TaskActivation:
104+
def create_activation(
105+
self,
106+
args: Collection[Any],
107+
kwargs: Mapping[Any, Any],
108+
headers: Mapping[str, Any] | None = None,
109+
) -> TaskActivation:
108110
received_at = Timestamp()
109111
received_at.FromDatetime(timezone.now())
110112

@@ -116,9 +118,13 @@ def create_activation(self, *args: P.args, **kwargs: P.kwargs) -> TaskActivation
116118
if isinstance(expires, datetime.timedelta):
117119
expires = int(expires.total_seconds())
118120

121+
if not headers:
122+
headers = {}
123+
119124
headers = {
120125
"sentry-trace": sentry_sdk.get_traceparent() or "",
121126
"baggage": sentry_sdk.get_baggage() or "",
127+
**headers,
122128
}
123129

124130
return TaskActivation(

tests/sentry/taskworker/test_registry.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,12 @@ def with_expires() -> None:
8181
raise NotImplementedError
8282

8383
no_expires_task = namespace.get("test.no_expires")
84-
activation = no_expires_task.create_activation()
84+
activation = no_expires_task.create_activation([], {})
8585
assert activation.expires == 10 * 60
8686
assert activation.processing_deadline_duration == 5
8787

8888
with_expires_task = namespace.get("test.with_expires")
89-
activation = with_expires_task.create_activation()
89+
activation = with_expires_task.create_activation([], {})
9090
assert activation.expires == 30 * 60
9191
assert activation.processing_deadline_duration == 10
9292

@@ -115,7 +115,7 @@ def test_namespace_send_task_no_retry() -> None:
115115
def simple_task() -> None:
116116
raise NotImplementedError
117117

118-
activation = simple_task.create_activation()
118+
activation = simple_task.create_activation([], {})
119119
assert activation.retry_state.attempts == 0
120120
assert activation.retry_state.max_attempts == 1
121121
assert activation.retry_state.on_attempts_exceeded == ON_ATTEMPTS_EXCEEDED_DISCARD
@@ -147,7 +147,7 @@ def test_namespace_send_task_with_retry() -> None:
147147
def simple_task() -> None:
148148
raise NotImplementedError
149149

150-
activation = simple_task.create_activation()
150+
activation = simple_task.create_activation([], {})
151151
assert activation.retry_state.attempts == 0
152152
assert activation.retry_state.max_attempts == 3
153153
assert activation.retry_state.on_attempts_exceeded == ON_ATTEMPTS_EXCEEDED_DEADLETTER
@@ -175,7 +175,7 @@ def test_namespace_with_retry_send_task() -> None:
175175
def simple_task() -> None:
176176
raise NotImplementedError
177177

178-
activation = simple_task.create_activation()
178+
activation = simple_task.create_activation([], {})
179179
assert activation.retry_state.attempts == 0
180180
assert activation.retry_state.max_attempts == 3
181181
assert activation.retry_state.on_attempts_exceeded == ON_ATTEMPTS_EXCEEDED_DEADLETTER
@@ -205,7 +205,7 @@ def test_namespace_with_wait_for_delivery_send_task() -> None:
205205
def simple_task() -> None:
206206
raise NotImplementedError
207207

208-
activation = simple_task.create_activation()
208+
activation = simple_task.create_activation([], {})
209209

210210
mock_producer = Mock()
211211
namespace._producers[Topic.TASKWORKER] = mock_producer

tests/sentry/taskworker/test_task.py

+23-7
Original file line numberDiff line numberDiff line change
@@ -133,33 +133,33 @@ def test_create_activation(task_namespace: TaskNamespace) -> None:
133133
at_most_once=True,
134134
)
135135
# No retries will be made as there is no retry policy on the task or namespace.
136-
activation = no_retry_task.create_activation()
136+
activation = no_retry_task.create_activation([], {})
137137
assert activation.taskname == "test.no_retry"
138138
assert activation.namespace == task_namespace.name
139139
assert activation.retry_state
140140
assert activation.retry_state.attempts == 0
141141
assert activation.retry_state.max_attempts == 1
142142
assert activation.retry_state.on_attempts_exceeded == ON_ATTEMPTS_EXCEEDED_DISCARD
143143

144-
activation = retry_task.create_activation()
144+
activation = retry_task.create_activation([], {})
145145
assert activation.taskname == "test.with_retry"
146146
assert activation.namespace == task_namespace.name
147147
assert activation.retry_state
148148
assert activation.retry_state.attempts == 0
149149
assert activation.retry_state.max_attempts == 3
150150
assert activation.retry_state.on_attempts_exceeded == ON_ATTEMPTS_EXCEEDED_DEADLETTER
151151

152-
activation = timedelta_expiry_task.create_activation()
152+
activation = timedelta_expiry_task.create_activation([], {})
153153
assert activation.taskname == "test.with_timedelta_expires"
154154
assert activation.expires == 300
155155
assert activation.processing_deadline_duration == 30
156156

157-
activation = int_expiry_task.create_activation()
157+
activation = int_expiry_task.create_activation([], {})
158158
assert activation.taskname == "test.with_int_expires"
159159
assert activation.expires == 300
160160
assert activation.processing_deadline_duration == 30
161161

162-
activation = at_most_once_task.create_activation()
162+
activation = at_most_once_task.create_activation([], {})
163163
assert activation.taskname == "test.at_most_once"
164164
assert activation.namespace == task_namespace.name
165165
assert activation.retry_state
@@ -174,7 +174,7 @@ def test_create_activation_parameters(task_namespace: TaskNamespace) -> None:
174174
def with_parameters(one: str, two: int, org_id: int) -> None:
175175
raise NotImplementedError
176176

177-
activation = with_parameters.create_activation("one", 22, org_id=99)
177+
activation = with_parameters.create_activation(["one", 22], {"org_id": 99})
178178
params = json.loads(activation.parameters)
179179
assert params["args"]
180180
assert params["args"] == ["one", 22]
@@ -187,8 +187,24 @@ def with_parameters(one: str, two: int, org_id: int) -> None:
187187
raise NotImplementedError
188188

189189
with sentry_sdk.start_transaction(op="test.task"):
190-
activation = with_parameters.create_activation("one", 22, org_id=99)
190+
activation = with_parameters.create_activation(["one", 22], {"org_id": 99})
191191

192192
headers = activation.headers
193193
assert headers["sentry-trace"]
194194
assert "baggage" in headers
195+
196+
197+
def test_create_activation_headers(task_namespace: TaskNamespace) -> None:
198+
@task_namespace.register(name="test.parameters")
199+
def with_parameters(one: str, two: int, org_id: int) -> None:
200+
raise NotImplementedError
201+
202+
with sentry_sdk.start_transaction(op="test.task"):
203+
activation = with_parameters.create_activation(
204+
["one", 22], {"org_id": 99}, {"key": "value"}
205+
)
206+
207+
headers = activation.headers
208+
assert headers["sentry-trace"]
209+
assert "baggage" in headers
210+
assert headers["key"] == "value"

0 commit comments

Comments
 (0)