broccoli-workers 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- broccoli_workers-0.1.0/LICENSE +21 -0
- broccoli_workers-0.1.0/PKG-INFO +594 -0
- broccoli_workers-0.1.0/README.md +546 -0
- broccoli_workers-0.1.0/broccoli/__init__.py +0 -0
- broccoli_workers-0.1.0/broccoli/cli.py +637 -0
- broccoli_workers-0.1.0/broccoli/core/__init__.py +15 -0
- broccoli_workers-0.1.0/broccoli/core/chain/chain.py +45 -0
- broccoli_workers-0.1.0/broccoli/core/chain/chain_mixin.py +49 -0
- broccoli_workers-0.1.0/broccoli/core/chain/chain_queue.py +299 -0
- broccoli_workers-0.1.0/broccoli/core/chain/task_chain.py +201 -0
- broccoli_workers-0.1.0/broccoli/core/health.py +20 -0
- broccoli_workers-0.1.0/broccoli/core/redis_controller.py +35 -0
- broccoli_workers-0.1.0/broccoli/core/result.py +78 -0
- broccoli_workers-0.1.0/broccoli/core/task/task.py +58 -0
- broccoli_workers-0.1.0/broccoli/core/task/task_queue.py +300 -0
- broccoli_workers-0.1.0/broccoli/core/task/task_registry.py +47 -0
- broccoli_workers-0.1.0/broccoli/workers/__init__.py +15 -0
- broccoli_workers-0.1.0/broccoli/workers/async_worker.py +157 -0
- broccoli_workers-0.1.0/broccoli/workers/base_worker.py +339 -0
- broccoli_workers-0.1.0/broccoli/workers/chain_worker.py +94 -0
- broccoli_workers-0.1.0/broccoli/workers/hybrid_worker.py +269 -0
- broccoli_workers-0.1.0/broccoli/workers/threaded_worker.py +154 -0
- broccoli_workers-0.1.0/broccoli/workers/worker_pool.py +99 -0
- broccoli_workers-0.1.0/broccoli_workers.egg-info/PKG-INFO +594 -0
- broccoli_workers-0.1.0/broccoli_workers.egg-info/SOURCES.txt +29 -0
- broccoli_workers-0.1.0/broccoli_workers.egg-info/dependency_links.txt +1 -0
- broccoli_workers-0.1.0/broccoli_workers.egg-info/entry_points.txt +2 -0
- broccoli_workers-0.1.0/broccoli_workers.egg-info/requires.txt +9 -0
- broccoli_workers-0.1.0/broccoli_workers.egg-info/top_level.txt +1 -0
- broccoli_workers-0.1.0/pyproject.toml +106 -0
- broccoli_workers-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Broccoli Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: broccoli-workers
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Distributed task queue with priorities, dependencies, and chains, backed by Redis
|
|
5
|
+
Author-email: Success Oguntuyi <oguntuyisuccess@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Broccoli Contributors
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Classifier: Development Status :: 4 - Beta
|
|
29
|
+
Classifier: Intended Audience :: Developers
|
|
30
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
31
|
+
Classifier: Programming Language :: Python :: 3
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
35
|
+
Classifier: Operating System :: OS Independent
|
|
36
|
+
Requires-Python: >=3.11
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
License-File: LICENSE
|
|
39
|
+
Requires-Dist: redis>=4.0.0
|
|
40
|
+
Provides-Extra: dev
|
|
41
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
42
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
43
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
44
|
+
Requires-Dist: isort>=5.0; extra == "dev"
|
|
45
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
46
|
+
Requires-Dist: flake8; extra == "dev"
|
|
47
|
+
Dynamic: license-file
|
|
48
|
+
|
|
49
|
+
# Broccoli
|
|
50
|
+
|
|
51
|
+
A lightweight Redis-backed task queue with priority scheduling, dependency chaining, and multiple worker execution models.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Requirements
|
|
56
|
+
|
|
57
|
+
- Python 3.10+
|
|
58
|
+
- Redis 6+
|
|
59
|
+
- `redis-py`
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Quick start
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from broccoli.core.task.task import Task
|
|
67
|
+
from broccoli.core.task.task_queue import TaskQueue
|
|
68
|
+
from broccoli.workers.base_worker import BaseWorker
|
|
69
|
+
|
|
70
|
+
# Register a task handler
|
|
71
|
+
class MyWorker(BaseWorker):
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
worker = MyWorker(redis_url="redis://localhost:6379")
|
|
75
|
+
worker.registry.register("send_email", lambda payload: send(payload["to"]))
|
|
76
|
+
|
|
77
|
+
# Push a task
|
|
78
|
+
queue = TaskQueue(redis_url="redis://localhost:6379")
|
|
79
|
+
task = Task(task_type="send_email", payload={"to": "user@example.com"})
|
|
80
|
+
queue.push(task)
|
|
81
|
+
|
|
82
|
+
# Start processing (blocking)
|
|
83
|
+
worker.start()
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Worker types
|
|
89
|
+
|
|
90
|
+
Choose the worker that fits your workload.
|
|
91
|
+
|
|
92
|
+
### BaseWorker — simple, single-threaded
|
|
93
|
+
|
|
94
|
+
Processes one task at a time in a blocking loop. Good for low-volume workloads or when you want the simplest possible setup.
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from broccoli.workers.base_worker import BaseWorker
|
|
98
|
+
|
|
99
|
+
class VideoWorker(BaseWorker):
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
worker = VideoWorker(redis_url="redis://localhost:6379")
|
|
103
|
+
worker.registry.register("transcode", transcode_video)
|
|
104
|
+
worker.start()
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### ThreadedWorker — concurrent, CPU-friendly
|
|
108
|
+
|
|
109
|
+
Runs a configurable thread pool. Good for I/O-bound tasks or when you want multiple tasks processing in parallel without asyncio.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from broccoli.workers.threaded_worker import ThreadedWorker
|
|
113
|
+
|
|
114
|
+
worker = ThreadedWorker(redis_url="redis://localhost:6379", max_workers=8)
|
|
115
|
+
worker.registry.register("resize_image", resize)
|
|
116
|
+
worker.start()
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### AsyncWorker — high-concurrency async
|
|
120
|
+
|
|
121
|
+
Dispatches tasks as asyncio coroutines. Good for large numbers of I/O-bound tasks (HTTP calls, DB queries) where thread overhead would be significant.
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from broccoli.workers.async_worker import AsyncWorker
|
|
125
|
+
|
|
126
|
+
worker = AsyncWorker(redis_url="redis://localhost:6379", max_concurrent=50)
|
|
127
|
+
worker.registry.register("fetch_url", fetch)
|
|
128
|
+
worker.start()
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### HybridWorker — async dispatch + thread execution
|
|
132
|
+
|
|
133
|
+
Combines asyncio concurrency control with a ThreadPoolExecutor for the actual handler. The right choice when you want high throughput and your handlers are not async-native.
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from broccoli.workers.hybrid_worker import HybridWorker
|
|
137
|
+
|
|
138
|
+
worker = HybridWorker(
|
|
139
|
+
redis_url="redis://localhost:6379",
|
|
140
|
+
thread_workers=4,
|
|
141
|
+
async_tasks=20,
|
|
142
|
+
result_ttl=3600, # seconds before result expires in Redis
|
|
143
|
+
)
|
|
144
|
+
worker.registry.register("process_batch", process)
|
|
145
|
+
worker.start()
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### WorkerPool — run multiple workers
|
|
149
|
+
|
|
150
|
+
Spawns N workers of any type, each in its own daemon thread.
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from broccoli.workers.worker_pool import WorkerPool
|
|
154
|
+
from broccoli.workers.threaded_worker import ThreadedWorker
|
|
155
|
+
|
|
156
|
+
pool = WorkerPool(
|
|
157
|
+
worker_type=ThreadedWorker,
|
|
158
|
+
num_workers=4,
|
|
159
|
+
redis_url="redis://localhost:6379",
|
|
160
|
+
)
|
|
161
|
+
pool.start() # blocks until pool.stop() or SIGTERM
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Task options
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from broccoli.core.task.task import Task
|
|
170
|
+
|
|
171
|
+
task = Task(
|
|
172
|
+
task_type="my_task",
|
|
173
|
+
payload={"key": "value"},
|
|
174
|
+
max_retries=3, # default: 0 (no retry)
|
|
175
|
+
depends_on="<task_id>", # optional: block until this task completes
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Priority
|
|
180
|
+
|
|
181
|
+
Lower number = higher priority. Default is `0`.
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
queue.push(urgent_task, priority=0) # processed first
|
|
185
|
+
queue.push(normal_task, priority=1)
|
|
186
|
+
queue.push(low_task, priority=5)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Tasks with the same priority are processed in FIFO order.
|
|
190
|
+
|
|
191
|
+
### Dependencies
|
|
192
|
+
|
|
193
|
+
A task with `depends_on` will not run until its parent task completes.
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
step1 = Task(task_type="extract", payload={...})
|
|
197
|
+
step2 = Task(task_type="transform", payload={...}, depends_on=step1.task_id)
|
|
198
|
+
step3 = Task(task_type="load", payload={...}, depends_on=step2.task_id)
|
|
199
|
+
|
|
200
|
+
queue.push(step1)
|
|
201
|
+
queue.push(step2)
|
|
202
|
+
queue.push(step3)
|
|
203
|
+
# step2 runs only after step1 completes; step3 only after step2
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Lifecycle hooks
|
|
209
|
+
|
|
210
|
+
Register functions to run at specific points in a task's lifecycle. All registration methods return `self` for chaining.
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
worker = MyWorker(redis_url="redis://localhost:6379")
|
|
214
|
+
|
|
215
|
+
# Method-style registration
|
|
216
|
+
worker.add_completion_handler(lambda task, result: print(f"Done: {result}"))
|
|
217
|
+
worker.add_failure_handler(lambda task, err: alert(str(err)))
|
|
218
|
+
worker.add_pre_process_handler(lambda task: task.payload.get("enabled", True))
|
|
219
|
+
worker.add_post_process_handler(lambda task, success: metrics.record(success))
|
|
220
|
+
|
|
221
|
+
# Chained registration
|
|
222
|
+
worker \
|
|
223
|
+
.add_completion_handler(notify_slack) \
|
|
224
|
+
.add_failure_handler(notify_pagerduty)
|
|
225
|
+
|
|
226
|
+
# Decorator-style registration
|
|
227
|
+
@worker.on_complete
|
|
228
|
+
def on_done(task, result):
|
|
229
|
+
print(f"Task {task.task_id} finished with: {result}")
|
|
230
|
+
|
|
231
|
+
@worker.on_failure
|
|
232
|
+
def on_fail(task, error):
|
|
233
|
+
logger.error(f"Task {task.task_id} failed: {error}")
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Pre-process handlers can abort a task by returning `False`:
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
@worker.on_pre_process
|
|
240
|
+
def gate(task):
|
|
241
|
+
if task.payload.get("dry_run"):
|
|
242
|
+
return False # skip this task
|
|
243
|
+
return True
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Chain tasks
|
|
249
|
+
|
|
250
|
+
For multi-step pipelines, use `ChainWorker`. Each step in the chain carries a `__chain_id` in its payload; the final result is stored atomically when the last step completes.
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
from broccoli.workers.chain_worker import ChainWorker
|
|
254
|
+
|
|
255
|
+
worker = ChainWorker(redis_url="redis://localhost:6379")
|
|
256
|
+
worker.registry.register("step_a", do_a)
|
|
257
|
+
worker.registry.register("step_b", do_b)
|
|
258
|
+
worker.start()
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Crash recovery
|
|
264
|
+
|
|
265
|
+
If a worker process dies mid-task, the task remains in the `tasks:processing` sorted set indefinitely. Call `recover_stalled` on startup (or on a schedule) to re-enqueue any tasks that have been in-flight longer than expected:
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
queue = TaskQueue(redis_url="redis://localhost:6379")
|
|
269
|
+
recovered = queue.recover_stalled(timeout_seconds=3600)
|
|
270
|
+
print(f"Re-enqueued {recovered} stalled tasks")
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Logging
|
|
276
|
+
|
|
277
|
+
Broccoli uses the standard `logging` module under the `broccoli` namespace. Configure it in your application:
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
import logging
|
|
281
|
+
logging.basicConfig(level=logging.INFO)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
# Command-Line Interface (CLI)
|
|
285
|
+
|
|
286
|
+
Broccoli ships with a powerful CLI for starting workers, inspecting queues, managing failed tasks, and monitoring chains – all from the terminal.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Installation
|
|
291
|
+
|
|
292
|
+
Make the CLI executable and available on your `PATH`:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
chmod +x broccoli/cli.py
|
|
296
|
+
# Optionally symlink it:
|
|
297
|
+
ln -s $(pwd)/broccoli/cli.py /usr/local/bin/broccoli
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Or run it directly via Python:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
python -m broccoli.cli --help
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Global Options
|
|
309
|
+
|
|
310
|
+
| Flag | Description |
|
|
311
|
+
|------|-------------|
|
|
312
|
+
| `-v`, `--verbose` | Increase logging verbosity (`-v` for INFO, `-vv` for DEBUG). |
|
|
313
|
+
| `--help` | Show help for any command or subcommand. |
|
|
314
|
+
|
|
315
|
+
All subcommands also respect environment variables for common settings:
|
|
316
|
+
|
|
317
|
+
| Env Variable | Default | Used For |
|
|
318
|
+
|--------------|---------|----------|
|
|
319
|
+
| `BROCCOLI_REDIS_URL` | `redis://localhost:6379` | Redis connection string |
|
|
320
|
+
| `BROCCOLI_QUEUE_NAME` | `tasks:queue` | Regular task queue name |
|
|
321
|
+
| `BROCCOLI_CHAIN_QUEUE_NAME` | `chain_tasks:queue` | Chain task queue name |
|
|
322
|
+
| `BROCCOLI_TASK_PREFIX` | `task` | Redis key prefix for task hashes |
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Worker Management
|
|
327
|
+
|
|
328
|
+
### Start a single worker
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
# Threaded worker with 8 threads
|
|
332
|
+
broccoli worker start --type threaded --concurrency 8
|
|
333
|
+
|
|
334
|
+
# Async worker with 20 concurrent tasks
|
|
335
|
+
broccoli worker start --type async --concurrency 20
|
|
336
|
+
|
|
337
|
+
# Hybrid worker (threads for CPU + asyncio for I/O)
|
|
338
|
+
broccoli worker start --type hybrid --thread-workers 4 --async-tasks 10
|
|
339
|
+
|
|
340
|
+
# Chain worker (for task chains)
|
|
341
|
+
broccoli worker start --type chain
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Start a worker pool
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
# Pool of 4 threaded workers
|
|
348
|
+
broccoli worker start --type threaded --pool --num-workers 4
|
|
349
|
+
|
|
350
|
+
# Pool of 3 async workers
|
|
351
|
+
broccoli worker start --type async --pool --num-workers 3
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Recover stalled tasks on startup
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
# Re-enqueue any tasks that have been in-flight for > 3600 seconds
|
|
358
|
+
broccoli worker start --type threaded --recover-stalled 3600
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Advanced: custom queue names
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
broccoli worker start --type threaded \
|
|
365
|
+
--queue-name myapp:queue \
|
|
366
|
+
--chain-queue-name myapp:chain \
|
|
367
|
+
--task-prefix myapp
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## Queue Inspection
|
|
373
|
+
|
|
374
|
+
### View queue statistics
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
broccoli queue stats
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Output:
|
|
381
|
+
```
|
|
382
|
+
runnable: 12
|
|
383
|
+
processing: 3
|
|
384
|
+
dead_letter: 2
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
With JSON output:
|
|
388
|
+
```bash
|
|
389
|
+
broccoli queue stats --format json
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### List tasks by status
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
# Pending tasks (ready to run)
|
|
396
|
+
broccoli queue list --status pending --limit 10
|
|
397
|
+
|
|
398
|
+
# In-progress tasks
|
|
399
|
+
broccoli queue list --status in_progress
|
|
400
|
+
|
|
401
|
+
# All tasks (scan all task hashes – use with care on large queues)
|
|
402
|
+
broccoli queue list --status all --limit 20
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Get a specific task
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
broccoli queue get <task_id>
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Returns full task metadata (payload, result, error, retries, etc.).
|
|
412
|
+
|
|
413
|
+
### Show tasks waiting for a parent
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
# See which tasks are blocked on a particular task
|
|
417
|
+
broccoli queue waiting <parent_task_id>
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
This is invaluable for debugging dependency deadlocks.
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## Dead-Letter Management
|
|
425
|
+
|
|
426
|
+
When tasks exhaust all retries, they are moved to the dead‑letter set for manual inspection and retry.
|
|
427
|
+
|
|
428
|
+
### List dead-letter tasks
|
|
429
|
+
|
|
430
|
+
```bash
|
|
431
|
+
broccoli dead list
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Shows task IDs and the timestamp when they failed.
|
|
435
|
+
|
|
436
|
+
### Requeue a dead task
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
broccoli dead requeue <task_id>
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
This resets the retry count to `0` and pushes the task back to the runnable queue.
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## Chain Inspection
|
|
447
|
+
|
|
448
|
+
### Get chain status
|
|
449
|
+
|
|
450
|
+
```bash
|
|
451
|
+
broccoli chain status <chain_id>
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
```json
|
|
456
|
+
{
|
|
457
|
+
"chain_id": "abc-123",
|
|
458
|
+
"total_tasks": 5,
|
|
459
|
+
"completed_tasks": 3,
|
|
460
|
+
"current_task": 3,
|
|
461
|
+
"status": "in_progress",
|
|
462
|
+
"failed": false
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### List all tasks in a chain
|
|
467
|
+
|
|
468
|
+
```bash
|
|
469
|
+
broccoli chain tasks <chain_id>
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
Shows the full task configuration for every step in the chain, including payloads and task IDs.
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
## Health Check
|
|
477
|
+
|
|
478
|
+
Useful for monitoring systems (e.g., Kubernetes liveness probes).
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
broccoli health
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
- Exits with `0` if Redis is reachable and basic queue operations work.
|
|
485
|
+
- Exits with `1` and prints an error on failure.
|
|
486
|
+
|
|
487
|
+
Example with `curl`‑style usage:
|
|
488
|
+
```bash
|
|
489
|
+
if broccoli health; then
|
|
490
|
+
echo "Broccoli is ready"
|
|
491
|
+
else
|
|
492
|
+
echo "Broccoli is unhealthy" >&2
|
|
493
|
+
exit 1
|
|
494
|
+
fi
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## Output Formats
|
|
500
|
+
|
|
501
|
+
All inspection commands support two output formats:
|
|
502
|
+
|
|
503
|
+
- **`table`** (default) – human‑readable, aligned columns.
|
|
504
|
+
- **`json`** – machine‑readable, ideal for scripting and APIs.
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
# Human‑readable
|
|
508
|
+
broccoli queue stats
|
|
509
|
+
|
|
510
|
+
# Machine‑readable
|
|
511
|
+
broccoli queue stats --format json
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
## Complete Usage Examples
|
|
517
|
+
|
|
518
|
+
### Development workflow
|
|
519
|
+
|
|
520
|
+
Start a single threaded worker with verbose logging:
|
|
521
|
+
```bash
|
|
522
|
+
broccoli -v worker start --type threaded --concurrency 4
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### Production deployment (systemd / supervisor)
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
# Start a pool of 8 async workers, recovering stalled tasks
|
|
529
|
+
broccoli worker start --type async --pool --num-workers 8 \
|
|
530
|
+
--concurrency 10 --recover-stalled 3600
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### Debugging a stuck workflow
|
|
534
|
+
|
|
535
|
+
1. Check if tasks are waiting:
|
|
536
|
+
```bash
|
|
537
|
+
broccoli queue waiting <parent_id>
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
2. Inspect the task that isn't progressing:
|
|
541
|
+
```bash
|
|
542
|
+
broccoli queue get <task_id>
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
3. If a task failed permanently, requeue it:
|
|
546
|
+
```bash
|
|
547
|
+
broccoli dead list
|
|
548
|
+
broccoli dead requeue <failed_task_id>
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
4. Monitor chain progress:
|
|
552
|
+
```bash
|
|
553
|
+
broccoli chain status <chain_id>
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
### Automation / scripting
|
|
557
|
+
|
|
558
|
+
Export settings once:
|
|
559
|
+
```bash
|
|
560
|
+
export BROCCOLI_REDIS_URL="redis://prod-cluster:6379"
|
|
561
|
+
export BROCCOLI_QUEUE_NAME="video:queue"
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
Then run commands without repeating arguments:
|
|
565
|
+
```bash
|
|
566
|
+
broccoli queue stats --format json
|
|
567
|
+
broccoli dead list
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
## CLI Reference
|
|
573
|
+
|
|
574
|
+
| Command | Description |
|
|
575
|
+
|---------|-------------|
|
|
576
|
+
| `worker start` | Start a worker (or pool) with the specified type and concurrency. |
|
|
577
|
+
| `queue stats` | Show runnable, processing, and dead‑letter counts. |
|
|
578
|
+
| `queue list` | List tasks filtered by status (pending, in_progress, etc.). |
|
|
579
|
+
| `queue get <id>` | Fetch and display a task's full metadata. |
|
|
580
|
+
| `queue waiting <parent_id>` | Show tasks blocked on a given parent. |
|
|
581
|
+
| `dead list` | List all permanently failed tasks. |
|
|
582
|
+
| `dead requeue <id>` | Re‑enqueue a failed task (retry). |
|
|
583
|
+
| `chain status <id>` | Show completion progress of a task chain. |
|
|
584
|
+
| `chain tasks <id>` | List all steps in a chain. |
|
|
585
|
+
| `health` | Check Redis connectivity and queue health. |
|
|
586
|
+
|
|
587
|
+
For detailed options on any command, use `--help`:
|
|
588
|
+
|
|
589
|
+
```bash
|
|
590
|
+
broccoli worker start --help
|
|
591
|
+
broccoli queue list --help
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
---
|