92 lines
3.6 KiB
Python
92 lines
3.6 KiB
Python
"""示例 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()
|