zapret-kvn/xray_fluent/engines/sidecar/readiness.py
loop-uh c624a581ac
Some checks failed
Windows project source guards / test (push) Has been cancelled
Подключение и переключения быстрее: без лишних ожиданий и PowerShell
- Ожидание освобождения TUN-адаптера ждало любой адаптер xftun*, а
  Wintun убитого ядра остаётся в системе выключенным: после первой же
  TUN-сессии каждое отключение и переключение в TUN стояло полные 10 с.
  Теперь ждём только адаптер своей сессии, пока он не перестанет быть Up.
- sing-box останавливается сразу kill(): terminate() консольный sing-box
  на Windows не обрабатывает, 500 мс «мягкой» остановки истекали всегда.
- «Осиротевшие» ядра и winws2 ищутся снимком WinAPI вместо PowerShell+WMI
  (~0,2 с на каждый запуск ядра, у Zapret — дважды); PowerShell — запасной.
- Готовность портов ядер, relay и Clash API сначала смотрится по таблице
  TCP: подключение к ещё закрытому порту Windows держит повторами SYN, и
  ранняя проба всегда выжидала свой таймаут (~0,35–0,5 с).
- sing-box check — только после неудачного запуска, ради понятной ошибки:
  параллельно с работающим ядром она для TUN-конфига мешает туннелю.
- cryptography грузится только при пароле данных и ссылках Happ.

Проверка готовности TUN оставлена прежней: при более ранней готовности
закрепление сервера через Clash API в первые ~0,4 с ломало TCP туннеля
в «родном» режиме sing-box.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:41:18 +03:00

81 lines
3 KiB
Python

"""Loopback relay readiness — the shared seam between sing-box and each core."""
from __future__ import annotations
import socket
import time
from typing import Callable
from ...application.async_steps import TransitionSteps, run_in_worker, sleep_ms
from ...constants import PROXY_HOST
from ...network.proxy_readiness import LISTENER_POLL_MS, ports_listening
from ...platform.windows.subprocess_utils import sleep_with_events
def probe_loopback_relay(port: int, *, host: str = PROXY_HOST, connect_timeout: float = 0.15) -> bool:
"""One blocking accept probe (worker-only)."""
try:
with socket.create_connection((host, int(port)), timeout=connect_timeout):
return True
except OSError:
return False
def wait_for_loopback_relay_steps(
port: int,
*,
host: str = PROXY_HOST,
timeout: float = 10.0,
should_continue: Callable[[], bool] | None = None,
connect_timeout: float = 0.15,
step: float = 0.05,
) -> TransitionSteps:
"""Step version of :func:`wait_for_loopback_relay` for transitions.
Every connect attempt runs in the worker pool; ``should_continue`` is
evaluated on the GUI thread between attempts, so it may read QProcess
and controller state.
"""
deadline = time.monotonic() + max(0.0, timeout)
while time.monotonic() < deadline:
if should_continue is not None and not should_continue():
return False
# Порт ещё не открыт (таблица TCP) — подключаться рано: на Windows
# такая попытка выжидает весь connect_timeout.
ready = yield run_in_worker(
lambda: None if ports_listening((port,)) is False
else probe_loopback_relay(port, host=host, connect_timeout=connect_timeout)
)
if ready:
return True
yield sleep_ms(LISTENER_POLL_MS if ready is None else int(step * 1000))
return False
def wait_for_loopback_relay(
port: int,
*,
host: str = PROXY_HOST,
timeout: float = 10.0,
should_continue: Callable[[], bool] | None = None,
connect_timeout: float = 0.15,
step: float = 0.05,
) -> bool:
"""Wait until a core's local SOCKS relay accepts a loopback TCP connection.
Returns ``True`` as soon as a connection succeeds, ``False`` if the deadline
passes or ``should_continue`` reports the attempt should be abandoned (e.g.
the owned process died or the transition was superseded). No payload is
sent — this only proves the listener is bound and accepting, which is the
precondition the sing-box front relies on before dialing the relay.
"""
deadline = time.monotonic() + max(0.0, timeout)
while time.monotonic() < deadline:
if should_continue is not None and not should_continue():
return False
try:
with socket.create_connection((host, int(port)), timeout=connect_timeout):
return True
except OSError:
sleep_with_events(step)
return False