ringo-task-queue 0.1.0.dev8__py3-none-macosx_13_0_arm64.whl
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.
- ringo_task_queue/__init__.py +75 -0
- ringo_task_queue/_pb.py +154 -0
- ringo_task_queue/_proto/ringo/v1/queue_pb2.py +153 -0
- ringo_task_queue/_proto/ringo/v1/queue_pb2_grpc.py +797 -0
- ringo_task_queue/_supervisor_child.py +91 -0
- ringo_task_queue/_supervisor_ipc.py +81 -0
- ringo_task_queue/_supervisor_policy.py +182 -0
- ringo_task_queue/_supervisor_process.py +562 -0
- ringo_task_queue/_supervisor_runtime.py +420 -0
- ringo_task_queue/_supervisor_watchdog.py +429 -0
- ringo_task_queue/_version.py +17 -0
- ringo_task_queue/bin/manifest.json +20 -0
- ringo_task_queue/bin/ringo-task-queue-darwin-arm64 +0 -0
- ringo_task_queue/binary.py +253 -0
- ringo_task_queue/client.py +1261 -0
- ringo_task_queue/concurrency.py +480 -0
- ringo_task_queue/daemon.py +507 -0
- ringo_task_queue/errors.py +159 -0
- ringo_task_queue/models.py +449 -0
- ringo_task_queue/py.typed +0 -0
- ringo_task_queue/shared.py +359 -0
- ringo_task_queue/supervisor.py +575 -0
- ringo_task_queue/worker.py +1868 -0
- ringo_task_queue-0.1.0.dev8.dist-info/METADATA +638 -0
- ringo_task_queue-0.1.0.dev8.dist-info/RECORD +27 -0
- ringo_task_queue-0.1.0.dev8.dist-info/WHEEL +4 -0
- ringo_task_queue-0.1.0.dev8.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ringo-task-queue
|
|
3
|
+
Version: 0.1.0.dev8
|
|
4
|
+
Summary: Python asyncio SDK for Ringo Task Queue with a bundled offline Go daemon
|
|
5
|
+
Author: Ringo Task Queue contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: asyncio,grpc,postgresql,sqlite,task-queue
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Framework :: AsyncIO
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: grpcio-health-checking!=1.82.*,<2,>=1.71.0
|
|
23
|
+
Requires-Dist: grpcio-status<2,>=1.71.0
|
|
24
|
+
Requires-Dist: grpcio<2,>=1.71.0
|
|
25
|
+
Requires-Dist: protobuf<7,>=5.29.5
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: build<2,>=1; extra == 'dev'
|
|
28
|
+
Requires-Dist: grpcio-tools==1.71.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: hatchling<2,>=1.27; extra == 'dev'
|
|
30
|
+
Requires-Dist: mypy<2,>=1.15; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff<1,>=0.11; extra == 'dev'
|
|
34
|
+
Provides-Extra: generate
|
|
35
|
+
Requires-Dist: grpcio-tools==1.71.0; extra == 'generate'
|
|
36
|
+
Provides-Extra: supervisor
|
|
37
|
+
Requires-Dist: psutil<8,>=7; (sys_platform != 'win32') and extra == 'supervisor'
|
|
38
|
+
Provides-Extra: test
|
|
39
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'test'
|
|
40
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# Ringo Task Queue — Python SDK
|
|
44
|
+
|
|
45
|
+
面向 Python 3.11+ 的 asyncio 任务队列 SDK。它支持 SDK 管理的本地 SQLite/PostgreSQL daemon 和远程 gRPC 服务,提供永久去重、至少一次执行、lease fencing、自动续租、重试和 DEAD/DLQ 语义。
|
|
46
|
+
|
|
47
|
+
**本文对应发行线:** Python `0.1.0.dev8` / Go `0.1.0-dev.8`,协议 **1.7 / Schema 3**。本文描述该版本代码与 wheel 的能力;是否已经公开上传,以 [PyPI 版本页](https://pypi.org/project/ringo-task-queue/0.1.0.dev8/) 和官方文件为准,候选构建本身不是发布证明。历史 dev7 使用协议 1.5,不包含 supervisor API。仅 Go 数据面与 Python SDK 持续维护;Node.js / Kotlin SDK 封存,保留历史代码/文档/测试,不维护、不发布,恢复须用户明确确认。
|
|
48
|
+
|
|
49
|
+
## 安装后即可离线运行
|
|
50
|
+
|
|
51
|
+
```shell
|
|
52
|
+
python -m pip install --pre "ringo-task-queue==0.1.0.dev8"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
平台 wheel 各内置对应 daemon;以下是分发目标,**不等于全部平台已完成原生运行验收**:
|
|
56
|
+
|
|
57
|
+
| 系统 | wheel tag | 内置 daemon |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| Windows x64 | `win_amd64` | Windows x64 |
|
|
60
|
+
| Linux x64 | `manylinux_2_17_x86_64` | Linux x64 |
|
|
61
|
+
| Linux ARM64 | `manylinux_2_17_aarch64` | Linux ARM64 |
|
|
62
|
+
| macOS 13+ Intel | `macosx_13_0_x86_64` | macOS 13+ Intel |
|
|
63
|
+
| macOS 13+ Apple Silicon | `macosx_13_0_arm64` | macOS 13+ ARM64 |
|
|
64
|
+
|
|
65
|
+
`pip` 会自动选择当前平台的 wheel。每个 wheel 已包含匹配的 Go daemon;本地模式不会在安装或运行时下载二进制,不要求安装 Go,也不需要设置 `RINGO_DAEMON_PATH`。PostgreSQL 服务本身不包含在包内。
|
|
66
|
+
|
|
67
|
+
选择官方索引中实际存在的最新开发版本,需要允许预发布:
|
|
68
|
+
|
|
69
|
+
```shell
|
|
70
|
+
pip install --pre ringo-task-queue
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 快速开始
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import asyncio
|
|
77
|
+
from ringo_task_queue import Ringo
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def main() -> None:
|
|
81
|
+
async with Ringo.local("./data/ringo") as client:
|
|
82
|
+
queue = client.queue("email")
|
|
83
|
+
handled = asyncio.Event()
|
|
84
|
+
|
|
85
|
+
async def handle(task, ctx) -> None:
|
|
86
|
+
print(task.id, task.payload)
|
|
87
|
+
await ctx.report_progress(1, 1, "sent")
|
|
88
|
+
handled.set()
|
|
89
|
+
|
|
90
|
+
worker = queue.worker(
|
|
91
|
+
handle,
|
|
92
|
+
task_types=["send-email"],
|
|
93
|
+
concurrency=4,
|
|
94
|
+
)
|
|
95
|
+
worker_task = asyncio.create_task(worker.run())
|
|
96
|
+
|
|
97
|
+
result = await queue.enqueue(
|
|
98
|
+
unique_key="welcome:user-42",
|
|
99
|
+
task_type="send-email",
|
|
100
|
+
payload={"user_id": 42},
|
|
101
|
+
)
|
|
102
|
+
print(result.task_id, result.created)
|
|
103
|
+
|
|
104
|
+
await asyncio.wait_for(handled.wait(), timeout=10)
|
|
105
|
+
await worker.close()
|
|
106
|
+
await worker_task
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
asyncio.run(main())
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`(queue, unique_key)` 在任务完整生命周期内永久唯一。重复 enqueue 返回原任务 ID 和 `created=False`,不会创建副本,也不会抛出重复异常。
|
|
113
|
+
|
|
114
|
+
## 本地模式
|
|
115
|
+
|
|
116
|
+
### SQLite
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
async with Ringo.local("./data/ringo") as client:
|
|
120
|
+
queue = client.queue("crawl")
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
默认本地模式使用进程私有 daemon:SDK 自动完成 daemon 校验、随机 loopback 端口启动、健康检查、协议协商和退出回收。SQLite 数据保存在 `data_dir`;daemon 日志追加到 `data_dir/ringo-daemon.stderr.log`。两个默认模式的 daemon 不能同时占用同一个 data dir。
|
|
124
|
+
|
|
125
|
+
若同一台机器上、**同一用户**的多个 SDK 进程需要共享一个 SQLite 队列,可显式启用共享模式:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
async with Ringo.local("./data/ringo", shared=True) as client:
|
|
129
|
+
result = await client.queue("crawl").enqueue(
|
|
130
|
+
unique_key="page:1", task_type="fetch", payload={"page": 1}
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
第一个客户端自动启动唯一持锁的共享 daemon,后续客户端认证后附着;启动者退出不影响仍在工作的客户端。最后一个客户端离开后 daemon 延时退出;崩溃后存活客户端会重新发现并接入,不会重放结果不明的写请求。无需手动常驻服务。该模式仅支持本机 SQLite,不支持跨用户、网络共享目录或 PostgreSQL。默认私有模式保持不变。
|
|
135
|
+
|
|
136
|
+
### PostgreSQL
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
import os
|
|
140
|
+
from ringo_task_queue import PostgresStorage, Ringo
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
async with Ringo.local(
|
|
144
|
+
"./data/ringo-process",
|
|
145
|
+
storage=PostgresStorage(os.environ["RINGO_POSTGRES_DSN"]),
|
|
146
|
+
) as client:
|
|
147
|
+
queue = client.queue("crawl")
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
PostgreSQL 必须由应用提供。SDK 不下载、启动或停止 PostgreSQL。DSN 通过子进程环境传递,不放进进程参数,`PostgresStorage` 的 `repr` 也会隐藏它。
|
|
151
|
+
|
|
152
|
+
## 远程模式
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
async with Ringo.connect(
|
|
156
|
+
"https://queue.internal.example:7233",
|
|
157
|
+
token="service-token",
|
|
158
|
+
) as client:
|
|
159
|
+
queue = client.queue("crawl")
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`grpc://`/`http://` 使用明文,`grpcs://`/`https://` 使用 TLS;省略端口时默认 `7233`。
|
|
163
|
+
|
|
164
|
+
受限链路可以启用整个 gRPC channel 的 gzip:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
async with Ringo.connect(
|
|
168
|
+
"https://queue.internal.example:7233",
|
|
169
|
+
token="service-token",
|
|
170
|
+
compression="gzip",
|
|
171
|
+
) as client:
|
|
172
|
+
...
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`compression` 只接受 `"none"` 或 `"gzip"`。它覆盖 unary RPC 和 Work stream 并在重连后保持;本地模式始终不压缩。
|
|
176
|
+
|
|
177
|
+
协议 1.6 的 ordered Work sources / SQLite ACK group commit 是当前源码的历史演进边界,1.7 的只读 `ProbeQueue` 用于可选疑似停滞观察;都不是已发布 dev7 / 协议 1.5 能力。远端可以按契约降级,bundled/shared-local 的本地 SDK/daemon 仍须精确匹配版本、协议和 schema,不能因 Schema 相同就混装。
|
|
178
|
+
|
|
179
|
+
## 提交任务
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from datetime import datetime, timedelta, timezone
|
|
183
|
+
from ringo_task_queue import RetryPolicy, RetryStrategy, TaskSpec
|
|
184
|
+
|
|
185
|
+
spec = TaskSpec(
|
|
186
|
+
unique_key="page:10001",
|
|
187
|
+
task_type="fetch-page",
|
|
188
|
+
payload={"url": "https://example.com/10001"},
|
|
189
|
+
priority=10,
|
|
190
|
+
available_at=datetime.now(timezone.utc) + timedelta(seconds=5),
|
|
191
|
+
retry_policy=RetryPolicy(
|
|
192
|
+
max_attempts=5,
|
|
193
|
+
strategy=RetryStrategy.EXPONENTIAL,
|
|
194
|
+
initial_delay=timedelta(seconds=1),
|
|
195
|
+
max_delay=timedelta(seconds=60),
|
|
196
|
+
multiplier=2.0,
|
|
197
|
+
jitter=0.2,
|
|
198
|
+
),
|
|
199
|
+
)
|
|
200
|
+
result = await queue.enqueue(spec)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
也可以使用关键字参数:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
result = await queue.enqueue(
|
|
207
|
+
unique_key="page:10001",
|
|
208
|
+
task_type="fetch-page",
|
|
209
|
+
payload={"url": "https://example.com/10001"},
|
|
210
|
+
)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
payload 必须显式提供;显式 `None` 表示 JSON null。允许 JSON 对象、数组和标量,但:
|
|
214
|
+
|
|
215
|
+
- UTF-8 JSON 编码不超过 1 MiB;
|
|
216
|
+
- 不允许非有限浮点数;
|
|
217
|
+
- 整数必须位于 `[-2**53, 2**53]`;
|
|
218
|
+
- JSON 对象的键必须是字符串;
|
|
219
|
+
- 实际使用建议保持在 16–64 KiB 内,大对象只传外部引用。
|
|
220
|
+
|
|
221
|
+
### 批量提交
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
batch = await queue.enqueue_many(
|
|
225
|
+
[
|
|
226
|
+
TaskSpec(
|
|
227
|
+
unique_key=f"page:{i}",
|
|
228
|
+
task_type="fetch-page",
|
|
229
|
+
payload={"page": i},
|
|
230
|
+
)
|
|
231
|
+
for i in range(100)
|
|
232
|
+
]
|
|
233
|
+
)
|
|
234
|
+
print(batch.created_count, batch.duplicate_count)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
`enqueue_many` 接受任意数量任务,SDK 自动按服务端单批上限 1,000 条切分并顺序发送,最终聚合为单个 `BatchResult`;`batch.results` 保留逐项结果。若任一批次失败,异常直接抛出,不返回部分结果。
|
|
238
|
+
|
|
239
|
+
## 查询和手工重试
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
task = await queue.get_by_id(result.task_id)
|
|
243
|
+
task = await queue.get_by_unique_key("page:10001")
|
|
244
|
+
|
|
245
|
+
# 始终按业务 unique_key 解释
|
|
246
|
+
reopened = await queue.retry("page:10001")
|
|
247
|
+
|
|
248
|
+
# 显式按服务端 task ID
|
|
249
|
+
reopened = await queue.retry_by_id(result.task_id)
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
状态包括 `SCHEDULED`、`READY`、`LEASED`、`SUCCEEDED`、`DEAD` 和 `CANCELLED`。`Task` 还提供最后错误、进度、当前 lease 摘要和最近 attempt 预览(最多 32 条 / 512 KiB);`task.attempts_truncated` 明确表示省略。手工 retry 只重置本轮 attempt,不删除累计历史或永久去重关系。
|
|
253
|
+
|
|
254
|
+
协议 1.5 的完整历史分页按稳定的 `history_id` 排序,首次读取的高水位保证后续新 attempt 不混入同一分页快照:
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
page = await queue.get_attempts("page:10001", limit=32)
|
|
258
|
+
# 或按服务端 task ID:await queue.get_attempts_by_id(result.task_id)
|
|
259
|
+
while True:
|
|
260
|
+
for attempt in page.attempts:
|
|
261
|
+
print(attempt.history_id, attempt.number, attempt.outcome)
|
|
262
|
+
if page.next_cursor is None:
|
|
263
|
+
break
|
|
264
|
+
page = await queue.get_attempts("page:10001", limit=32, cursor=page.next_cursor)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
页上限为 100 条 / 1 MiB;Python 整数保留 uint64 ID 精度。`number` 在显式 retry 后可以再次从 1 开始,不是累计行 ID。只读 `await queue.stats()` 需要协议 1.4+,返回全生命周期的状态计数快照,不运行 reap/retry。
|
|
268
|
+
|
|
269
|
+
## Worker(推荐)
|
|
270
|
+
|
|
271
|
+
处理队列任务推荐直接使用 `Worker`,而不是下面的手工 Claim:Worker 自动管理注册、credit、lease 续租、并发和优雅关闭。
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
from datetime import timedelta
|
|
275
|
+
from ringo_task_queue import PermanentTaskError, RetryTaskError
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
async def handle(task, ctx) -> None:
|
|
279
|
+
await ctx.report_progress(1, 2, "started")
|
|
280
|
+
if should_retry(task):
|
|
281
|
+
raise RetryTaskError("rate limited", delay=30) # 秒
|
|
282
|
+
if invalid(task):
|
|
283
|
+
raise PermanentTaskError("invalid input")
|
|
284
|
+
await process(task)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
worker = queue.worker(
|
|
288
|
+
handle,
|
|
289
|
+
task_types=["fetch-page"],
|
|
290
|
+
concurrency=32,
|
|
291
|
+
lease_duration=timedelta(seconds=60),
|
|
292
|
+
grace_period=timedelta(seconds=30),
|
|
293
|
+
)
|
|
294
|
+
await worker.run()
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
`run()` 一直运行到其他协程调用 `await worker.close()`,并且只能调用一次。常驻消费时用 `asyncio.create_task(worker.run())` 启动;已知有限批次(见下)直接 `await worker.run()`。
|
|
298
|
+
|
|
299
|
+
### 三种并发模式、共享代理池和 pause/resume
|
|
300
|
+
|
|
301
|
+
整数是固定并发,创建时至少为 1;运行后可以把目标改成 `0..2**31-1`:
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
worker = queue.worker(handle, concurrency=8)
|
|
305
|
+
run_task = asyncio.create_task(worker.run())
|
|
306
|
+
await worker.set_concurrency(0) # pause:不再派发,已有 handler 不取消
|
|
307
|
+
await worker.set_concurrency(8) # resume/resize
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
`ResourceConcurrency` 是同一进程/事件循环可由多个 queue/client 共享的总容量。容量是 admission permit,不是每个 Worker 的副本;缩容不会抢占已运行的 handler。
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
from ringo_task_queue import ResourceConcurrency
|
|
314
|
+
|
|
315
|
+
proxies = ResourceConcurrency(capacity=12)
|
|
316
|
+
west = client.queue("west").worker(handle, concurrency=proxies, weight=1)
|
|
317
|
+
east = client.queue("east").worker(handle, concurrency=proxies, weight=3)
|
|
318
|
+
runs = [asyncio.create_task(w.run()) for w in (west, east)]
|
|
319
|
+
|
|
320
|
+
await proxies.set_capacity(20) # 例如健康代理数变化
|
|
321
|
+
# 共享 controller 时不能调用 west.set_concurrency(...)
|
|
322
|
+
|
|
323
|
+
await west.close()
|
|
324
|
+
await east.close()
|
|
325
|
+
await asyncio.gather(*runs)
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
持续有任务时,权重按平滑加权轮转长期接近 1:3;空闲成员的临时 probe 会在 250 ms 后撤回,因此容量会给活跃成员 work-conserving 使用。可手动暂停整组:`await proxies.set_capacity(0)`,恢复时再设置非零容量。
|
|
329
|
+
|
|
330
|
+
`AdaptiveConcurrency` 使用 AIMD:当前容量个成功 ACK(且当时已饱和)后 `+1`,只有成功 NACK 且 `RetryTaskError(congestion=True)` 才按 `*0.5` 降低,始终限制在 `minimum..maximum`;普通异常、永久失败和 lease/传输丢失不改变它:
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
from ringo_task_queue import AdaptiveConcurrency, RetryTaskError
|
|
334
|
+
|
|
335
|
+
limits = AdaptiveConcurrency(initial=4, minimum=1, maximum=32)
|
|
336
|
+
|
|
337
|
+
async def handle(task, ctx):
|
|
338
|
+
try:
|
|
339
|
+
await call_upstream(task.payload)
|
|
340
|
+
except RateLimitError as exc:
|
|
341
|
+
raise RetryTaskError(str(exc), delay=2, congestion=True) from exc
|
|
342
|
+
|
|
343
|
+
worker = queue.worker(handle, concurrency=limits)
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
动态 controller 和运行中的 fixed resize 需要协议 minor 2;不改变 fixed 并发的 Worker 仍兼容旧 minor。controller 可以跨 queue/client 共享,但不能跨进程共享。
|
|
347
|
+
|
|
348
|
+
Handler 映射:
|
|
349
|
+
|
|
350
|
+
| 结果 | 动作 |
|
|
351
|
+
|---|---|
|
|
352
|
+
| 正常返回 | ACK / `SUCCEEDED` |
|
|
353
|
+
| 普通异常 | NACK / 按策略重试 |
|
|
354
|
+
| `RetryTaskError` | NACK,可覆盖本次延迟;仅 `congestion=True` 且 NACK 被服务端接受时触发 Adaptive 的降速 |
|
|
355
|
+
| `PermanentTaskError` | Reject / `DEAD` |
|
|
356
|
+
| 取消 | 不发送终态,等待 lease 到期重投 |
|
|
357
|
+
|
|
358
|
+
Worker 持有 lease token,自动续租并严格控制并发。`TaskContext` 提供 `attempt`、`max_attempts`、`worker_id`、`cancelled`、`wait_cancelled()` 和 `report_progress()`。lease 丢失、stream 断开或强制关闭时,context 会被取消。
|
|
359
|
+
|
|
360
|
+
这是至少一次系统:handler 的外部副作用必须使用 `unique_key` 或任务 ID 实现幂等。
|
|
361
|
+
|
|
362
|
+
### 有限批次与 `on_idle`(PyPI 推荐范式)
|
|
363
|
+
|
|
364
|
+
已知有限批次(例如"把这批 seed 跑完就退出")的推荐写法:**先在 `run()` 之前提交幂等 seed**,再给 Worker 配置一次性的 `on_idle` 回调,并**直接 `await worker.run()`**:
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
import asyncio
|
|
368
|
+
import sys
|
|
369
|
+
|
|
370
|
+
from ringo_task_queue import Ringo
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
async def main() -> None:
|
|
374
|
+
async with Ringo.local("./data/ringo") as client:
|
|
375
|
+
queue = client.queue("email")
|
|
376
|
+
|
|
377
|
+
# 1. 先提交幂等 seed(run() 之前 enqueue)。
|
|
378
|
+
for user_id in (101, 102, 103):
|
|
379
|
+
await queue.enqueue(
|
|
380
|
+
unique_key=f"welcome:{user_id}",
|
|
381
|
+
task_type="send-email",
|
|
382
|
+
payload={"user_id": user_id},
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
async def handle(task, ctx) -> None:
|
|
386
|
+
await send_welcome(task.payload["user_id"])
|
|
387
|
+
|
|
388
|
+
# 2. 队列在该 Worker 的 task type 范围内连续空闲满 1 秒后触发一次。
|
|
389
|
+
worker = queue.worker(
|
|
390
|
+
handle,
|
|
391
|
+
task_types=["send-email"],
|
|
392
|
+
concurrency=4,
|
|
393
|
+
on_idle=lambda: sys.exit(0),
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
# 3. 前台直接 await run();on_idle 触发后整个批次随之结束。
|
|
397
|
+
await worker.run()
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
asyncio.run(main())
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
语义与限制(务必阅读):
|
|
404
|
+
|
|
405
|
+
- **固定参数**:SDK 以 250 ms 间隔轮询、连续空闲满 1 秒触发一次;这两个值是协议级常量,不提供配置参数。
|
|
406
|
+
- **单次触发**:回调最多触发一次。正常返回后 Worker 继续常驻运行且不再触发;要结束进程请在回调里退出(如 `sys.exit(0)`),异常会原样从 `await worker.run()` 传播。
|
|
407
|
+
- **type scope**:活动数只统计该 Worker `task_types` 范围内的任务(`SCHEDULED`/`READY`/`LEASED` 都算活动),不影响队列里其他类型的任务。
|
|
408
|
+
- **开放队列竞态**:空闲观察只是开放队列上的观测快照,不是持久完成判定。回调**正常返回**且 Worker 保持常驻时,观察之后提交的新任务仍会被正常消费;但如果回调直接退出进程(如 `sys.exit(0)`),观测之后、退出之前竞态提交的任务不会被本 Worker 处理,可能留给其他 Worker 或下一次运行。因此 `sys.exit(0)` 范式只适合生产者已经停止的有限批次。需要严格分布式完成判定时,必须另行设计 queue seal/generation。
|
|
409
|
+
- **不要在回调里 `close()`**:`on_idle` 由 `worker.run()` 协程内联执行,在回调里等待同一 Worker/client 的关闭会造成自等待。外层资源作用域(如上面的 `async with`)负责清理。
|
|
410
|
+
- **兼容性**:不带 `on_idle` 的 Worker 从不发送活动查询 RPC,与协议 minor 0 的旧 server 完全兼容;配置 `on_idle` 需要 minor ≥ 1 的 server,旧 server 会在 `run()` 快速抛出 `IncompatibleVersionError`。
|
|
411
|
+
|
|
412
|
+
## 手工 Claim
|
|
413
|
+
|
|
414
|
+
> 常规消费任务优先使用上面推荐的 `Worker`;手工 Claim 适用于需要自行控制 claim/ACK 时序的高级场景。
|
|
415
|
+
|
|
416
|
+
```python
|
|
417
|
+
from datetime import timedelta
|
|
418
|
+
|
|
419
|
+
leased = await queue.claim(
|
|
420
|
+
limit=10,
|
|
421
|
+
task_types=["fetch-page"],
|
|
422
|
+
wait_timeout=timedelta(seconds=5),
|
|
423
|
+
lease_duration=timedelta(seconds=60),
|
|
424
|
+
worker_id="manual-worker-1",
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
for item in leased:
|
|
428
|
+
try:
|
|
429
|
+
await process(item.task)
|
|
430
|
+
await item.ack()
|
|
431
|
+
except TimeoutError as exc:
|
|
432
|
+
await item.nack(str(exc), delay=timedelta(seconds=10))
|
|
433
|
+
except ValueError as exc:
|
|
434
|
+
await item.reject(str(exc))
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
续租:
|
|
438
|
+
|
|
439
|
+
```python
|
|
440
|
+
await item.extend_lease(timedelta(seconds=60))
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
lease token 不公开。旧 attempt 或迟到 ACK 会被 fencing 拒绝并映射成 `LeaseLostError`。
|
|
444
|
+
|
|
445
|
+
## 生命周期和关闭
|
|
446
|
+
|
|
447
|
+
推荐:
|
|
448
|
+
|
|
449
|
+
```python
|
|
450
|
+
async with Ringo.local("./data/ringo") as client:
|
|
451
|
+
...
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
或手工:
|
|
455
|
+
|
|
456
|
+
```python
|
|
457
|
+
client = Ringo.local("./data/ringo")
|
|
458
|
+
await client.start()
|
|
459
|
+
try:
|
|
460
|
+
...
|
|
461
|
+
finally:
|
|
462
|
+
await client.close()
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
`close()` 可重复调用,且调用者取消不会中断底层清理。它会先 drain worker,在 grace period 内继续续租并允许 handler 完成,再关闭 channel 和 daemon。被强制取消的 handler 不发送 ACK/NACK。
|
|
466
|
+
|
|
467
|
+
本地 daemon 意外退出时最多自动重启三次。结果不确定的写操作不会被盲目重放;只读查询可以在重新协商后重试。
|
|
468
|
+
|
|
469
|
+
## 可选业务进程守护(dev8)
|
|
470
|
+
|
|
471
|
+
这是显式 opt-in API,不是 `Ringo.local()` 的自动重启选项,也不是通用服务平台。该 API 需要 dev8 或后续支持版本;历史 dev7 不提供。可从完整候选 wheel 集合验收,公开安装是否可用以官方 PyPI 文件为准:
|
|
472
|
+
|
|
473
|
+
```shell
|
|
474
|
+
python -m pip install --pre 'ringo-task-queue[supervisor]==0.1.0.dev8'
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
POSIX supervisor extra 安装 `psutil>=7,<8` 以观察进程身份/RSS;Windows 使用 native Job/进程 API,不强制 psutil。普通 Worker 不必启用 supervisor。源码 checkout 可自行安装 `.[supervisor]` extra 并准备同线 daemon,但源码/交叉构建不等于发行或全平台原生验证。
|
|
478
|
+
|
|
479
|
+
### 一个同步启动器、两个不可变配置对象
|
|
480
|
+
|
|
481
|
+
```python
|
|
482
|
+
from ringo_task_queue.supervisor import supervise, SupervisorPolicy, TaskTimeouts
|
|
483
|
+
# supervise(entrypoint: str, *, policy: SupervisorPolicy | None = None,
|
|
484
|
+
# task_timeouts: TaskTimeouts | None = None) -> int
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
`supervise` 是 **blocking、main-thread** 启动器,必须在运行中的 asyncio loop 之外调用。入口是可导入、可零参调用的 `module:function`,支持 sync/async;同步入口可以自行拥有 `asyncio.run()`,异步入口的 awaitable 也可直接运行。不重建当前命令、不 pickle 闭包,不接受 `__main__` / 局部函数,也不递归监督自己。`SupervisorPolicy` / `TaskTimeouts` 是 immutable 配置,`by_type` 会复制成只读映射;所有公开时长使用 `datetime.timedelta`。
|
|
488
|
+
|
|
489
|
+
真实 handler 可能健康运行 20–30 分钟时,要**同时**留够业务总预算、Worker drain 和父级整代 shutdown 三个窗口。以下 45/45/46 分钟仅为清楚的配置示例,不是通用 SLA;快任务应另配更短预算。
|
|
490
|
+
|
|
491
|
+
```python
|
|
492
|
+
# myapp.py:可导入的零参业务入口,不在导入时启动事件循环
|
|
493
|
+
from datetime import timedelta as td
|
|
494
|
+
from ringo_task_queue import Ringo
|
|
495
|
+
|
|
496
|
+
async def main() -> None:
|
|
497
|
+
async with Ringo.local("./data/ringo") as client:
|
|
498
|
+
async def handle(task, ctx) -> None:
|
|
499
|
+
# 替换成幂等的异步业务;实际阶段完成后才 mark_progress。
|
|
500
|
+
print(task.id, task.payload)
|
|
501
|
+
ctx.mark_progress()
|
|
502
|
+
|
|
503
|
+
worker = client.queue("crawl").worker(
|
|
504
|
+
handle, task_types=["fetch-page"], concurrency=4,
|
|
505
|
+
grace_period=td(minutes=45),
|
|
506
|
+
task_timeouts=None, # 继承 launcher 的 TaskTimeouts
|
|
507
|
+
)
|
|
508
|
+
await worker.run()
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
```python
|
|
512
|
+
# launch.py:python launch.py,在最外层保护中同步调用,不放进 asyncio.run
|
|
513
|
+
from datetime import timedelta as td
|
|
514
|
+
from ringo_task_queue.supervisor import supervise, SupervisorPolicy, TaskTimeouts
|
|
515
|
+
|
|
516
|
+
if __name__ == "__main__":
|
|
517
|
+
raise SystemExit(supervise(
|
|
518
|
+
"myapp:main",
|
|
519
|
+
policy=SupervisorPolicy(
|
|
520
|
+
recycle_after=td(hours=12),
|
|
521
|
+
shutdown_grace=td(minutes=46),
|
|
522
|
+
no_start_timeout=td(minutes=5),
|
|
523
|
+
restart_on_stall=False,
|
|
524
|
+
),
|
|
525
|
+
task_timeouts=TaskTimeouts(
|
|
526
|
+
default=td(minutes=45),
|
|
527
|
+
by_type={"fast": td(minutes=2), "unbounded": None},
|
|
528
|
+
progress_timeout=None,
|
|
529
|
+
cancel_grace=td(seconds=5),
|
|
530
|
+
),
|
|
531
|
+
))
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### 总时长、业务进度和外层守护分开
|
|
535
|
+
|
|
536
|
+
- `TaskTimeouts.default` 是**实际业务执行总预算**,不含本地排队、permit 等待或业务结束后的 ACK/NACK 回执;`by_type` 只覆盖总预算,某类型的 `None` 表示该类型总时长 unlimited,不禁用独立的进度预算。
|
|
537
|
+
- `progress_timeout` 仅在首次真实业务进度后计时。`ctx.mark_progress()` 同步、本地、无 RPC,在 Worker 所属 asyncio 循环调用;业务线程可通过 `loop.call_soon_threadsafe(ctx.mark_progress)` 回送标记。`await ctx.report_progress(...)` 也记本地进度。从未报进度不判死;lease 续租、ACK 和其他 Worker 的推进都不是本任务的业务进度。按实际阶段推进标记,不为喂 watchdog 增加逐任务网络调用。
|
|
538
|
+
- `queue.worker(..., task_timeouts=None)` 继承 supervisor;传 `TaskTimeouts(...)` **整体替换而不合并**,包括 `by_type`、`progress_timeout` 与 `cancel_grace`。`TaskTimeouts()` 明确使该 Worker 不限任务总时长、不开进度超时。
|
|
539
|
+
- `startup_timeout` 只管启动/导入/就绪,`heartbeat_timeout` 管可响应性,`soft_rss_bytes` / `max_rss_bytes` 管 owned 树采样 RSS;**它们不是 task timeout**。默认可选破坏性阈值为 `None`,RSS 不是内核硬限额。硬 RSS 观察在整代 drain 期间仍生效;排空中越限会缩短等待并回收,原计划轮换也按资源故障计入重启预算。纯同步阻塞也会妨碍 asyncio 续租;WebDriver 等阻塞调用应使用异步方式或 `asyncio.to_thread`,而非依靠守护假装续租安全。
|
|
540
|
+
- 超时先 fence 再取消;协作停止且 lease/source 仍有效时可用超时错误 NACK。吞取消、`uncancel()` 或超时后返回都不能 ACK 成功。不配合且超过 `cancel_grace` 才请求整代恢复;单独 Worker 只能协作取消,不能强杀 Python/native/browser 代码。父级观察也是采样,不是实时硬截止;含 unlimited 类型时不能臆造通用总时长界限,需要另行选择独立心跳 SLA。
|
|
541
|
+
- `no_start_timeout` 启用协议 1.7 的只读 `ProbeQueue`,只查匹配 READY / 到期 SCHEDULED 且剩余 attempt 的 EXISTS,不 claim/reap/count 历史。它按每 Worker/当前 source、实际获 permit 与服务端 accepted credit 判断**疑似停滞**,默认 warn。`restart_on_stall=True` 显式接受多消费者竞争误判风险;远端 minor <7 跳过此探测但保留其他守护,不绕过正常版本握手。
|
|
542
|
+
|
|
543
|
+
### 资源归属、退出与恢复
|
|
544
|
+
|
|
545
|
+
Windows 的 root 进程先以 **initially suspended** 状态创建,再 assign 到 native kill-on-close Job 后 resume,避免 venv redirector 在归属前派生 base interpreter;用户模块导入也等待 containment。shared-local daemon 使用显式 breakaway,失败不回退。POSIX 使用进程组/session 与身份跟踪,不是 cgroup,未被观察到就 setsid 逃逸的子进程不保证回收。共享 daemon、外部 PostgreSQL、远程 WebDriver 不归 business 所有;远端浏览器资源仍需业务 `try/finally` 或上下文清理。身份不明/清理不确定时 fail closed,不启动新代。
|
|
546
|
+
|
|
547
|
+
`shutdown_grace` 是**整代上限**,不是多个 Worker grace 相加;STOP 后所有 Worker 并行 drain。正常 return / exit 0 结束守护;计划轮换已触发时,即使收尾 exit 0 仍重启,且不消耗、不清空故障预算。Ctrl+C 停止而不重启。未确认任务按 lease expiry 恢复,已消耗 attempt 不退款、`maxAttempts` 耗尽进入 DEAD,不伪造成功或自动激活 DEAD,业务必须幂等。supervisor 自身/机器退出后的自动恢复属于 OS 外层服务管理,不由这个 API 承诺。
|
|
548
|
+
|
|
549
|
+
## 错误处理
|
|
550
|
+
|
|
551
|
+
```python
|
|
552
|
+
from ringo_task_queue import NotFoundError, RingoError, UnavailableError
|
|
553
|
+
|
|
554
|
+
try:
|
|
555
|
+
task = await queue.get_by_unique_key("missing")
|
|
556
|
+
except NotFoundError:
|
|
557
|
+
...
|
|
558
|
+
except UnavailableError as exc:
|
|
559
|
+
logger.warning("temporarily unavailable: %s", exc)
|
|
560
|
+
except RingoError as exc:
|
|
561
|
+
logger.exception(
|
|
562
|
+
"queue error code=%s request_id=%s metadata=%r",
|
|
563
|
+
exc.code,
|
|
564
|
+
exc.request_id,
|
|
565
|
+
exc.metadata,
|
|
566
|
+
)
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
公共异常:
|
|
570
|
+
|
|
571
|
+
- `InvalidArgumentError`;
|
|
572
|
+
- `DuplicateError`(普通重复 enqueue 返回 `created=False`);
|
|
573
|
+
- `NotFoundError`;
|
|
574
|
+
- `ConflictError`;
|
|
575
|
+
- `LeaseLostError`;
|
|
576
|
+
- `UnavailableError`;
|
|
577
|
+
- `DeadlineExceededError`;
|
|
578
|
+
- `IncompatibleVersionError`;
|
|
579
|
+
- `InternalError`。
|
|
580
|
+
|
|
581
|
+
## 默认值与限制
|
|
582
|
+
|
|
583
|
+
| 项目 | 默认值或限制 |
|
|
584
|
+
|---|---|
|
|
585
|
+
| 本发行线协议 | 1.7(历史 dev7 为 1.5) |
|
|
586
|
+
| schema | 3 |
|
|
587
|
+
| lease | 60 秒 |
|
|
588
|
+
| worker concurrency | 1 |
|
|
589
|
+
| Worker graceful shutdown | 30 秒;长任务显式配置 `grace_period` |
|
|
590
|
+
| supervisor task total/progress timeout | 默认均为 `None`,不开任务预算 |
|
|
591
|
+
| supervisor shutdown/startup | 分别默认 30 秒;整代 shutdown 与任务总预算独立 |
|
|
592
|
+
| supervisor stall action | 默认 `restart_on_stall=False`;仅配置 no-start 窗口后观察并 warn |
|
|
593
|
+
| retry attempts | 每轮默认 3,最大 32;累计历史不设生命周期 32 条上限 |
|
|
594
|
+
| Task attempt 预览 | 最多 32 条 / 512 KiB,截断明确标记 |
|
|
595
|
+
| 完整历史分页 | 默认 32,最多 100 条 / 1 MiB,需要协议 1.5 |
|
|
596
|
+
| 默认 decoded gRPC envelope | 16 MiB;显式 channel 选项优先 |
|
|
597
|
+
| retry | 指数退避,1 秒到 60 秒,multiplier 2,jitter 0.2 |
|
|
598
|
+
| batch | SDK 自动分批,单批最大 1,000 |
|
|
599
|
+
| payload | 最大 1 MiB UTF-8 JSON |
|
|
600
|
+
| error text | 最大 256 KiB UTF-8 |
|
|
601
|
+
| daemon 自动重启 | 最多 3 次 |
|
|
602
|
+
|
|
603
|
+
## wheel 完整性和二进制解析
|
|
604
|
+
|
|
605
|
+
本地 daemon 解析优先级:
|
|
606
|
+
|
|
607
|
+
1. `Ringo.local(..., daemon_path=...)`;
|
|
608
|
+
2. `RINGO_DAEMON_PATH`;
|
|
609
|
+
3. wheel 内置平台 daemon;
|
|
610
|
+
4. 仅用于开发 checkout 的搜索。
|
|
611
|
+
|
|
612
|
+
普通 PyPI 安装直接使用第 3 项。SDK 在启动前验证 daemon 是普通文件、长度和 SHA-256 与 manifest 一致,然后运行 `version --json` 校验产品名、版本、协议和 schema。校验失败时拒绝启动,不会静默使用错误二进制。
|
|
613
|
+
|
|
614
|
+
## 常见问题
|
|
615
|
+
|
|
616
|
+
### `No matching distribution found`
|
|
617
|
+
|
|
618
|
+
确认 Python 为 3.11+,对应平台 wheel 已上传;安装开发版本时使用 `--pre`。
|
|
619
|
+
|
|
620
|
+
### 找不到 daemon
|
|
621
|
+
|
|
622
|
+
正式 wheel 不应出现。使用 `python -m pip show -f ringo-task-queue` 检查是否误装了 editable checkout 或非正式产物。
|
|
623
|
+
|
|
624
|
+
### `ConflictError`
|
|
625
|
+
|
|
626
|
+
另一个进程通常已占用同一个 SQLite data dir。复用已有客户端或换一个目录。
|
|
627
|
+
|
|
628
|
+
### 任务重复执行
|
|
629
|
+
|
|
630
|
+
这是至少一次语义的正常边界。使用任务 ID/`unique_key` 为外部副作用建立幂等约束。
|
|
631
|
+
|
|
632
|
+
### Worker 关闭较慢
|
|
633
|
+
|
|
634
|
+
默认允许 handler 在 30 秒内完成。可以调整 `grace_period`,并让 handler 响应 cancellation 或 `ctx.cancelled`。dev8 源码启用 supervisor 时还必须相应延长整代 `SupervisorPolicy.shutdown_grace`;例如健康 20–30 分钟任务可分别配置任务总预算 45 分钟、Worker grace 45 分钟、父级 shutdown 46 分钟。单独延长任务预算不会延长退出窗口。
|
|
635
|
+
|
|
636
|
+
## 许可证
|
|
637
|
+
|
|
638
|
+
MIT License。
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
ringo_task_queue/__init__.py,sha256=alSfOYp1QTH4eolTnSkl_gJpI-XC5LN7ijkoHQD4HZk,1571
|
|
2
|
+
ringo_task_queue/_pb.py,sha256=gA8HMCx9ZXEMB-ca6me8CYXdRLLPzUXVIz63Q4bz75I,5683
|
|
3
|
+
ringo_task_queue/_supervisor_child.py,sha256=yodTiYJ4UKRisYssVpxJvyamUt4M2eS1Hdp3EYOMngk,3155
|
|
4
|
+
ringo_task_queue/_supervisor_ipc.py,sha256=wZS3iHtB41QO7o70mEb8PsPfcVH07p-MEiy3K8c3Ug4,3110
|
|
5
|
+
ringo_task_queue/_supervisor_policy.py,sha256=moMF_khQDWajEOyNZ6fKNJCVWhyUF5JbhsvKbIw69jk,7375
|
|
6
|
+
ringo_task_queue/_supervisor_process.py,sha256=IBqNW0U6Dvt1tPCkgDSpmSam3kuVxft18D0P0OiroZA,23548
|
|
7
|
+
ringo_task_queue/_supervisor_runtime.py,sha256=N8FR5xCJ2bHrDcHf2td_x3-IysJzDChew6WxztgFWlo,16518
|
|
8
|
+
ringo_task_queue/_supervisor_watchdog.py,sha256=-gam4zvr98Ug1Dv59AMv1o2msiccJUT2R22FXIouSP8,17600
|
|
9
|
+
ringo_task_queue/_version.py,sha256=WtWgo4TJhfWDKq712U4ydYQ9SsH_EnzYYFD9cdFterA,642
|
|
10
|
+
ringo_task_queue/binary.py,sha256=lJtFz_t5BuxDV4dMAqQDpP3mhTvJlnPBGnaiB0Z9D04,9526
|
|
11
|
+
ringo_task_queue/client.py,sha256=G1q8BFgU-1_ZHoAJlvV4ypaJfAOFURwEqSg5Ig6IVP8,52084
|
|
12
|
+
ringo_task_queue/concurrency.py,sha256=AxmtDa39xoda0TnvxNlLmZri1TTWCkS7VaRomAONwLU,19151
|
|
13
|
+
ringo_task_queue/daemon.py,sha256=YS8otZpD9hqKeASb2S8ZWLCWzQj6-FYUb-grmYSnrIs,19849
|
|
14
|
+
ringo_task_queue/errors.py,sha256=FQXkfz0SS-mFtY61b6f9HamPZUPWcuf-33AfB7B0rmo,4829
|
|
15
|
+
ringo_task_queue/models.py,sha256=75hAR0XDAb0BFWvVaoBzj_HGrcIQX4MH6MFmJOHZENY,14556
|
|
16
|
+
ringo_task_queue/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
ringo_task_queue/shared.py,sha256=s2WB-6NKGxUoioOz3oGKazkdQBKuiSWvQzQyq_ABi-s,14105
|
|
18
|
+
ringo_task_queue/supervisor.py,sha256=ILc5hXwHSDjNjwdDFEL5RePsS9rUsjqWu_FzU8TU3ps,22559
|
|
19
|
+
ringo_task_queue/worker.py,sha256=p9rrFXU3k4np0m1Tio5XZx8ipLKVjSiM5hS_6rF_WsI,80397
|
|
20
|
+
ringo_task_queue/_proto/ringo/v1/queue_pb2.py,sha256=NehLqhJKBIakaeP0534UHOtZVnuWudMw2xN05bQZKfI,21867
|
|
21
|
+
ringo_task_queue/_proto/ringo/v1/queue_pb2_grpc.py,sha256=Nk_Eg1YV-LC7K4JV-KomgIaEx7hb5ZZ65V0bFg-XYyI,36822
|
|
22
|
+
ringo_task_queue/bin/manifest.json,sha256=5R3sLLUJN11UWpRFICAYK56xkGsYIiQWGeYCkuBvZeM,674
|
|
23
|
+
ringo_task_queue/bin/ringo-task-queue-darwin-arm64,sha256=HbIbTuF2W4eOIFXOpjGMf4VFLpfza6NhW7lfHY2mR0Q,22132594
|
|
24
|
+
ringo_task_queue-0.1.0.dev8.dist-info/METADATA,sha256=UWL6dZEXJMQwjFkGCiB_XnkXFcoEQ9zdxEfu85Od7V8,28949
|
|
25
|
+
ringo_task_queue-0.1.0.dev8.dist-info/WHEEL,sha256=UdtIaXUypiq_TXm9T5EXO7SUX68Qi7Twa82HX8g5nwA,102
|
|
26
|
+
ringo_task_queue-0.1.0.dev8.dist-info/licenses/LICENSE,sha256=yK3nnJz_bbOAPlcJqAooYcvecNJnBH9miT_X8Qrerv4,1086
|
|
27
|
+
ringo_task_queue-0.1.0.dev8.dist-info/RECORD,,
|