2026-06-01 18:54:52 +09:00
|
|
|
import asyncio
|
|
|
|
|
import os
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
2026-05-26 10:39:44 +09:00
|
|
|
from datetime import datetime
|
2026-06-01 18:54:52 +09:00
|
|
|
from pathlib import Path
|
2026-05-26 10:39:44 +09:00
|
|
|
|
2026-06-01 18:54:52 +09:00
|
|
|
|
|
|
|
|
PROJECT = Path(__file__).resolve().parents[1]
|
|
|
|
|
PID_FILE = PROJECT / "logs" / "bot.pid"
|
2026-05-26 10:39:44 +09:00
|
|
|
|
|
|
|
|
os.chdir(PROJECT)
|
2026-06-01 18:54:52 +09:00
|
|
|
sys.path.insert(0, str(PROJECT))
|
|
|
|
|
|
2026-05-26 10:39:44 +09:00
|
|
|
from app.main import load_env
|
2026-06-01 18:54:52 +09:00
|
|
|
|
2026-05-26 10:39:44 +09:00
|
|
|
load_env()
|
|
|
|
|
|
|
|
|
|
|
2026-06-01 18:54:52 +09:00
|
|
|
def _is_process_alive(pid: int) -> bool:
|
|
|
|
|
result = subprocess.run(
|
|
|
|
|
["tasklist", "/FI", f"PID eq {pid}", "/NH"],
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
2026-05-26 10:39:44 +09:00
|
|
|
)
|
2026-06-01 18:54:52 +09:00
|
|
|
return str(pid) in result.stdout
|
2026-05-26 10:39:44 +09:00
|
|
|
|
|
|
|
|
|
2026-06-01 18:54:52 +09:00
|
|
|
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:
|
2026-05-26 10:39:44 +09:00
|
|
|
try:
|
2026-06-01 18:54:52 +09:00
|
|
|
return int(PID_FILE.read_text(encoding="utf-8").strip())
|
2026-05-26 10:39:44 +09:00
|
|
|
except Exception:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
2026-06-01 18:54:52 +09:00
|
|
|
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)
|
2026-05-26 10:39:44 +09:00
|
|
|
return proc.pid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def main():
|
|
|
|
|
now = datetime.now()
|
|
|
|
|
now_str = now.strftime("%H:%M")
|
|
|
|
|
|
|
|
|
|
if not ("09:00" <= now_str <= "15:10"):
|
2026-06-01 18:54:52 +09:00
|
|
|
print(f"[{now_str}] outside trading window - watchdog skipped")
|
2026-05-26 10:39:44 +09:00
|
|
|
return
|
|
|
|
|
|
|
|
|
|
from app.monitor.notifier import send
|
|
|
|
|
|
2026-06-01 18:54:52 +09:00
|
|
|
pid = _get_pid()
|
|
|
|
|
if pid is not None and _is_process_alive(pid):
|
|
|
|
|
print(f"[{now_str}] bot running PID={pid}")
|
|
|
|
|
return
|
2026-05-26 10:39:44 +09:00
|
|
|
|
2026-06-01 18:54:52 +09:00
|
|
|
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})"
|
2026-05-26 10:39:44 +09:00
|
|
|
print(msg)
|
|
|
|
|
await send(msg)
|
|
|
|
|
return
|
|
|
|
|
|
2026-06-01 18:54:52 +09:00
|
|
|
msg = f"[긴급] bot process not found (pid={pid}) - restarting"
|
2026-05-26 10:39:44 +09:00
|
|
|
print(msg)
|
|
|
|
|
await send(msg)
|
|
|
|
|
|
2026-06-01 18:54:52 +09:00
|
|
|
new_pid = _restart_bot()
|
|
|
|
|
await send(f"[복구] bot restarted PID={new_pid} ({now_str})")
|
|
|
|
|
print(f"bot restarted PID={new_pid}")
|
2026-05-26 10:39:44 +09:00
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
asyncio.run(main())
|