129 lines
4.5 KiB
Python
129 lines
4.5 KiB
Python
"""Запуск asyncio-прокси в отдельном потоке (общий для tray entrypoints)."""
|
||
from __future__ import annotations
|
||
|
||
import asyncio as _asyncio
|
||
import threading
|
||
import time
|
||
from typing import Any, Callable, Dict, Mapping, Optional, Tuple
|
||
|
||
import proxy.tg_ws_proxy as tg_ws_proxy
|
||
|
||
ProxyStopState = Tuple[Any, Any] # (loop, Event)
|
||
|
||
|
||
class ProxyThreadRunner:
|
||
"""Управляет потоком с tg_ws_proxy._run и корректной остановкой."""
|
||
|
||
def __init__(
|
||
self,
|
||
*,
|
||
default_config: Mapping[str, Any],
|
||
get_config: Callable[[], Dict[str, Any]],
|
||
log: Any,
|
||
show_error: Callable[[str], None],
|
||
join_timeout: float = 2.0,
|
||
warn_on_join_stuck: bool = False,
|
||
treat_win_error_10048_as_port_in_use: bool = False,
|
||
) -> None:
|
||
self._default = dict(default_config)
|
||
self._get_config = get_config
|
||
self._log = log
|
||
self._show_error = show_error
|
||
self._join_timeout = join_timeout
|
||
self._warn_on_join_stuck = warn_on_join_stuck
|
||
self._win10048 = treat_win_error_10048_as_port_in_use
|
||
|
||
self._thread: Optional[threading.Thread] = None
|
||
self._async_stop: Optional[ProxyStopState] = None
|
||
|
||
@property
|
||
def async_stop(self) -> Optional[ProxyStopState]:
|
||
return self._async_stop
|
||
|
||
def _run_proxy_thread(
|
||
self,
|
||
port: int,
|
||
dc_opt: Dict[int, str],
|
||
verbose: bool,
|
||
host: str = "127.0.0.1",
|
||
) -> None:
|
||
loop = _asyncio.new_event_loop()
|
||
_asyncio.set_event_loop(loop)
|
||
stop_ev = _asyncio.Event()
|
||
self._async_stop = (loop, stop_ev)
|
||
|
||
try:
|
||
loop.run_until_complete(
|
||
tg_ws_proxy._run(port, dc_opt, stop_event=stop_ev, host=host)
|
||
)
|
||
except Exception as exc:
|
||
self._log.error("Proxy thread crashed: %s", exc)
|
||
msg = str(exc)
|
||
port_busy = "Address already in use" in msg
|
||
if self._win10048 and "10048" in msg:
|
||
port_busy = True
|
||
if port_busy:
|
||
self._show_error(
|
||
"Не удалось запустить прокси:\n"
|
||
"Порт уже используется другим приложением.\n\n"
|
||
"Закройте приложение, использующее этот порт, "
|
||
"или измените порт в настройках прокси и перезапустите."
|
||
)
|
||
finally:
|
||
loop.close()
|
||
self._async_stop = None
|
||
|
||
def start(self) -> None:
|
||
if self._thread and self._thread.is_alive():
|
||
self._log.info("Proxy already running")
|
||
return
|
||
|
||
cfg = self._get_config()
|
||
port = cfg.get("port", self._default["port"])
|
||
host = cfg.get("host", self._default["host"])
|
||
dc_ip_list = cfg.get("dc_ip", self._default["dc_ip"])
|
||
verbose = bool(cfg.get("verbose", False))
|
||
|
||
try:
|
||
dc_opt = tg_ws_proxy.parse_dc_ip_list(dc_ip_list)
|
||
except ValueError as e:
|
||
self._log.error("Bad config dc_ip: %s", e)
|
||
self._show_error(f"Ошибка конфигурации:\n{e}")
|
||
return
|
||
|
||
self._log.info("Starting proxy on %s:%d ...", host, port)
|
||
|
||
buf_kb = cfg.get("buf_kb", self._default["buf_kb"])
|
||
pool_size = cfg.get("pool_size", self._default["pool_size"])
|
||
tg_ws_proxy._RECV_BUF = max(4, buf_kb) * 1024
|
||
tg_ws_proxy._SEND_BUF = tg_ws_proxy._RECV_BUF
|
||
tg_ws_proxy._WS_POOL_SIZE = max(0, pool_size)
|
||
|
||
self._thread = threading.Thread(
|
||
target=self._run_proxy_thread,
|
||
args=(port, dc_opt, verbose, host),
|
||
daemon=True,
|
||
name="proxy",
|
||
)
|
||
self._thread.start()
|
||
|
||
def stop(self) -> None:
|
||
if self._async_stop:
|
||
loop, stop_ev = self._async_stop
|
||
loop.call_soon_threadsafe(stop_ev.set)
|
||
if self._thread:
|
||
self._thread.join(timeout=self._join_timeout)
|
||
if self._warn_on_join_stuck and self._thread.is_alive():
|
||
self._log.warning(
|
||
"Proxy thread did not finish within timeout; "
|
||
"the process may still exit shortly"
|
||
)
|
||
self._thread = None
|
||
self._log.info("Proxy stopped")
|
||
|
||
def restart(self) -> None:
|
||
self._log.info("Restarting proxy...")
|
||
self.stop()
|
||
time.sleep(0.3)
|
||
self.start()
|