tasklite-engine 1.0.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.
Files changed (44) hide show
  1. tasklite_engine-1.0.0/LICENSE +21 -0
  2. tasklite_engine-1.0.0/PKG-INFO +321 -0
  3. tasklite_engine-1.0.0/README.md +305 -0
  4. tasklite_engine-1.0.0/pyproject.toml +53 -0
  5. tasklite_engine-1.0.0/setup.cfg +4 -0
  6. tasklite_engine-1.0.0/tasklite/__init__.py +55 -0
  7. tasklite_engine-1.0.0/tasklite/backend/__init__.py +1 -0
  8. tasklite_engine-1.0.0/tasklite/backend/base.py +199 -0
  9. tasklite_engine-1.0.0/tasklite/backend/sqlite_backend.py +570 -0
  10. tasklite_engine-1.0.0/tasklite/contrib/__init__.py +6 -0
  11. tasklite_engine-1.0.0/tasklite/engine/__init__.py +16 -0
  12. tasklite_engine-1.0.0/tasklite/engine/completion.py +439 -0
  13. tasklite_engine-1.0.0/tasklite/engine/deadlock.py +33 -0
  14. tasklite_engine-1.0.0/tasklite/engine/dispatch.py +426 -0
  15. tasklite_engine-1.0.0/tasklite/engine/executor.py +1202 -0
  16. tasklite_engine-1.0.0/tasklite/engine/failure.py +509 -0
  17. tasklite_engine-1.0.0/tasklite/engine/inflight.py +25 -0
  18. tasklite_engine-1.0.0/tasklite/engine/loop.py +281 -0
  19. tasklite_engine-1.0.0/tasklite/engine/recovery.py +409 -0
  20. tasklite_engine-1.0.0/tasklite/engine/resource.py +240 -0
  21. tasklite_engine-1.0.0/tasklite/engine/retry.py +147 -0
  22. tasklite_engine-1.0.0/tasklite/engine/runtime.py +331 -0
  23. tasklite_engine-1.0.0/tasklite/engine/scheduler.py +346 -0
  24. tasklite_engine-1.0.0/tasklite/error_codes.py +66 -0
  25. tasklite_engine-1.0.0/tasklite/exceptions.py +215 -0
  26. tasklite_engine-1.0.0/tasklite/models/__init__.py +6 -0
  27. tasklite_engine-1.0.0/tasklite/models/context.py +313 -0
  28. tasklite_engine-1.0.0/tasklite/models/job.py +304 -0
  29. tasklite_engine-1.0.0/tasklite/models/state.py +413 -0
  30. tasklite_engine-1.0.0/tasklite/pipeline.py +914 -0
  31. tasklite_engine-1.0.0/tasklite/pipeline_util.py +204 -0
  32. tasklite_engine-1.0.0/tasklite/py.typed +0 -0
  33. tasklite_engine-1.0.0/tasklite/utils/__init__.py +6 -0
  34. tasklite_engine-1.0.0/tasklite/utils/ipc.py +88 -0
  35. tasklite_engine-1.0.0/tasklite/utils/jsonutil.py +65 -0
  36. tasklite_engine-1.0.0/tasklite/utils/lockfile.py +154 -0
  37. tasklite_engine-1.0.0/tasklite/utils/validation.py +177 -0
  38. tasklite_engine-1.0.0/tasklite/wrappers/__init__.py +26 -0
  39. tasklite_engine-1.0.0/tasklite/wrappers/discovery.py +620 -0
  40. tasklite_engine-1.0.0/tasklite_engine.egg-info/PKG-INFO +321 -0
  41. tasklite_engine-1.0.0/tasklite_engine.egg-info/SOURCES.txt +42 -0
  42. tasklite_engine-1.0.0/tasklite_engine.egg-info/dependency_links.txt +1 -0
  43. tasklite_engine-1.0.0/tasklite_engine.egg-info/requires.txt +7 -0
  44. tasklite_engine-1.0.0/tasklite_engine.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TaskLite 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,321 @@
