Files
chongming-utils-task/examples/06_cross_process.py
T

92 lines
3.6 KiB
Python
Raw Normal View History

"""示例 6:跨进程 —— 一个进程跑任务,另一个进程查询与控制。
这里用**同一个 SQLite 文件上的两个独立连接**模拟两个进程。真正的两个进程行为完全
一样:它们看不到彼此的内存,只能通过存储交换状态与指令。
运行::
uv run python examples/06_cross_process.py
"""
from __future__ import annotations
import tempfile
import time
from pathlib import Path
from chongming_task import Controller, SqliteTaskStore, render_table
def sync_tenant(ctx, tenant: str) -> str:
"""稳步推进直到被停止。"""
for _ in range(300):
ctx.checkpoint()
time.sleep(0.02)
return f"{tenant} 同步完成"
def _wait_for_status(client: Controller, task_id: str, wanted: str, timeout: float = 20.0) -> bool:
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
snapshot = client.get(task_id)
if snapshot is not None and snapshot.status.value == wanted:
return True
time.sleep(0.02)
return False
def main() -> None:
with tempfile.TemporaryDirectory(prefix="chongming-demo-") as directory:
path = Path(directory) / "tasks.db"
# ---- 进程 A:真正跑任务的 worker ----
worker = Controller(
store=SqliteTaskStore(path),
max_parallel=2,
command_poll_interval=0.1,
owner="worker-1",
)
worker.register("sync", sync_tenant)
worker.submit("sync", "租户A", task_id="job-1", label="A-同步", bucket_key="tenant-A")
worker.submit("sync", "租户B", task_id="job-2", label="B-同步", bucket_key="tenant-B")
print("worker-1 已提交 2 个任务(并发上限 2)\n")
time.sleep(0.5)
# ---- 进程 B:管理客户端,只查询与控制,不执行任何任务 ----
client = Controller(store=SqliteTaskStore(path), owner="ops-console")
print("--- ops-console 看到的状态(它并不持有这些任务)---")
print(render_table(client.list_tasks()))
print(f" 它本地的任务句柄数:{len(client.runs())}")
print(f" 统计:{ {s.value: n for s, n in client.stats().by_status.items()} }")
print("\n>>> ops-console 跨进程停止 job-1")
print(f" 下发结果:{client.stop('job-1', reason='运维从后台停止')}")
print(" (客户端无法给另一个进程的线程发信号,只能写一条指令)")
if _wait_for_status(client, "job-1", "stopped"):
print(" worker-1 已取走指令并停止该任务")
else:
print(" !! 超时:指令没有被取走")
print("\n--- job-1 的迁移历史(从存储读,可审计)---")
for record in client.store.history("job-1"): # type: ignore[union-attr]
print(
f" {record.version:>2} {record.from_status.value:>10} -> "
f"{record.to_status.value:<10} by {record.trigger}"
)
print("\n--- 让 job-2 自己跑完,再看最终状态 ---")
worker.wait_all(timeout=30)
print(render_table(client.list_tasks()))
# 收尾:两个 Controller 各自关掉自己的存储连接。没关的 SQLite 连接会泄漏
# 文件句柄,在 Windows 上还会让数据库文件无法删除。
worker.close(timeout=10, close_store=True)
client.close(timeout=5, close_store=True)
print("\n要点:worker 负责执行,console 负责查询与控制,两者只通过存储耦合。")
if __name__ == "__main__":
main()