diff --git a/main.py b/main.py index f397000..1fcf267 100644 --- a/main.py +++ b/main.py @@ -19,6 +19,108 @@ if hasattr(sys.stderr, "reconfigure"): from loguru import logger +# ── Windows startup-freeze fix ───────────────────────────────────────────── +# asyncio.run() → ProactorEventLoop.__init__ calls socket.socketpair(). On +# Windows Python 3.12 that goes through _fallback_socketpair(), which does a +# loopback (127.0.0.1) TCP connect/accept dance. Under the run_bot.bat console +# path, security software filters that loopback handshake → accept() blocks +# FOREVER → the bot hangs before start() ever runs (2 init lines on console, +# log file stays empty, process idle at 0 CPU). Windows 10+ supports AF_UNIX +# sockets, which need no loopback TCP at all, so build the self-pipe pair over +# AF_UNIX and fall back to the stock socketpair if AF_UNIX is unavailable. +if sys.platform == "win32": + import ctypes as _ctypes + import os as _os + import socket as _socket_mod + import tempfile as _tempfile + + # Python builds (incl. python.org 3.12) don't expose socket.AF_UNIX on + # Windows even though Winsock has supported it since Win10 17063, so we + # call ws2_32 directly. socket.socket(fileno=N) on Windows interprets N as + # a raw SOCKET handle (proven empirically), so the pair wraps cleanly. + _ws2 = _ctypes.WinDLL("ws2_32", use_last_error=True) + _ws2.socket.argtypes = [_ctypes.c_int, _ctypes.c_int, _ctypes.c_int] + _ws2.socket.restype = _ctypes.c_size_t + _ws2.bind.argtypes = [_ctypes.c_size_t, _ctypes.c_void_p, _ctypes.c_int] + _ws2.bind.restype = _ctypes.c_int + _ws2.listen.argtypes = [_ctypes.c_size_t, _ctypes.c_int] + _ws2.listen.restype = _ctypes.c_int + _ws2.connect.argtypes = [_ctypes.c_size_t, _ctypes.c_void_p, _ctypes.c_int] + _ws2.connect.restype = _ctypes.c_int + _ws2.accept.argtypes = [_ctypes.c_size_t, _ctypes.c_void_p, _ctypes.POINTER(_ctypes.c_int)] + _ws2.accept.restype = _ctypes.c_size_t + _ws2.closesocket.argtypes = [_ctypes.c_size_t] + _ws2.closesocket.restype = _ctypes.c_int + + class _SockAddrUn(_ctypes.Structure): + _fields_ = [ + ("sun_family", _ctypes.c_ushort), + ("sun_path", _ctypes.c_char * 260), # Windows uses MAX_PATH + ] + + _AF_UNIX = 1 # Winsock2.h: AF_UNIX == 1 + _SOCK_STREAM = 1 + _INVALID_SOCKET = _ctypes.c_size_t(-1).value + + def _unix_socketpair(): + path = _os.path.join( + _tempfile.gettempdir(), + f"xau_sp_{_os.getpid()}_{_os.urandom(4).hex()}.sock", + ) + server = _ws2.socket(_AF_UNIX, _SOCK_STREAM, 0) + if server == _INVALID_SOCKET: + raise _ctypes.OSError(_ctypes.get_last_error(), "socket(AF_UNIX)") + client = _INVALID_SOCKET + accepted = _INVALID_SOCKET + ok = False + try: + addr = _SockAddrUn() + addr.sun_family = _AF_UNIX + path_b = path.encode("utf-8") + if len(path_b) >= 260: + raise ValueError("AF_UNIX path too long") + addr.sun_path = path_b + b"\x00" + addr_len = _ctypes.sizeof(_ctypes.c_ushort) + len(path_b) + 1 + if _ws2.bind(server, _ctypes.byref(addr), addr_len) != 0: + raise _ctypes.OSError(_ctypes.get_last_error(), "bind AF_UNIX") + if _ws2.listen(server, 1) != 0: + raise _ctypes.OSError(_ctypes.get_last_error(), "listen AF_UNIX") + client = _ws2.socket(_AF_UNIX, _SOCK_STREAM, 0) + if client == _INVALID_SOCKET: + raise _ctypes.OSError(_ctypes.get_last_error(), "socket(AF_UNIX) client") + if _ws2.connect(client, _ctypes.byref(addr), addr_len) != 0: + raise _ctypes.OSError(_ctypes.get_last_error(), "connect AF_UNIX") + accepted = _ws2.accept(server, None, None) + if accepted == _INVALID_SOCKET: + raise _ctypes.OSError(_ctypes.get_last_error(), "accept AF_UNIX") + # Ownership of both raw handles transfers to the Python sockets. + result = (_socket_mod.socket(fileno=accepted), _socket_mod.socket(fileno=client)) + ok = True + return result + finally: + if server != _INVALID_SOCKET: + _ws2.closesocket(server) + if not ok: + for h in (accepted, client): + if h != _INVALID_SOCKET: + _ws2.closesocket(h) + try: + _os.unlink(path) + except OSError: + pass # unique temp name → leftovers never collide + + _orig_socketpair = _socket_mod.socketpair + + def _safe_socketpair(*args, **kwargs): + try: + return _unix_socketpair() + except Exception: + # Rare fallback (pre-17063 Windows without AF_UNIX) — same + # behavior as before the patch. + return _orig_socketpair(*args, **kwargs) + + _socket_mod.socketpair = _safe_socketpair + def parse_args(): parser = argparse.ArgumentParser(