Skip to content

Commit e8f21d4

Browse files
authored
feat(taskworker): Add namespace parameter to taskworker client (#81860)
Now that taskbroker gRPC supports getting tasks by namespace, this PR enables the taskworker to use that parameter if provided. Depends on: getsentry/taskbroker#74
1 parent 3997d3f commit e8f21d4

File tree

7 files changed

+113
-23
lines changed

7 files changed

+113
-23
lines changed

requirements-dev-frozen.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ sentry-forked-django-stubs==5.1.1.post1
188188
sentry-forked-djangorestframework-stubs==3.15.1.post2
189189
sentry-kafka-schemas==0.1.122
190190
sentry-ophio==1.0.0
191-
sentry-protos==0.1.37
191+
sentry-protos==0.1.39
192192
sentry-redis-tools==0.1.7
193193
sentry-relay==0.9.3
194194
sentry-sdk==2.19.2

requirements-frozen.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ s3transfer==0.10.0
127127
sentry-arroyo==2.18.2
128128
sentry-kafka-schemas==0.1.122
129129
sentry-ophio==1.0.0
130-
sentry-protos==0.1.37
130+
sentry-protos==0.1.39
131131
sentry-redis-tools==0.1.7
132132
sentry-relay==0.9.3
133133
sentry-sdk==2.19.2

src/sentry/runner/commands/run.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,13 +243,18 @@ def worker(ignore_unknown_queues: bool, **options: Any) -> None:
243243
@click.option(
244244
"--max-task-count", help="Number of tasks this worker should run before exiting", default=10000
245245
)
246+
@click.option(
247+
"--namespace", help="The dedicated task namespace that taskworker operates on", default=None
248+
)
246249
@log_options()
247250
@configuration
248-
def taskworker(rpc_host: str, max_task_count: int, **options: Any) -> None:
251+
def taskworker(rpc_host: str, max_task_count: int, namespace: str | None, **options: Any) -> None:
249252
from sentry.taskworker.worker import TaskWorker
250253

251254
with managed_bgtasks(role="taskworker"):
252-
worker = TaskWorker(rpc_host=rpc_host, max_task_count=max_task_count, **options)
255+
worker = TaskWorker(
256+
rpc_host=rpc_host, max_task_count=max_task_count, namespace=namespace, **options
257+
)
253258
exitcode = worker.start()
254259
raise SystemExit(exitcode)
255260

src/sentry/taskworker/client.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import grpc
44
from sentry_protos.sentry.v1.taskworker_pb2 import (
5+
FetchNextTask,
56
GetTaskRequest,
67
SetTaskStatusRequest,
78
TaskActivation,
@@ -24,13 +25,14 @@ def __init__(self, host: str) -> None:
2425
self._channel = grpc.insecure_channel(self._host)
2526
self._stub = ConsumerServiceStub(self._channel)
2627

27-
def get_task(self) -> TaskActivation | None:
28+
def get_task(self, namespace: str | None = None) -> TaskActivation | None:
2829
"""
29-
Fetch a pending task
30+
Fetch a pending task.
3031
31-
Will return None when there are no tasks to fetch
32+
If a namespace is provided, only tasks for that namespace will be fetched.
33+
This will return None if there are no tasks to fetch.
3234
"""
33-
request = GetTaskRequest()
35+
request = GetTaskRequest(namespace=namespace)
3436
try:
3537
response = self._stub.GetTask(request)
3638
except grpc.RpcError as err:
@@ -42,7 +44,7 @@ def get_task(self) -> TaskActivation | None:
4244
return None
4345

4446
def update_task(
45-
self, task_id: str, status: TaskActivationStatus.ValueType, fetch_next: bool = True
47+
self, task_id: str, status: TaskActivationStatus.ValueType, fetch_next_task: FetchNextTask
4648
) -> TaskActivation | None:
4749
"""
4850
Update the status for a given task activation.
@@ -52,7 +54,7 @@ def update_task(
5254
request = SetTaskStatusRequest(
5355
id=task_id,
5456
status=status,
55-
fetch_next=fetch_next,
57+
fetch_next_task=fetch_next_task,
5658
)
5759
try:
5860
response = self._stub.SetTaskStatus(request)

src/sentry/taskworker/worker.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
TASK_ACTIVATION_STATUS_COMPLETE,
1818
TASK_ACTIVATION_STATUS_FAILURE,
1919
TASK_ACTIVATION_STATUS_RETRY,
20+
FetchNextTask,
2021
TaskActivation,
2122
)
2223

@@ -67,12 +68,17 @@ class TaskWorker:
6768
"""
6869

6970
def __init__(
70-
self, rpc_host: str, max_task_count: int | None = None, **options: dict[str, Any]
71+
self,
72+
rpc_host: str,
73+
max_task_count: int | None = None,
74+
namespace: str | None = None,
75+
**options: dict[str, Any],
7176
) -> None:
7277
self.options = options
7378
self._execution_count = 0
7479
self._worker_id = uuid4().hex
7580
self._max_task_count = max_task_count
81+
self._namespace = namespace
7682
self.client = TaskworkerClient(rpc_host)
7783
self._pool: Pool | None = None
7884
self._build_pool()
@@ -134,7 +140,7 @@ def start(self) -> int:
134140

135141
def fetch_task(self) -> TaskActivation | None:
136142
try:
137-
activation = self.client.get_task()
143+
activation = self.client.get_task(self._namespace)
138144
except grpc.RpcError:
139145
metrics.incr("taskworker.worker.get_task.failed")
140146
logger.info("get_task failed. Retrying in 1 second")
@@ -177,6 +183,7 @@ def process_task(self, activation: TaskActivation) -> TaskActivation | None:
177183
return self.client.update_task(
178184
task_id=activation.id,
179185
status=TASK_ACTIVATION_STATUS_FAILURE,
186+
fetch_next_task=FetchNextTask(namespace=self._namespace),
180187
)
181188

182189
if task.at_most_once:
@@ -264,4 +271,5 @@ def process_task(self, activation: TaskActivation) -> TaskActivation | None:
264271
return self.client.update_task(
265272
task_id=activation.id,
266273
status=next_state,
274+
fetch_next_task=FetchNextTask(namespace=self._namespace),
267275
)

tests/sentry/taskworker/test_client.py

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from google.protobuf.message import Message
99
from sentry_protos.sentry.v1.taskworker_pb2 import (
1010
TASK_ACTIVATION_STATUS_RETRY,
11+
FetchNextTask,
1112
GetTaskResponse,
1213
SetTaskStatusResponse,
1314
TaskActivation,
@@ -97,6 +98,31 @@ def test_get_task_ok():
9798
assert result.namespace == "testing"
9899

99100

101+
def test_get_task_with_namespace():
102+
channel = MockChannel()
103+
channel.add_response(
104+
"/sentry_protos.sentry.v1.ConsumerService/GetTask",
105+
GetTaskResponse(
106+
task=TaskActivation(
107+
id="abc123",
108+
namespace="testing",
109+
taskname="do_thing",
110+
parameters="",
111+
headers={},
112+
processing_deadline_duration=10,
113+
)
114+
),
115+
)
116+
with patch("sentry.taskworker.client.grpc.insecure_channel") as mock_channel:
117+
mock_channel.return_value = channel
118+
client = TaskworkerClient("localhost:50051")
119+
result = client.get_task(namespace="testing")
120+
121+
assert result
122+
assert result.id
123+
assert result.namespace == "testing"
124+
125+
100126
def test_get_task_not_found():
101127
channel = MockChannel()
102128
channel.add_response(
@@ -142,11 +168,39 @@ def test_update_task_ok_with_next():
142168
with patch("sentry.taskworker.client.grpc.insecure_channel") as mock_channel:
143169
mock_channel.return_value = channel
144170
client = TaskworkerClient("localhost:50051")
145-
result = client.update_task("abc123", TASK_ACTIVATION_STATUS_RETRY)
171+
result = client.update_task(
172+
"abc123", TASK_ACTIVATION_STATUS_RETRY, FetchNextTask(namespace=None)
173+
)
146174
assert result
147175
assert result.id == "abc123"
148176

149177

178+
def test_update_task_ok_with_next_namespace():
179+
channel = MockChannel()
180+
channel.add_response(
181+
"/sentry_protos.sentry.v1.ConsumerService/SetTaskStatus",
182+
SetTaskStatusResponse(
183+
task=TaskActivation(
184+
id="abc123",
185+
namespace="testing",
186+
taskname="do_thing",
187+
parameters="",
188+
headers={},
189+
processing_deadline_duration=10,
190+
)
191+
),
192+
)
193+
with patch("sentry.taskworker.client.grpc.insecure_channel") as mock_channel:
194+
mock_channel.return_value = channel
195+
client = TaskworkerClient("localhost:50051")
196+
result = client.update_task(
197+
"abc123", TASK_ACTIVATION_STATUS_RETRY, FetchNextTask(namespace="testing")
198+
)
199+
assert result
200+
assert result.id == "abc123"
201+
assert result.namespace == "testing"
202+
203+
150204
def test_update_task_ok_no_next():
151205
channel = MockChannel()
152206
channel.add_response(
@@ -155,7 +209,9 @@ def test_update_task_ok_no_next():
155209
with patch("sentry.taskworker.client.grpc.insecure_channel") as mock_channel:
156210
mock_channel.return_value = channel
157211
client = TaskworkerClient("localhost:50051")
158-
result = client.update_task("abc123", TASK_ACTIVATION_STATUS_RETRY)
212+
result = client.update_task(
213+
"abc123", TASK_ACTIVATION_STATUS_RETRY, FetchNextTask(namespace=None)
214+
)
159215
assert result is None
160216

161217

@@ -168,5 +224,7 @@ def test_update_task_not_found():
168224
with patch("sentry.taskworker.client.grpc.insecure_channel") as mock_channel:
169225
mock_channel.return_value = channel
170226
client = TaskworkerClient("localhost:50051")
171-
result = client.update_task("abc123", TASK_ACTIVATION_STATUS_RETRY)
227+
result = client.update_task(
228+
"abc123", TASK_ACTIVATION_STATUS_RETRY, FetchNextTask(namespace=None)
229+
)
172230
assert result is None

tests/sentry/taskworker/test_worker.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
TASK_ACTIVATION_STATUS_COMPLETE,
66
TASK_ACTIVATION_STATUS_FAILURE,
77
TASK_ACTIVATION_STATUS_RETRY,
8+
FetchNextTask,
89
TaskActivation,
910
)
1011

@@ -110,7 +111,9 @@ def test_process_task_complete(self) -> None:
110111
result = taskworker.process_task(SIMPLE_TASK)
111112

112113
mock_update.assert_called_with(
113-
task_id=SIMPLE_TASK.id, status=TASK_ACTIVATION_STATUS_COMPLETE
114+
task_id=SIMPLE_TASK.id,
115+
status=TASK_ACTIVATION_STATUS_COMPLETE,
116+
fetch_next_task=FetchNextTask(namespace=None),
114117
)
115118

116119
assert result
@@ -123,7 +126,9 @@ def test_process_task_retry(self) -> None:
123126
result = taskworker.process_task(RETRY_TASK)
124127

125128
mock_update.assert_called_with(
126-
task_id=RETRY_TASK.id, status=TASK_ACTIVATION_STATUS_RETRY
129+
task_id=RETRY_TASK.id,
130+
status=TASK_ACTIVATION_STATUS_RETRY,
131+
fetch_next_task=FetchNextTask(namespace=None),
127132
)
128133

129134
assert result
@@ -136,7 +141,9 @@ def test_process_task_failure(self) -> None:
136141
result = taskworker.process_task(FAIL_TASK)
137142

138143
mock_update.assert_called_with(
139-
task_id=FAIL_TASK.id, status=TASK_ACTIVATION_STATUS_FAILURE
144+
task_id=FAIL_TASK.id,
145+
status=TASK_ACTIVATION_STATUS_FAILURE,
146+
fetch_next_task=FetchNextTask(namespace=None),
140147
)
141148
assert result
142149
assert result.id == SIMPLE_TASK.id
@@ -148,7 +155,9 @@ def test_process_task_at_most_once(self) -> None:
148155
result = taskworker.process_task(AT_MOST_ONCE_TASK)
149156

150157
mock_update.assert_called_with(
151-
task_id=AT_MOST_ONCE_TASK.id, status=TASK_ACTIVATION_STATUS_COMPLETE
158+
task_id=AT_MOST_ONCE_TASK.id,
159+
status=TASK_ACTIVATION_STATUS_COMPLETE,
160+
fetch_next_task=FetchNextTask(namespace=None),
152161
)
153162
assert taskworker.process_task(AT_MOST_ONCE_TASK) is None
154163
assert result
@@ -169,7 +178,9 @@ def test_start_max_task_count(self) -> None:
169178
assert result == 0
170179
assert mock_client.get_task.called
171180
mock_client.update_task.assert_called_with(
172-
task_id=SIMPLE_TASK.id, status=TASK_ACTIVATION_STATUS_COMPLETE
181+
task_id=SIMPLE_TASK.id,
182+
status=TASK_ACTIVATION_STATUS_COMPLETE,
183+
fetch_next_task=FetchNextTask(namespace=None),
173184
)
174185

175186
def test_start_loop(self) -> None:
@@ -188,10 +199,14 @@ def test_start_loop(self) -> None:
188199
assert mock_client.update_task.call_count == 2
189200

190201
mock_client.update_task.assert_any_call(
191-
task_id=SIMPLE_TASK.id, status=TASK_ACTIVATION_STATUS_COMPLETE
202+
task_id=SIMPLE_TASK.id,
203+
status=TASK_ACTIVATION_STATUS_COMPLETE,
204+
fetch_next_task=FetchNextTask(namespace=None),
192205
)
193206
mock_client.update_task.assert_any_call(
194-
task_id=RETRY_TASK.id, status=TASK_ACTIVATION_STATUS_RETRY
207+
task_id=RETRY_TASK.id,
208+
status=TASK_ACTIVATION_STATUS_RETRY,
209+
fetch_next_task=FetchNextTask(namespace=None),
195210
)
196211

197212
def test_start_keyboard_interrupt(self) -> None:
@@ -210,5 +225,7 @@ def test_start_unknown_task(self) -> None:
210225
result = taskworker.start()
211226
assert result == 0, "Exit zero, all tasks complete"
212227
mock_client.update_task.assert_any_call(
213-
task_id=UNDEFINED_TASK.id, status=TASK_ACTIVATION_STATUS_FAILURE
228+
task_id=UNDEFINED_TASK.id,
229+
status=TASK_ACTIVATION_STATUS_FAILURE,
230+
fetch_next_task=FetchNextTask(namespace=None),
214231
)

0 commit comments

Comments
 (0)