fix: trimite alert Telegram la închidere fereastră CMD pe Windows

CTRL_CLOSE_EVENT omora procesul instant, înainte ca finally să ruleze.
_run_live_win32 instalează SetConsoleCtrlHandler care cancelează task-ul
asyncio → finally trimite "ATM oprit" și flushează notifier-ul (~5s fereastra).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-04-18 12:51:27 +00:00
parent 414ad69369
commit e1572fd3e3

View File

@@ -1124,7 +1124,69 @@ async def _drain_cmd_queue(ctx: RunContext) -> None:
def run_live(cfg, duration_s=None, capture_stub: bool = False) -> None:
"""Sync entry point — delegates to asyncio event loop."""
asyncio.run(run_live_async(cfg, duration_s=duration_s, capture_stub=capture_stub))
if sys.platform == "win32":
_run_live_win32(cfg, duration_s=duration_s, capture_stub=capture_stub)
else:
asyncio.run(run_live_async(cfg, duration_s=duration_s, capture_stub=capture_stub))
def _run_live_win32(cfg, duration_s=None, capture_stub: bool = False) -> None:
"""Windows variant: catches CTRL_CLOSE_EVENT so the finally block (Telegram alert) runs."""
try:
import win32api # type: ignore[import-untyped]
except ImportError:
asyncio.run(run_live_async(cfg, duration_s=duration_s, capture_stub=capture_stub))
return
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
_task: list[asyncio.Task] = []
def _close_handler(event: int) -> bool:
# CTRL_CLOSE_EVENT=2, CTRL_LOGOFF_EVENT=5, CTRL_SHUTDOWN_EVENT=6
if event in (2, 5, 6) and _task:
loop.call_soon_threadsafe(_task[0].cancel)
return True # tell Windows we handled it; gives ~5 s before force-kill
return False # CTRL+C / CTRL+BREAK: let Python's default handler fire
win32api.SetConsoleCtrlHandler(_close_handler, True)
async def _run() -> None:
t = asyncio.create_task(
run_live_async(cfg, duration_s=duration_s, capture_stub=capture_stub),
name="atm_live",
)
_task.append(t)
try:
await t
except asyncio.CancelledError:
pass # finally in run_live_async already sent the Telegram alert
try:
loop.run_until_complete(_run())
except KeyboardInterrupt:
# CTRL+C: cancel the live task and let its finally block run
if _task and not _task[0].done():
_task[0].cancel()
try:
loop.run_until_complete(_task[0])
except (asyncio.CancelledError, Exception):
pass
finally:
pending = asyncio.all_tasks(loop)
for t in pending:
t.cancel()
if pending:
try:
loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
except Exception:
pass
try:
loop.run_until_complete(loop.shutdown_asyncgens())
except Exception:
pass
asyncio.set_event_loop(None)
loop.close()
async def run_live_async(cfg, duration_s=None, capture_stub: bool = False) -> None: