"""示例 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()