单任务受控执行(15 态状态机 + 审计轨迹)、DAG 管道、Controller 门面。
执行器三个:线程(默认,含 async def 任务)、内联(同步 / 测试)、进程
(spawn 子进程:硬性停止、绕开 GIL、崩溃隔离)。
调度:并发额度 + 每桶限流 + 具名资源信号量(按标签)+ 优先级排队。
持久层:TaskStore 契约 + 内存 / SQLite / Postgres 实现,契约测试随包发布。
跨进程与多节点:写穿 / 读穿、指令通道与租约、owned_buckets 划界、reconcile。
CLI、12 个可直接运行的示例、docs/ 下 5 份文档。
门禁:ruff / ruff format / mypy(strict) 干净;
pytest 563 passed(44 skipped 是需要真实 Postgres 的那批);
带一台可连的 Postgres 时 607 passed。
87 lines
2.4 KiB
Python
87 lines
2.4 KiB
Python
"""示例 3:重试与超时。
|
||
|
||
运行::
|
||
|
||
uv run python examples/03_retry_and_timeout.py
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import time
|
||
|
||
from chongming_task.policies import ExponentialBackoff, RetryPolicy, Timeout
|
||
from chongming_task.run import run_task
|
||
|
||
_attempts = 0
|
||
|
||
|
||
def flaky(ctx, succeed_on_attempt: int) -> str:
|
||
"""前几次故意失败,用来演示重试与退避。"""
|
||
global _attempts
|
||
_attempts += 1
|
||
if _attempts < succeed_on_attempt:
|
||
raise RuntimeError(f"第 {_attempts} 次尝试遇到暂时性故障")
|
||
return f"第 {_attempts} 次尝试成功"
|
||
|
||
|
||
def slow(ctx) -> str:
|
||
"""跑得很慢,用来演示超时后会被请求停止。"""
|
||
for _ in range(10_000):
|
||
ctx.checkpoint()
|
||
time.sleep(0.01)
|
||
return "跑完了"
|
||
|
||
|
||
def demo_retry() -> None:
|
||
print("== 重试:失败两次后成功 ==")
|
||
_attempts = 0
|
||
outcome = run_task(
|
||
flaky,
|
||
3,
|
||
retry=RetryPolicy(max_attempts=5, backoff=ExponentialBackoff(base=0.05, factor=2)),
|
||
task_id="demo-retry",
|
||
name="会抖的任务",
|
||
)
|
||
print(f" 终态 :{outcome.status.value}")
|
||
print(f" 返回值 :{outcome.value}")
|
||
print(f" 尝试次数 :{outcome.snapshot.attempt}/{outcome.snapshot.max_attempts}")
|
||
|
||
|
||
def demo_retry_exhausted() -> None:
|
||
print("\n== 重试:预算耗尽 ==")
|
||
global _attempts
|
||
_attempts = 0
|
||
outcome = run_task(
|
||
flaky,
|
||
99, # 永远不会成功
|
||
retry=RetryPolicy(max_attempts=2, backoff=ExponentialBackoff(base=0.05, factor=1)),
|
||
task_id="demo-exhausted",
|
||
name="必定失败",
|
||
)
|
||
print(f" 终态 :{outcome.status.value}")
|
||
print(f" 尝试次数 :{outcome.snapshot.attempt}/{outcome.snapshot.max_attempts}")
|
||
print(f" 失败归类 :{outcome.failure.kind.value if outcome.failure else '-'}")
|
||
|
||
|
||
def demo_timeout() -> None:
|
||
print("\n== 超时:单次尝试不超过 0.5 秒 ==")
|
||
outcome = run_task(
|
||
slow,
|
||
timeout=Timeout(per_attempt=0.5),
|
||
task_id="demo-timeout",
|
||
name="慢任务",
|
||
)
|
||
print(f" 终态 :{outcome.status.value}")
|
||
print(f" 失败归类 :{outcome.failure.kind.value if outcome.failure else '-'}")
|
||
print(f" 可重试 :{outcome.failure.retryable if outcome.failure else '-'}")
|
||
|
||
|
||
def main() -> None:
|
||
demo_retry()
|
||
demo_retry_exhausted()
|
||
demo_timeout()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|