1
+ Metadata-Version: 2.4
2
+ Name: tasklite-engine
3
+ Version: 1.0.0
4
+ Summary: A lightweight task orchestration engine with zero external dependencies, process isolation, and ACID persistence
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Provides-Extra: dev
10
+ Requires-Dist: pytest; extra == "dev"
11
+ Requires-Dist: hypothesis>=6.0; extra == "dev"
12
+ Requires-Dist: mutmut>=3.0; extra == "dev"
13
+ Requires-Dist: pytest-timeout>=2.0; extra == "dev"
14
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
15
+ Dynamic: license-file
16
+
17
+ # ⚡ TaskLite — 零依赖、进程隔离的轻量任务编排引擎
18
+
19
+ > **A Lightweight Task Orchestration Engine for Agent & Batch Pipelines.**
20
+ > 专为 **AI Agent 批量处理**、数据同步与采集、音视频转码、离线 ETL 与后台批处理任务打造。
21
+ > 核心解决「单线程处理耗时长」、「中途失败需全量重跑」、「子任务卡死拖垮主流程」等工程痛点。采用类似 **systemd** 的守护与物理进程隔离架构,内置 SQLite WAL 事务持久化,子进程崩溃或断电不坏库,重启自动断点续跑。
22
+
23
+ [![Python Version](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/)
24
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
25
+ [![Zero Dependencies](https://img.shields.io/badge/dependencies-0%20external-brightgreen.svg)]()
26
+ [![ACID Persistence](https://img.shields.io/badge/persistence-SQLite%20WAL%20(ACID)-orange.svg)]()
27
+ [![Tests](https://img.shields.io/badge/tests-1160%2B%20passed-success.svg)]()
28
+
29
+ ---
30
+
31
+ ## 💡 为什么选择 TaskLite?
32
+
33
+ 在运行大量并发任务(如 AI Agent 批量调用、大模型推理批处理、多媒体文件处理、离线数据流转)时,常见方案常面临以下痛点:
34
+
35
+ - **单脚本串行或简单循环**:
36
+ - ❌ 单线程处理速度慢、耗时长;
37
+ - ❌ 跑上千个任务时,第 800 个报错导致整个程序终止,缺乏断点记录,下次必须**全量重跑**。
38
+ - **标准库进程池 (`multiprocessing.Pool` / `ProcessPoolExecutor`)**:
39
+ - ❌ 缺乏内置状态持久化,程序中断后已完成进度丢失;
40
+ - ❌ 子任务发生 C 扩展段错误 (SIGSEGV) 或内存泄露 (OOM) 时,容易导致主进程一同崩溃;
41
+ - ❌ 缺少全局速率限制与服务熔断机制,缺乏 DAG 拓扑依赖与死信隔离。
42
+ - **重型分布式任务队列 (`Celery` / `Airflow` / `Temporal`)**:
43
+ - ❌ 强依赖 Redis、RabbitMQ、PostgreSQL 等外部服务,本地单机运行或轻量嵌入脚本时运维成本过高;
44
+ - ❌ 架构较重,不适合作为随用随走的轻量批处理脚手架。
45
+
46
+ **TaskLite 的设计定位**:
47
+ **零依赖的单机批处理脚手架**——只需 `pip install tasklite-engine`。
48
+ 1. **多 Worker 并发加速**:多进程并行执行,充分利用多核算力,彻底解决单线程耗时长的问题;
49
+ 2. **断点续跑防重跑**:以 SQLite WAL 事务表记录状态,已完成的任务自动跳过,失败任务进入死信队列或退避重试,杜绝因个别失败导致全量重跑;
50
+ 3. **物理隔离与容灾**:主进程只负责调度,业务逻辑在独立子进程中运行并设超时强杀,即使子任务崩溃、死锁或系统断电,状态依然完整可恢复。
51
+
52
+ ---
53
+
54
+ ## 📊 方案对比矩阵
55
+
56
+ | 特性维度 | TaskLite | ProcessPoolExecutor | Celery | Airflow / Prefect |
57
+ | :--- | :---: | :---: | :---: | :---: |
58
+ | **外部依赖** | **零依赖 (标准库+SQLite)** | 零依赖 | 需 Redis / RabbitMQ | 需 DB + Web Server |
59
+ | **物理进程隔离** | **独立 spawn 子进程 + SIGKILL** | 进程池复用(易污染) | Worker 进程池 | 依赖 Runner 调度 |
60
+ | **子进程异常隔离** | **主进程不崩溃,支持自动重试** | 进程退出 | Worker 重启(易丢任务) | 需外部重启 |
61
+ | **状态持久化** | **SQLite WAL ACID 事务** | 无持久化 | 依赖 Broker 配置 | 数据库记录 |
62
+ | **抗漂移增量探索 (Discovery)** | **内置函数式无游标去重** | 无 | 无 | 需自定义传感器 |
63
+ | **全局限速与持久化休眠** | **内置令牌桶 + 跨重启持久化** | 无 | 需 Redis 限流组件 | 需复杂调度配置 |
64
+ | **DAG 依赖与级联熔断** | **内置拓扑依赖 + 自动跳过** | 无 | 需 Canvas 复杂编排 | 原生支持 |
65
+ | **产物沙盒与失败自动清理** | **内置 declare_output** | 无 | 无 | 需自定义 Hook |
66
+
67
+ ---
68
+
69
+ ## 🏗️ 核心架构与设计哲学
70
+
71
+ ```mermaid
72
+ flowchart TD
73
+ subgraph Host ["主调度进程 (Master Scheduler) - 永不运行业务 Handler"]
74
+ State[("SQLite WAL 状态机\n(Wall / Queue / Failed / In-Flight)")]
75
+ Scheduler["JobScheduler (资源调度 / 拓扑依赖)"]
76
+ Watchdog["进程看门狗 (超时 SIGKILL / 孤儿锁探测)"]
77
+ end
78
+
79
+ subgraph Workers ["独立 Worker 子进程 (Isolated Subprocesses)"]
80
+ W1["Worker 1 (Handler)"]
81
+ W2["Worker 2 (Handler)"]
82
+ WN["Worker N (Handler)"]
83
+ end
84
+
85
+ Scheduler -->|"1. 检查资源 & 拓扑依赖"| State
86
+ Scheduler -->|"2. Spawn 独立子进程 (隔离)"| Workers
87
+ Workers -.->|"3. 原子落盘结果/信号 (.result.json)"| State
88
+ Watchdog -->|"4. 轮询超时 SIGKILL 强杀"| Workers
89
+ State -->|"5. BEGIN IMMEDIATE 原子收尾"| State
90
+ ```
91
+
92
+ ### 三层崩溃恢复防线
93
+ 1. **执行代标识 (Generation Tag)**:结果文件携带 `{uid}.{run_id}.{seq}`,前次崩溃的孤儿进程结果对新 Run 不可见;
94
+ 2. **启动期残留消费**:启动时自动识别并提交崩溃前已落盘的有效结果,不重复执行;
95
+ 3. **孤儿文件锁探测 (Lockfile Fencing)**:严格保证同一 UID 在全局只有一个物理执行体。
96
+
97
+ ---
98
+
99
+ ## 🌟 核心特性
100
+
101
+ ### 1. 🛡️ 进程隔离与守护 (Process Isolation)
102
+ - 主进程作为总控调度器,不执行具体的业务代码;
103
+ - 每一个任务均在独立的 `spawn` 子进程中隔离执行;
104
+ - 当子进程发生死循环或超时,看门狗将在指定超时后发送 `SIGKILL` 强杀子进程并自动回收资源;
105
+ - 子进程发生异常退出或被操作系统终止时,不影响主调度进程的稳定运行。
106
+
107
+ ### 2. ⚡ SQLite WAL 状态持久化 (ACID)
108
+ - 基于 SQLite WAL + `synchronous=FULL` 模式;
109
+ - 四集合状态机(Wall 已完成 / Queue 待办 / In-Flight 在途 / Failed 死信)在单次事务中原子转移;
110
+ - 遭遇中断或异常关机时,重启后支持 At-Least-Once 接续执行。
111
+
112
+ ### 3. 🔍 函数式增量探索 (Functional Discovery)
113
+ - 针对数据同步、采集与拉取场景的无游标 (Cursor-less) 增量扫描;
114
+ - 直接复用已完成集合 (Wall) 即时判定已见内容,整页命中自动刹车,天然免疫上游记录删除、插入导致的游标漂移。
115
+
116
+ ### 4. ⏸️ 全局限流与配额熔断持久化休眠
117
+ - 内置 `RateLimitResource`(速率限制)与 `CapacityResource`(并发限制);
118
+ - 遇到 HTTP 429 速率限制或配额超限时,调用 `ctx.suspend_resource("api", 3600)` 挂起全管线,休眠状态跨进程、跨重启持久化。
119
+
120
+ ### 5. 🔗 DAG 拓扑依赖与级联熔断
121
+ - 任务通过 `depends_on` 声明依赖;
122
+ - 父任务失败进入死信队列时,所有下游依赖自动标记跳过 (`JOB_DEPENDENCY`),避免无效计算。
123
+
124
+ ### 6. 📦 产物沙盒与自动清理
125
+ - `ctx.declare_output()` 声明产出文件,内置路径遍历防御 (`../` 拒绝);
126
+ - 任务成功时校验物理产出完整性,任务失败时自动清理半成品文件,避免残留。
127
+
128
+ ---
129
+
130
+ ## 🚀 快速上手 (Quickstart)
131
+
132
+ ### 安装
133
+
134
+ 要求 **Python ≥ 3.9**,零外部依赖:
135
+
136
+ ```bash
137
+ pip install tasklite-engine
138
+ ```
139
+
140
+ ### 最小示例
141
+
142
+ ```python
143
+ import logging
144
+ from tasklite import TaskLite, Job, RetryError, RateLimitResource
145
+
146
+ logging.getLogger("tasklite").setLevel(logging.INFO)
147
+
148
+ # 1. 定义任务处理器(在独立子进程中执行)
149
+ def download_handler(job: Job, ctx):
150
+ # 声明产出文件(失败时自动清理半残文件)
151
+ out = ctx.declare_output(f"./downloads/{job.job_id}.jpg", cleanup_on_fail=True)
152
+
153
+ # 模拟业务下载
154
+ success = do_download(job.payload["url"], out)
155
+ if not success:
156
+ raise RetryError("网络抖动,触发指数退避重试")
157
+
158
+ return True, {"size": 1024, "path": str(out)}
159
+
160
+ # 2. 初始化引擎与配置资源
161
+ pipeline = TaskLite(
162
+ name="media_downloader",
163
+ state_dir="./state", # 状态持久化目录(SQLite WAL)
164
+ output_root="./downloads", # 产物沙盒根目录
165
+ max_workers=4, # 并发子进程数
166
+ )
167
+
168
+ # 3. 注册全局限速(每秒最多 2 次请求)与处理器
169
+ pipeline.add_resource(RateLimitResource("api", interval_seconds=0.5))
170
+ pipeline.register_handler("download", download_handler, default_resources={"api": 1.0})
171
+
172
+ # 4. 入队并启动(阻塞直到队列全部完成)
173
+ pipeline.enqueue([
174
+ Job("download", "img_001", payload={"url": "https://example.com/1.jpg"}),
175
+ Job("download", "img_002", payload={"url": "https://example.com/2.jpg"}),
176
+ ])
177
+ pipeline.run()
178
+ ```
179
+
180
+ ---
181
+
182
+ ## 🍳 实战 Recipes
183
+
184
+ ### Recipe 1: 增量分页扫描器 (Discovery)
185
+
186
+ 无需游标,增量同步时只要发现整页内容已全部处理过,自动刹车停止扫描:
187
+
188
+ ```python
189
+ from tasklite.wrappers.discovery import register_discovery
190
+
191
+ def fetch_page(job, ctx, page: int):
192
+ # 从第 1 页开始逐页获取列表,返回空列表表示翻页结束
193
+ return api.get_author_posts(author_id=job.payload["author_id"], page=page)
194
+
195
+ def item_id(post) -> str:
196
+ return str(post["id"]) # 唯一内容 ID
197
+
198
+ def process_item(job, ctx, post, content_id: str):
199
+ # 发现新内容时派生子任务(content_id 已做单射净化)
200
+ ctx.spawn(Job("download", content_id, payload={"url": post["url"]}))
201
+
202
+ # 注册增量探索
203
+ register_discovery(
204
+ pipeline,
205
+ task_type="discover_author",
206
+ fetch_func=fetch_page,
207
+ id_func=item_id,
208
+ process_item_func=process_item,
209
+ process_task_type="download",
210
+ default_resources={"api": 1.0},
211
+ )
212
+ ```
213
+
214
+ ### Recipe 2: 视频转码与 DAG 依赖级联
215
+
216
+ 下载完成后自动触发转码,下载若失败则自动跳过转码:
217
+
218
+ ```python
219
+ # 构造具有拓扑依赖的任务链
220
+ download_job = Job("download", "video_101", payload={"url": "https://example.com/v.mp4"})
221
+ transcode_job = Job(
222
+ "transcode",
223
+ "video_101",
224
+ payload={"preset": "1080p"},
225
+ depends_on=[download_job.uid], # 依赖 download::video_101
226
+ )
227
+
228
+ pipeline.enqueue([download_job, transcode_job])
229
+ pipeline.run()
230
+ ```
231
+
232
+ ### Recipe 3: 遭遇 429 限流与配额熔断全局休眠
233
+
234
+ ```python
235
+ from tasklite import RateLimitHit
236
+
237
+ def fetch_handler(job, ctx):
238
+ resp = requests.get(job.payload["url"])
239
+ if resp.status_code == 429:
240
+ # 全管线挂起 api 资源 1 小时,状态持久化到磁盘,随后抛出退避重试
241
+ ctx.suspend_resource("api", seconds=3600)
242
+ raise RateLimitHit("Triggered 429, suspending API for 1h")
243
+ return True, resp.json()
244
+ ```
245
+
246
+ ### Recipe 4: AI Agent / 大模型批量任务断点续跑
247
+
248
+ 批量调用大模型处理数千条文本,多进程并发加速 + 速率控制 + 自动断点续跑:
249
+
250
+ ```python
251
+ import json
252
+ from tasklite import TaskLite, Job, RateLimitResource
253
+
254
+ def agent_worker(job: Job, ctx):
255
+ # 模拟 Agent 分析并生成结果
256
+ prompt = job.payload["prompt"]
257
+ result = call_llm(prompt)
258
+
259
+ # 声明产物,失败时自动清理
260
+ out_file = ctx.declare_output(f"./results/{job.job_id}.json", cleanup_on_fail=True)
261
+ out_file.write_text(json.dumps(result, ensure_ascii=False))
262
+ return True, {"tokens": result.get("usage", 0)}
263
+
264
+ pipeline = TaskLite(name="agent_batch", state_dir="./agent_state", max_workers=8)
265
+ pipeline.add_resource(RateLimitResource("llm_rpm", interval_seconds=0.1))
266
+ pipeline.register_handler("analyze", agent_worker, default_resources={"llm_rpm": 1.0})
267
+
268
+ # 入队海量任务,中途即使断网或意外中断,重启后自动跳过已完成任务接续执行
269
+ pipeline.enqueue([
270
+ Job("analyze", f"doc_{i}", payload={"prompt": f"分析文本 {i}"})
271
+ for i in range(1000)
272
+ ])
273
+ pipeline.run()
274
+ ```
275
+
276
+ ---
277
+
278
+ ## 🛠️ 死信队列与运维 (DLQ & Ops)
279
+
280
+ 当任务重试超限或发生致命错误时,任务进入持久化死信队列 (DLQ)。可以通过标准 API 检查与清理:
281
+
282
+ ```python
283
+ # 1. 检查死信详情
284
+ for entry in pipeline.list_dlq():
285
+ print(f"UID: {entry.uid}, 错误类型: {entry.error_type}, 原因: {entry.error}")
286
+
287
+ # 2. 清理指定类型的 DLQ(默认保留 fatal 错误,可入队同名任务重跑)
288
+ pipeline.clear_dlq(task_types=["download"], keep_fatal=False)
289
+
290
+ # 3. 精确或前缀清理历史(wall / failed)
291
+ pipeline.clear_history("download::")
292
+ ```
293
+
294
+ ---
295
+
296
+ ## 📁 目录结构
297
+
298
+ ```
299
+ tasklite/
300
+ ├── pipeline.py # TaskLite / TaskLite 核心门面与配置
301
+ ├── engine/ # 核心执行机器群
302
+ │ ├── loop.py # 主调度事件循环 (LoopRunner)
303
+ │ ├── dispatch.py # 任务派发状态机 (DispatchMachine)
304
+ │ ├── completion.py # 任务完成与提交 (CompletionMachine)
305
+ │ ├── recovery.py # 崩溃检测与恢复 (RecoveryMachine)
306
+ │ ├── failure.py # 失败收敛与死信归因 (FailureMachine)
307
+ │ ├── scheduler.py # 资源调度与 DAG 依赖 (JobScheduler)
308
+ │ ├── executor.py # 子进程隔离执行器与看门狗
309
+ │ └── resource.py # 令牌桶限速与并发容量资源
310
+ ├── backend/ # SQLite WAL 强一致事务持久化后端
311
+ ├── models/ # Job / TaskContext / PipelineState 数据模型
312
+ ├── wrappers/ # discovery.py(增量扫描封装)
313
+ ├── pipeline_util.py # 通用脚手架(任务指纹 / 进度钩子 / 瞬态错误注册)
314
+ └── utils/ # jsonutil (禁NaN) / lockfile / validation
315
+ ```
316
+
317
+ ---
318
+
319
+ ## 📄 开源许可证
320
+
321
+ 本项目基于 [MIT License](LICENSE) 开源。
@@ -0,0 +1,305 @@
1
+ # ⚡ TaskLite — 零依赖、进程隔离的轻量任务编排引擎
2
+
3
+ > **A Lightweight Task Orchestration Engine for Agent & Batch Pipelines.**
4
+ > 专为 **AI Agent 批量处理**、数据同步与采集、音视频转码、离线 ETL 与后台批处理任务打造。
5
+ > 核心解决「单线程处理耗时长」、「中途失败需全量重跑」、「子任务卡死拖垮主流程」等工程痛点。采用类似 **systemd** 的守护与物理进程隔离架构,内置 SQLite WAL 事务持久化,子进程崩溃或断电不坏库,重启自动断点续跑。
6
+
7
+ [![Python Version](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
9
+ [![Zero Dependencies](https://img.shields.io/badge/dependencies-0%20external-brightgreen.svg)]()
10
+ [![ACID Persistence](https://img.shields.io/badge/persistence-SQLite%20WAL%20(ACID)-orange.svg)]()
11
+ [![Tests](https://img.shields.io/badge/tests-1160%2B%20passed-success.svg)]()
12
+
13
+ ---
14
+
15
+ ## 💡 为什么选择 TaskLite?
16
+
17
+ 在运行大量并发任务(如 AI Agent 批量调用、大模型推理批处理、多媒体文件处理、离线数据流转)时,常见方案常面临以下痛点:
18
+
19
+ - **单脚本串行或简单循环**:
20
+ - ❌ 单线程处理速度慢、耗时长;
21
+ - ❌ 跑上千个任务时,第 800 个报错导致整个程序终止,缺乏断点记录,下次必须**全量重跑**。
22
+ - **标准库进程池 (`multiprocessing.Pool` / `ProcessPoolExecutor`)**:
23
+ - ❌ 缺乏内置状态持久化,程序中断后已完成进度丢失;
24
+ - ❌ 子任务发生 C 扩展段错误 (SIGSEGV) 或内存泄露 (OOM) 时,容易导致主进程一同崩溃;
25
+ - ❌ 缺少全局速率限制与服务熔断机制,缺乏 DAG 拓扑依赖与死信隔离。
26
+ - **重型分布式任务队列 (`Celery` / `Airflow` / `Temporal`)**:
27
+ - ❌ 强依赖 Redis、RabbitMQ、PostgreSQL 等外部服务,本地单机运行或轻量嵌入脚本时运维成本过高;
28
+ - ❌ 架构较重,不适合作为随用随走的轻量批处理脚手架。
29
+
30
+ **TaskLite 的设计定位**:
31
+ **零依赖的单机批处理脚手架**——只需 `pip install tasklite-engine`。
32
+ 1. **多 Worker 并发加速**:多进程并行执行,充分利用多核算力,彻底解决单线程耗时长的问题;
33
+ 2. **断点续跑防重跑**:以 SQLite WAL 事务表记录状态,已完成的任务自动跳过,失败任务进入死信队列或退避重试,杜绝因个别失败导致全量重跑;
34
+ 3. **物理隔离与容灾**:主进程只负责调度,业务逻辑在独立子进程中运行并设超时强杀,即使子任务崩溃、死锁或系统断电,状态依然完整可恢复。
35
+
36
+ ---
37
+
38
+ ## 📊 方案对比矩阵
39
+
40
+ | 特性维度 | TaskLite | ProcessPoolExecutor | Celery | Airflow / Prefect |
41
+ | :--- | :---: | :---: | :---: | :---: |
42
+ | **外部依赖** | **零依赖 (标准库+SQLite)** | 零依赖 | 需 Redis / RabbitMQ | 需 DB + Web Server |
43
+ | **物理进程隔离** | **独立 spawn 子进程 + SIGKILL** | 进程池复用(易污染) | Worker 进程池 | 依赖 Runner 调度 |
44
+ | **子进程异常隔离** | **主进程不崩溃,支持自动重试** | 进程退出 | Worker 重启(易丢任务) | 需外部重启 |
45
+ | **状态持久化** | **SQLite WAL ACID 事务** | 无持久化 | 依赖 Broker 配置 | 数据库记录 |
46
+ | **抗漂移增量探索 (Discovery)** | **内置函数式无游标去重** | 无 | 无 | 需自定义传感器 |
47
+ | **全局限速与持久化休眠** | **内置令牌桶 + 跨重启持久化** | 无 | 需 Redis 限流组件 | 需复杂调度配置 |
48
+ | **DAG 依赖与级联熔断** | **内置拓扑依赖 + 自动跳过** | 无 | 需 Canvas 复杂编排 | 原生支持 |
49
+ | **产物沙盒与失败自动清理** | **内置 declare_output** | 无 | 无 | 需自定义 Hook |
50
+
51
+ ---
52
+
53
+ ## 🏗️ 核心架构与设计哲学
54
+
55
+ ```mermaid
56
+ flowchart TD
57
+ subgraph Host ["主调度进程 (Master Scheduler) - 永不运行业务 Handler"]
58
+ State[("SQLite WAL 状态机\n(Wall / Queue / Failed / In-Flight)")]
59
+ Scheduler["JobScheduler (资源调度 / 拓扑依赖)"]
60
+ Watchdog["进程看门狗 (超时 SIGKILL / 孤儿锁探测)"]
61
+ end
62
+
63
+ subgraph Workers ["独立 Worker 子进程 (Isolated Subprocesses)"]
64
+ W1["Worker 1 (Handler)"]
65
+ W2["Worker 2 (Handler)"]
66
+ WN["Worker N (Handler)"]
67
+ end
68
+
69
+ Scheduler -->|"1. 检查资源 & 拓扑依赖"| State
70
+ Scheduler -->|"2. Spawn 独立子进程 (隔离)"| Workers
71
+ Workers -.->|"3. 原子落盘结果/信号 (.result.json)"| State
72
+ Watchdog -->|"4. 轮询超时 SIGKILL 强杀"| Workers
73
+ State -->|"5. BEGIN IMMEDIATE 原子收尾"| State
74
+ ```
75
+
76
+ ### 三层崩溃恢复防线
77
+ 1. **执行代标识 (Generation Tag)**:结果文件携带 `{uid}.{run_id}.{seq}`,前次崩溃的孤儿进程结果对新 Run 不可见;
78
+ 2. **启动期残留消费**:启动时自动识别并提交崩溃前已落盘的有效结果,不重复执行;
79
+ 3. **孤儿文件锁探测 (Lockfile Fencing)**:严格保证同一 UID 在全局只有一个物理执行体。
80
+
81
+ ---
82
+
83
+ ## 🌟 核心特性
84
+
85
+ ### 1. 🛡️ 进程隔离与守护 (Process Isolation)
86
+ - 主进程作为总控调度器,不执行具体的业务代码;
87
+ - 每一个任务均在独立的 `spawn` 子进程中隔离执行;
88
+ - 当子进程发生死循环或超时,看门狗将在指定超时后发送 `SIGKILL` 强杀子进程并自动回收资源;
89
+ - 子进程发生异常退出或被操作系统终止时,不影响主调度进程的稳定运行。
90
+
91
+ ### 2. ⚡ SQLite WAL 状态持久化 (ACID)
92
+ - 基于 SQLite WAL + `synchronous=FULL` 模式;
93
+ - 四集合状态机(Wall 已完成 / Queue 待办 / In-Flight 在途 / Failed 死信)在单次事务中原子转移;
94
+ - 遭遇中断或异常关机时,重启后支持 At-Least-Once 接续执行。
95
+
96
+ ### 3. 🔍 函数式增量探索 (Functional Discovery)
97
+ - 针对数据同步、采集与拉取场景的无游标 (Cursor-less) 增量扫描;
98
+ - 直接复用已完成集合 (Wall) 即时判定已见内容,整页命中自动刹车,天然免疫上游记录删除、插入导致的游标漂移。
99
+
100
+ ### 4. ⏸️ 全局限流与配额熔断持久化休眠
101
+ - 内置 `RateLimitResource`(速率限制)与 `CapacityResource`(并发限制);
102
+ - 遇到 HTTP 429 速率限制或配额超限时,调用 `ctx.suspend_resource("api", 3600)` 挂起全管线,休眠状态跨进程、跨重启持久化。
103
+
104
+ ### 5. 🔗 DAG 拓扑依赖与级联熔断
105
+ - 任务通过 `depends_on` 声明依赖;
106
+ - 父任务失败进入死信队列时,所有下游依赖自动标记跳过 (`JOB_DEPENDENCY`),避免无效计算。
107
+
108
+ ### 6. 📦 产物沙盒与自动清理
109
+ - `ctx.declare_output()` 声明产出文件,内置路径遍历防御 (`../` 拒绝);
110
+ - 任务成功时校验物理产出完整性,任务失败时自动清理半成品文件,避免残留。
111
+
112
+ ---
113
+
114
+ ## 🚀 快速上手 (Quickstart)
115
+
116
+ ### 安装
117
+
118
+ 要求 **Python ≥ 3.9**,零外部依赖:
119
+
120
+ ```bash
121
+ pip install tasklite-engine
122
+ ```
123
+
124
+ ### 最小示例
125
+
126
+ ```python
127
+ import logging
128
+ from tasklite import TaskLite, Job, RetryError, RateLimitResource
129
+
130
+ logging.getLogger("tasklite").setLevel(logging.INFO)
131
+
132
+ # 1. 定义任务处理器(在独立子进程中执行)
133
+ def download_handler(job: Job, ctx):
134
+ # 声明产出文件(失败时自动清理半残文件)
135
+ out = ctx.declare_output(f"./downloads/{job.job_id}.jpg", cleanup_on_fail=True)
136
+
137
+ # 模拟业务下载
138
+ success = do_download(job.payload["url"], out)
139
+ if not success:
140
+ raise RetryError("网络抖动,触发指数退避重试")
141
+
142
+ return True, {"size": 1024, "path": str(out)}
143
+
144
+ # 2. 初始化引擎与配置资源
145
+ pipeline = TaskLite(
146
+ name="media_downloader",
147
+ state_dir="./state", # 状态持久化目录(SQLite WAL)
148
+ output_root="./downloads", # 产物沙盒根目录
149
+ max_workers=4, # 并发子进程数
150
+ )
151
+
152
+ # 3. 注册全局限速(每秒最多 2 次请求)与处理器
153
+ pipeline.add_resource(RateLimitResource("api", interval_seconds=0.5))
154
+ pipeline.register_handler("download", download_handler, default_resources={"api": 1.0})
155
+
156
+ # 4. 入队并启动(阻塞直到队列全部完成)
157
+ pipeline.enqueue([
158
+ Job("download", "img_001", payload={"url": "https://example.com/1.jpg"}),
159
+ Job("download", "img_002", payload={"url": "https://example.com/2.jpg"}),
160
+ ])
161
+ pipeline.run()
162
+ ```
163
+
164
+ ---
165
+
166
+ ## 🍳 实战 Recipes
167
+
168
+ ### Recipe 1: 增量分页扫描器 (Discovery)
169
+
170
+ 无需游标,增量同步时只要发现整页内容已全部处理过,自动刹车停止扫描:
171
+
172
+ ```python
173
+ from tasklite.wrappers.discovery import register_discovery
174
+
175
+ def fetch_page(job, ctx, page: int):
176
+ # 从第 1 页开始逐页获取列表,返回空列表表示翻页结束
177
+ return api.get_author_posts(author_id=job.payload["author_id"], page=page)
178
+
179
+ def item_id(post) -> str:
180
+ return str(post["id"]) # 唯一内容 ID
181
+
182
+ def process_item(job, ctx, post, content_id: str):
183
+ # 发现新内容时派生子任务(content_id 已做单射净化)
184
+ ctx.spawn(Job("download", content_id, payload={"url": post["url"]}))
185
+
186
+ # 注册增量探索
187
+ register_discovery(
188
+ pipeline,
189
+ task_type="discover_author",
190
+ fetch_func=fetch_page,
191
+ id_func=item_id,
192
+ process_item_func=process_item,
193
+ process_task_type="download",
194
+ default_resources={"api": 1.0},
195
+ )
196
+ ```
197
+
198
+ ### Recipe 2: 视频转码与 DAG 依赖级联
199
+
200
+ 下载完成后自动触发转码,下载若失败则自动跳过转码:
201
+
202
+ ```python
203
+ # 构造具有拓扑依赖的任务链
204
+ download_job = Job("download", "video_101", payload={"url": "https://example.com/v.mp4"})
205
+ transcode_job = Job(
206
+ "transcode",
207
+ "video_101",
208
+ payload={"preset": "1080p"},
209
+ depends_on=[download_job.uid], # 依赖 download::video_101
210
+ )
211
+
212
+ pipeline.enqueue([download_job, transcode_job])
213
+ pipeline.run()
214
+ ```
215
+
216
+ ### Recipe 3: 遭遇 429 限流与配额熔断全局休眠
217
+
218
+ ```python
219
+ from tasklite import RateLimitHit
220
+
221
+ def fetch_handler(job, ctx):
222
+ resp = requests.get(job.payload["url"])
223
+ if resp.status_code == 429:
224
+ # 全管线挂起 api 资源 1 小时,状态持久化到磁盘,随后抛出退避重试
225
+ ctx.suspend_resource("api", seconds=3600)
226
+ raise RateLimitHit("Triggered 429, suspending API for 1h")
227
+ return True, resp.json()
228
+ ```
229
+
230
+ ### Recipe 4: AI Agent / 大模型批量任务断点续跑
231
+
232
+ 批量调用大模型处理数千条文本,多进程并发加速 + 速率控制 + 自动断点续跑:
233
+
234
+ ```python
235
+ import json
236
+ from tasklite import TaskLite, Job, RateLimitResource
237
+
238
+ def agent_worker(job: Job, ctx):
239
+ # 模拟 Agent 分析并生成结果
240
+ prompt = job.payload["prompt"]
241
+ result = call_llm(prompt)
242
+
243
+ # 声明产物,失败时自动清理
244
+ out_file = ctx.declare_output(f"./results/{job.job_id}.json", cleanup_on_fail=True)
245
+ out_file.write_text(json.dumps(result, ensure_ascii=False))
246
+ return True, {"tokens": result.get("usage", 0)}
247
+
248
+ pipeline = TaskLite(name="agent_batch", state_dir="./agent_state", max_workers=8)
249
+ pipeline.add_resource(RateLimitResource("llm_rpm", interval_seconds=0.1))
250
+ pipeline.register_handler("analyze", agent_worker, default_resources={"llm_rpm": 1.0})
251
+
252
+ # 入队海量任务,中途即使断网或意外中断,重启后自动跳过已完成任务接续执行
253
+ pipeline.enqueue([
254
+ Job("analyze", f"doc_{i}", payload={"prompt": f"分析文本 {i}"})
255
+ for i in range(1000)
256
+ ])
257
+ pipeline.run()
258
+ ```
259
+
260
+ ---
261
+
262
+ ## 🛠️ 死信队列与运维 (DLQ & Ops)
263
+
264
+ 当任务重试超限或发生致命错误时,任务进入持久化死信队列 (DLQ)。可以通过标准 API 检查与清理:
265
+
266
+ ```python
267
+ # 1. 检查死信详情
268
+ for entry in pipeline.list_dlq():
269
+ print(f"UID: {entry.uid}, 错误类型: {entry.error_type}, 原因: {entry.error}")
270
+
271
+ # 2. 清理指定类型的 DLQ(默认保留 fatal 错误,可入队同名任务重跑)
272
+ pipeline.clear_dlq(task_types=["download"], keep_fatal=False)
273
+
274
+ # 3. 精确或前缀清理历史(wall / failed)
275
+ pipeline.clear_history("download::")
276
+ ```
277
+
278
+ ---
279
+
280
+ ## 📁 目录结构
281
+
282
+ ```
283
+ tasklite/
284
+ ├── pipeline.py # TaskLite / TaskLite 核心门面与配置
285
+ ├── engine/ # 核心执行机器群
286
+ │ ├── loop.py # 主调度事件循环 (LoopRunner)
287
+ │ ├── dispatch.py # 任务派发状态机 (DispatchMachine)
288
+ │ ├── completion.py # 任务完成与提交 (CompletionMachine)
289
+ │ ├── recovery.py # 崩溃检测与恢复 (RecoveryMachine)
290
+ │ ├── failure.py # 失败收敛与死信归因 (FailureMachine)
291
+ │ ├── scheduler.py # 资源调度与 DAG 依赖 (JobScheduler)
292
+ │ ├── executor.py # 子进程隔离执行器与看门狗
293
+ │ └── resource.py # 令牌桶限速与并发容量资源
294
+ ├── backend/ # SQLite WAL 强一致事务持久化后端
295
+ ├── models/ # Job / TaskContext / PipelineState 数据模型
296
+ ├── wrappers/ # discovery.py(增量扫描封装)
297
+ ├── pipeline_util.py # 通用脚手架(任务指纹 / 进度钩子 / 瞬态错误注册)
298
+ └── utils/ # jsonutil (禁NaN) / lockfile / validation
299
+ ```
300
+
301
+ ---
302
+
303
+ ## 📄 开源许可证
304
+
305
+ 本项目基于 [MIT License](LICENSE) 开源。