#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""修 crawl_scheduler_v2.py：超时时连子进程一起杀（避免孤儿卡死进程）。

问题：`subprocess.run(cmd, shell=True, timeout=...)` 超时只杀 sh，python3 子进程变孤儿
（实测有两个跑了 3.5 天 / 5.4 天，占着库连接）。
修法：改用 Popen + start_new_session=True（自成进程组），超时时 killpg 整组。
"""
import ast
import io
import os
import shutil

D = "/root/gov_crawler"
fn = "crawl_scheduler_v2.py"
p = os.path.join(D, fn)
src = io.open(p, encoding="utf-8").read()

OLD = '''    cmd = f'cd "{BASE_DIR}" && python3 "{script}" {args}'
    try:
        r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
        elapsed = time.time() - t0
        out = (r.stdout or "") + ("\\n" + r.stderr if r.stderr else "")
        return out.strip(), r.returncode, round(elapsed, 1)
    except subprocess.TimeoutExpired:
        elapsed = time.time() - t0
        return "[TIMEOUT]", -1, round(elapsed, 1)'''

NEW = '''    cmd = f'cd "{BASE_DIR}" && python3 "{script}" {args}'
    # 2026-09-22 修复：原来用 subprocess.run(..., shell=True, timeout=...)，
    #   超时只杀掉 sh，python3 子进程会变孤儿继续跑（实测积了 3.5 天 / 5.4 天的卡死进程，
    #   一直占着 sqlite 连接）。改为 Popen + start_new_session（自成进程组），
    #   超时时 killpg 整组连子进程一起杀。
    proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                            text=True, start_new_session=True)
    try:
        out_s, err_s = proc.communicate(timeout=timeout)
        elapsed = time.time() - t0
        out = (out_s or "") + ("\\n" + err_s if err_s else "")
        return out.strip(), proc.returncode, round(elapsed, 1)
    except subprocess.TimeoutExpired:
        try:
            os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
        except Exception:
            try:
                proc.kill()
            except Exception:
                pass
        try:
            proc.communicate(timeout=15)
        except Exception:
            pass
        elapsed = time.time() - t0
        return "[TIMEOUT]\\n[KILLED_PROCESS_GROUP]", -1, round(elapsed, 1)'''

assert src.count(OLD) == 1, "锚点命中 %d 次" % src.count(OLD)
src = src.replace(OLD, NEW)

# 确保 import signal / os
if "\nimport signal" not in src:
    src = src.replace("\nimport subprocess", "\nimport signal\nimport subprocess", 1)
if not any(l.strip() == "import os" for l in src.split("\n")):
    src = src.replace("\nimport signal", "\nimport os\nimport signal", 1)

ast.parse(src)
os.makedirs(os.path.join(D, "Archive"), exist_ok=True)
bak = os.path.join(D, "Archive", fn + ".bak_20260922_killpg")
if not os.path.exists(bak):
    shutil.copy2(p, bak)
io.open(p, "w", encoding="utf-8").write(src)
print("✅ %s 已修（Popen + killpg 整组杀）" % fn)
print("   备份 Archive/%s.bak_20260922_killpg" % fn)
import subprocess
print("   头部 import: %s" % [l for l in src.split("\n")[:20] if l.startswith("import")])
