tg-ws-proxy/utils/tray_proxy_runner.py

129 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Запуск 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()