_ops_launch.py 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. r"""动作启动器 (2026-09-12) —— 只做一件事: 把真正的执行器 **甩开** 与网关的父子关系, 然后立刻退出。
  4. ## 为什么需要这层
  5. 网关用 `subprocess.Popen` 起的任务, 在 Windows 上是网关的**子进程**; 而 `guanlan.py stop` 用的是
  6. `taskkill /PID <网关> /T /F` —— `/T` 会**沿父子树把子孙一起杀**。于是:
  7. 在控制台点"执行重算" → 重算跑到第 6 步"重启服务" → stop 杀掉网关 → **把正在执行的重算也杀了**
  8. (实测: 日志停在 `[4/12] ⑥ 重启服务 (stop)` 那一行, 之后什么都没有, 服务也停在半路)
  9. 现在由本启动器 `Popen` 真正的执行器后**立即退出**: 执行器的父进程变成一个已退出进程, 于是它不再是
  10. 网关的子孙, `taskkill /T` 不会再连带杀掉它。启动器把执行器的 pid 打到 stdout 最后一行, 供父进程记账。
  11. 用法: python scripts/_ops_launch.py --tag rebuild --log <日志文件> -- <命令...>
  12. """
  13. from __future__ import annotations
  14. import argparse
  15. import os
  16. import pathlib
  17. import subprocess
  18. import sys
  19. ROOT = pathlib.Path(__file__).resolve().parents[1]
  20. def main() -> int:
  21. ap = argparse.ArgumentParser()
  22. ap.add_argument('--tag', required=True)
  23. ap.add_argument('--log', required=True)
  24. ap.add_argument('cmd', nargs=argparse.REMAINDER)
  25. a = ap.parse_args()
  26. cmd = [c for c in a.cmd if c != '--']
  27. if not cmd:
  28. print('[X] 没给命令', file=sys.stderr); return 2
  29. log = pathlib.Path(a.log)
  30. log.parent.mkdir(parents=True, exist_ok=True)
  31. env = dict(os.environ, PYTHONUTF8='1', PYTHONIOENCODING='utf-8', PYTHONUNBUFFERED='1')
  32. flags = 0
  33. if os.name == 'nt':
  34. flags = subprocess.CREATE_NEW_PROCESS_GROUP | getattr(subprocess, 'DETACHED_PROCESS', 0)
  35. with open(log, 'ab') as f:
  36. p = subprocess.Popen([sys.executable, 'scripts/_ops_run.py', '--tag', a.tag, '--'] + cmd,
  37. cwd=str(ROOT), env=env, stdout=f, stderr=subprocess.STDOUT,
  38. stdin=subprocess.DEVNULL, creationflags=flags, close_fds=True)
  39. print(p.pid) # ← 最后一行是执行器 pid, 父进程读它
  40. return 0
  41. if __name__ == '__main__':
  42. sys.exit(main())