import asyncio import os import subprocess import sys from datetime import datetime from pathlib import Path PROJECT = Path(__file__).resolve().parents[1] PID_FILE = PROJECT / "logs" / "bot.pid" os.chdir(PROJECT) sys.path.insert(0, str(PROJECT)) from app.main import load_env load_env() def _is_process_alive(pid: int) -> bool: result = subprocess.run( ["tasklist", "/FI", f"PID eq {pid}", "/NH"], capture_output=True, text=True, ) return str(pid) in result.stdout def _find_bot_pids() -> list[int]: command = ( "Get-CimInstance Win32_Process | " "Where-Object { $_.Name -like 'python*' -and " "($_.CommandLine -like '*app/main.py*' " "-or $_.CommandLine -like '*app\\\\main.py*') } | " "Select-Object -ExpandProperty ProcessId" ) result = subprocess.run( ["powershell", "-NoProfile", "-Command", command], capture_output=True, text=True, ) pids: list[int] = [] for line in result.stdout.splitlines(): line = line.strip() if line.isdigit(): pids.append(int(line)) return pids def _get_pid() -> int | None: try: return int(PID_FILE.read_text(encoding="utf-8").strip()) except Exception: return None def _write_pid(pid: int) -> None: PID_FILE.parent.mkdir(exist_ok=True) PID_FILE.write_text(str(pid), encoding="utf-8") def _restart_bot() -> int: env = os.environ.copy() env["PYTHONUNBUFFERED"] = "1" creationflags = 0 if hasattr(subprocess, "DETACHED_PROCESS"): creationflags = subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP log_path = PROJECT / "logs" / "bot_stderr.log" with open(log_path, "a", encoding="utf-8") as log: proc = subprocess.Popen( [sys.executable, "-u", "app/main.py"], cwd=PROJECT, creationflags=creationflags, stdout=log, stderr=subprocess.STDOUT, close_fds=True, env=env, ) _write_pid(proc.pid) return proc.pid async def main(): now = datetime.now() now_str = now.strftime("%H:%M") if not ("09:00" <= now_str <= "15:10"): print(f"[{now_str}] outside trading window - watchdog skipped") return from app.monitor.notifier import send pid = _get_pid() if pid is not None and _is_process_alive(pid): print(f"[{now_str}] bot running PID={pid}") return live_pids = _find_bot_pids() if live_pids: recovered_pid = live_pids[0] _write_pid(recovered_pid) msg = f"[복구] bot.pid corrected to running bot PID={recovered_pid} ({now_str})" print(msg) await send(msg) return msg = f"[긴급] bot process not found (pid={pid}) - restarting" print(msg) await send(msg) new_pid = _restart_bot() await send(f"[복구] bot restarted PID={new_pid} ({now_str})") print(f"bot restarted PID={new_pid}") if __name__ == "__main__": asyncio.run(main())