Загрузка данных


        await self._ensure_connected_for_call()
        self.dispatcher.register_subscription(scope, target, args)
        self.dispatcher.advance_watermark(scope)
        await self._send_keeping_connection(self._client.invoke, target, args)
        self.last_invocation_id = self._client.invocation_id

    async def invoke_stream(self, scope: SubscriptionScope, target: str, args: Any) -> None:
        """Отправляет streaming-вызов; в реестр не добавляется, потому что он одноразовый."""
        await self._ensure_connected_for_call()
        self.dispatcher.advance_watermark(scope)
        await self._send_keeping_connection(self._client.invoke_stream, target, args)
        self.last_invocation_id = self._client.invocation_id

    async def _send_keeping_connection(
        self, send_factory: Callable[..., Coroutine[Any, Any, Any]], target: str, args: Any
    ) -> None:
        """
        Отправляет вызов и переживает обрыв соединения между проверкой isConnected и send.
        """
        self._ensure_client()
        connection_before_send = self.connection_id
        try:
            await send_factory(target, args)
        except websockets.WebSocketException as error:
            logger.warning(
                f"[WS_MANAGER] Обрыв соединения при отправке {target}: {mask_secrets(error)}. "
                "Ждём, пока supervisor поднимет реконнект"
            )
            if not await self._wait_for_new_connection(connection_before_send, PersistConst.CALL_CONNECTION_WAIT_S):
                snapshot = self.health_snapshot()
                raise ConnectionError(
                    f"Соединение не восстановилось при отправке {target}: "
                    f"isConnected={snapshot['isConnected']}, connectionId={snapshot['connectionId']}, "
                    f"connectionsEstablished={snapshot['connectionsEstablished']}, "
                    f"connectFailures={snapshot['connectFailures']}"
                ) from error
            try:
                await send_factory(target, args)
            except websockets.WebSocketException as retry_error:
                snapshot = self.health_snapshot()
                raise ConnectionError(
                    f"Повторная отправка {target} не удалась: {mask_secrets(retry_error)}, "
                    f"isConnected={snapshot['isConnected']}, connectionId={snapshot['connectionId']}"
                ) from retry_error

    async def _wait_for_new_connection(self, previous_connection_id: Optional[str], timeout: float) -> bool:
        """
        Ждёт, пока supervisor поднимет соединение с новым connectionId.
        """
        deadline = time.monotonic() + timeout

        def connected_with_new_id() -> bool:
            return self._is_connected() and self.connection_id != previous_connection_id

        while time.monotonic() < deadline and not self._stopped.is_set():
            if connected_with_new_id():
                return True
            await asyncio.sleep(PersistConst.RECEIVE_POLL_INTERVAL_S)
        return connected_with_new_id()