Spaces:
Running
Running
File size: 4,740 Bytes
d646dd7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | """热加载 Worker — watchfiles 监听代码变化,自动重启全部 worker 进程。
worker 本身无热重载(`python -m worker` 启动时加载模块代码,改代码必须重启)。
本脚本与 hot_reload_backend.py 同模式:监听 app/worker/.env 文件变化 →
干净杀掉全部 worker → 重新启动,避免"改了代码 worker 还在跑旧逻辑"。
中断安全:worker 执行中被 kill 的任务,后端靠租约(默认 90s)到期后重新
放回队列,由重启后的 worker 重新领取 —— 任务不丢,最多从头重跑一遍。
用法:
python scripts/hot_reload_worker.py
(API Key 未设置时自动从项目 .env 读 WORKER_API_KEY,不会打印)
可选环境变量:
EGODATA_WORKER_COUNT=3 worker 数量(默认 3,与后端进程布局一致)
EGODATA_WORKER_API_KEY=... 显式指定(优先于 .env)
EGODATA_SERVER_URL=... 默认 http://127.0.0.1:8000
EGODATA_DEVICE=auto 上报的设备类型
"""
import os
import socket
import subprocess
import sys
import time
import uuid
from pathlib import Path
import watchfiles
ROOT = Path(__file__).resolve().parent.parent
# .env 也在监视内:改配置(如 WORKER_API_KEY / 临时目录)后自动重启生效
WATCH_DIRS = [ROOT / "app", ROOT / "worker", ROOT / ".env"]
LOG_PATH = ROOT / "data" / "tmp" / "worker" / "worker-hot.log"
WORKER_COUNT = int(os.getenv("EGODATA_WORKER_COUNT", "3"))
def _read_env_file(path: Path) -> dict[str, str]:
out: dict[str, str] = {}
if not path.is_file():
return out
for line in path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, _, v = line.partition("=")
out[k.strip()] = v.strip().strip('"').strip("'")
return out
def _log(msg: str) -> None:
line = f"[{time.strftime('%H:%M:%S')}] {msg}"
print(line, flush=True)
try:
LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
with open(LOG_PATH, "a", encoding="utf-8") as f:
f.write(line + "\n")
except Exception:
pass
def _resolve_api_key() -> str:
env = _read_env_file(ROOT / ".env")
return (os.getenv("EGODATA_WORKER_API_KEY")
or env.get("EGODATA_WORKER_API_KEY")
or env.get("WORKER_API_KEY")
or "")
API_KEY = _resolve_api_key()
def start_worker(index: int, generation: str) -> subprocess.Popen:
env = dict(os.environ)
env.setdefault("PYTHONPATH", str(ROOT))
env["EGODATA_WORKER_API_KEY"] = API_KEY
env.setdefault("EGODATA_SERVER_URL", "http://127.0.0.1:8000")
env.setdefault("EGODATA_DEVICE", "auto")
host = os.getenv("EGODATA_WORKER_ID_PREFIX", f"linux-{socket.gethostname()}")
env["EGODATA_WORKER_ID"] = f"{host}-g{generation}-{index + 1}"
# 每个 worker 独立临时子目录,避免共目录残留冲突
env.setdefault("EGODATA_WORK_DIR",
str(ROOT / "data" / "tmp" / "worker" / f"w{index + 1}"))
log_path = LOG_PATH.parent / f"worker-hot-{index + 1}.log"
log_file = open(log_path, "a", encoding="utf-8")
proc = subprocess.Popen([sys.executable, "-m", "worker"],
cwd=str(ROOT), env=env,
stdout=log_file, stderr=log_file)
_log(f"started worker #{index + 1} pid={proc.pid} id={env['EGODATA_WORKER_ID']}")
return proc
def stop_worker(proc: subprocess.Popen) -> None:
if proc is None or proc.poll() is not None:
return
proc.terminate()
try:
proc.wait(timeout=8)
except subprocess.TimeoutExpired:
proc.kill()
try:
proc.wait(timeout=5)
except Exception:
pass
def main() -> None:
if not API_KEY:
print("[error] EGODATA_WORKER_API_KEY 未设置,且 .env 里没有 WORKER_API_KEY",
file=sys.stderr)
sys.exit(1)
generation = uuid.uuid4().hex[:10]
procs = [start_worker(i, generation) for i in range(WORKER_COUNT)]
last_restart = time.monotonic()
try:
for changes in watchfiles.watch(*WATCH_DIRS, step=500):
if time.monotonic() - last_restart < 1.5:
continue # 防抖:合并连续变更
files = sorted({str(p) for _, p in changes})
_log("detected changes: " + ", ".join(files[:5]))
for p in procs:
stop_worker(p)
generation = uuid.uuid4().hex[:10]
procs = [start_worker(i, generation) for i in range(WORKER_COUNT)]
last_restart = time.monotonic()
except KeyboardInterrupt:
for p in procs:
stop_worker(p)
_log("hot reload stopped")
if __name__ == "__main__":
main()
|