From 9deb7f42752592c8e87084b9c140ee16aadb65a4 Mon Sep 17 00:00:00 2001 From: Ivan Kravets Date: Fri, 21 Jul 2023 15:27:31 +0300 Subject: [PATCH] Run sync RPC methods in thread --- platformio/home/rpc/handlers/account.py | 5 +++-- platformio/home/rpc/handlers/app.py | 1 + platformio/home/rpc/handlers/base.py | 2 ++ platformio/home/rpc/handlers/ide.py | 1 + platformio/home/rpc/handlers/misc.py | 2 ++ platformio/home/rpc/handlers/os.py | 24 +++++++------------- platformio/home/rpc/handlers/piocore.py | 2 ++ platformio/home/rpc/handlers/platform.py | 28 +++++++++++------------- platformio/home/rpc/handlers/project.py | 2 ++ platformio/home/rpc/handlers/registry.py | 8 +++---- platformio/home/rpc/server.py | 23 ++++++++++++++----- platformio/home/run.py | 18 +++++++-------- 12 files changed, 64 insertions(+), 52 deletions(-) diff --git a/platformio/home/rpc/handlers/account.py b/platformio/home/rpc/handlers/account.py index f7e5dade..b3ba3521 100644 --- a/platformio/home/rpc/handlers/account.py +++ b/platformio/home/rpc/handlers/account.py @@ -19,11 +19,12 @@ from platformio.home.rpc.handlers.base import BaseRPCHandler class AccountRPC(BaseRPCHandler): + NAMESPACE = "account" + @staticmethod def call_client(method, *args, **kwargs): try: - client = AccountClient() - return getattr(client, method)(*args, **kwargs) + return getattr(AccountClient(), method)(*args, **kwargs) except Exception as exc: # pylint: disable=bare-except raise JSONRPC20DispatchException( code=5000, message="PIO Account Call Error", data=str(exc) diff --git a/platformio/home/rpc/handlers/app.py b/platformio/home/rpc/handlers/app.py index 4b6195e4..0baec8d0 100644 --- a/platformio/home/rpc/handlers/app.py +++ b/platformio/home/rpc/handlers/app.py @@ -20,6 +20,7 @@ from platformio.project.helpers import is_platformio_project class AppRPC(BaseRPCHandler): + NAMESPACE = "app" IGNORE_STORAGE_KEYS = [ "cid", "coreVersion", diff --git a/platformio/home/rpc/handlers/base.py b/platformio/home/rpc/handlers/base.py index 7b3f5d8f..2def3ca1 100644 --- a/platformio/home/rpc/handlers/base.py +++ b/platformio/home/rpc/handlers/base.py @@ -14,4 +14,6 @@ class BaseRPCHandler: + NAMESPACE = None + factory = None diff --git a/platformio/home/rpc/handlers/ide.py b/platformio/home/rpc/handlers/ide.py index 643ee0c5..df8a3bf3 100644 --- a/platformio/home/rpc/handlers/ide.py +++ b/platformio/home/rpc/handlers/ide.py @@ -22,6 +22,7 @@ from platformio.home.rpc.handlers.base import BaseRPCHandler class IDERPC(BaseRPCHandler): + NAMESPACE = "ide" COMMAND_TIMEOUT = 1.5 # in seconds def __init__(self): diff --git a/platformio/home/rpc/handlers/misc.py b/platformio/home/rpc/handlers/misc.py index ab2508a4..1e23d271 100644 --- a/platformio/home/rpc/handlers/misc.py +++ b/platformio/home/rpc/handlers/misc.py @@ -22,6 +22,8 @@ from platformio.home.rpc.handlers.os import OSRPC class MiscRPC(BaseRPCHandler): + NAMESPACE = "misc" + async def load_latest_tweets(self, data_url): cache_key = ContentCache.key_from_args(data_url, "tweets") cache_valid = "180d" diff --git a/platformio/home/rpc/handlers/os.py b/platformio/home/rpc/handlers/os.py index b8dcfc9b..86927e7b 100644 --- a/platformio/home/rpc/handlers/os.py +++ b/platformio/home/rpc/handlers/os.py @@ -22,25 +22,18 @@ import click from platformio import fs from platformio.cache import ContentCache -from platformio.compat import aio_to_thread from platformio.device.list.util import list_logical_devices from platformio.home.rpc.handlers.base import BaseRPCHandler from platformio.http import HTTPSession, ensure_internet_on -class HTTPAsyncSession(HTTPSession): - async def request( # pylint: disable=signature-differs,invalid-overridden-method - self, *args, **kwargs - ): - func = super().request - return await aio_to_thread(func, *args, **kwargs) - - class OSRPC(BaseRPCHandler): + NAMESPACE = "os" + _http_session = None @classmethod - async def fetch_content(cls, url, data=None, headers=None, cache_valid=None): + def fetch_content(cls, url, data=None, headers=None, cache_valid=None): if not headers: headers = { "User-Agent": ( @@ -60,12 +53,12 @@ class OSRPC(BaseRPCHandler): ensure_internet_on(raise_exception=True) if not cls._http_session: - cls._http_session = HTTPAsyncSession() + cls._http_session = HTTPSession() if data: - r = await cls._http_session.post(url, data=data, headers=headers) + r = cls._http_session.post(url, data=data, headers=headers) else: - r = await cls._http_session.get(url, headers=headers) + r = cls._http_session.get(url, headers=headers) r.raise_for_status() result = r.text @@ -74,13 +67,12 @@ class OSRPC(BaseRPCHandler): cc.set(cache_key, result, cache_valid) return result - async def request_content(self, uri, data=None, headers=None, cache_valid=None): + def request_content(self, uri, data=None, headers=None, cache_valid=None): if uri.startswith("http"): - return await self.fetch_content(uri, data, headers, cache_valid) + return self.fetch_content(uri, data, headers, cache_valid) local_path = uri[7:] if uri.startswith("file://") else uri with io.open(local_path, encoding="utf-8") as fp: return fp.read() - return None @staticmethod def open_url(url): diff --git a/platformio/home/rpc/handlers/piocore.py b/platformio/home/rpc/handlers/piocore.py index b09338e5..3dbdded9 100644 --- a/platformio/home/rpc/handlers/piocore.py +++ b/platformio/home/rpc/handlers/piocore.py @@ -102,6 +102,8 @@ def get_core_fullpath(): class PIOCoreRPC(BaseRPCHandler): + NAMESPACE = "core" + @staticmethod def version(): return __version__ diff --git a/platformio/home/rpc/handlers/platform.py b/platformio/home/rpc/handlers/platform.py index 34daf540..4956675f 100644 --- a/platformio/home/rpc/handlers/platform.py +++ b/platformio/home/rpc/handlers/platform.py @@ -14,8 +14,8 @@ import os.path -from platformio.compat import aio_to_thread from platformio.home.rpc.handlers.base import BaseRPCHandler +from platformio.home.rpc.handlers.registry import RegistryRPC from platformio.package.manager.platform import PlatformPackageManager from platformio.package.manifest.parser import ManifestParserFactory from platformio.package.meta import PackageSpec @@ -23,15 +23,13 @@ from platformio.platform.factory import PlatformFactory class PlatformRPC(BaseRPCHandler): - async def fetch_platforms(self, search_query=None, page=0, force_installed=False): - if force_installed: - return { - "items": await aio_to_thread( - self._load_installed_platforms, search_query - ) - } + NAMESPACE = "platform" - search_result = await self.factory.manager.dispatcher["registry.call_client"]( + def fetch_platforms(self, search_query=None, page=0, force_installed=False): + if force_installed: + return {"items": self._load_installed_platforms(search_query)} + + search_result = RegistryRPC.call_client( method="list_packages", query=search_query, qualifiers={ @@ -88,17 +86,17 @@ class PlatformRPC(BaseRPCHandler): ) return items - async def fetch_boards(self, platform_spec): + def fetch_boards(self, platform_spec): spec = PackageSpec(platform_spec) if spec.owner: - return await self.factory.manager.dispatcher["registry.call_client"]( + return RegistryRPC.call_client( method="get_package", typex="platform", owner=spec.owner, name=spec.name, extra_path="/boards", ) - return await aio_to_thread(self._load_installed_boards, spec) + return self._load_installed_boards(spec) @staticmethod def _load_installed_boards(platform_spec): @@ -108,17 +106,17 @@ class PlatformRPC(BaseRPCHandler): key=lambda item: item["name"], ) - async def fetch_examples(self, platform_spec): + def fetch_examples(self, platform_spec): spec = PackageSpec(platform_spec) if spec.owner: - return await self.factory.manager.dispatcher["registry.call_client"]( + return RegistryRPC.call_client( method="get_package", typex="platform", owner=spec.owner, name=spec.name, extra_path="/examples", ) - return await aio_to_thread(self._load_installed_examples, spec) + return self._load_installed_examples(spec) @staticmethod def _load_installed_examples(platform_spec): diff --git a/platformio/home/rpc/handlers/project.py b/platformio/home/rpc/handlers/project.py index 90b3ab78..fba7a25c 100644 --- a/platformio/home/rpc/handlers/project.py +++ b/platformio/home/rpc/handlers/project.py @@ -34,6 +34,8 @@ from platformio.project.options import get_config_options_schema class ProjectRPC(BaseRPCHandler): + NAMESPACE = "project" + @staticmethod def config_call(init_kwargs, method, *args): assert isinstance(init_kwargs, dict) diff --git a/platformio/home/rpc/handlers/registry.py b/platformio/home/rpc/handlers/registry.py index 84a374b6..a862df06 100644 --- a/platformio/home/rpc/handlers/registry.py +++ b/platformio/home/rpc/handlers/registry.py @@ -14,17 +14,17 @@ from ajsonrpc.core import JSONRPC20DispatchException -from platformio.compat import aio_to_thread from platformio.home.rpc.handlers.base import BaseRPCHandler from platformio.registry.client import RegistryClient class RegistryRPC(BaseRPCHandler): + NAMESPACE = "registry" + @staticmethod - async def call_client(method, *args, **kwargs): + def call_client(method, *args, **kwargs): try: - client = RegistryClient() - return await aio_to_thread(getattr(client, method), *args, **kwargs) + return getattr(RegistryClient(), method)(*args, **kwargs) except Exception as exc: # pylint: disable=bare-except raise JSONRPC20DispatchException( code=5000, message="Registry Call Error", data=str(exc) diff --git a/platformio/home/rpc/server.py b/platformio/home/rpc/server.py index ab7c13da..83b4da02 100644 --- a/platformio/home/rpc/server.py +++ b/platformio/home/rpc/server.py @@ -12,22 +12,24 @@ # See the License for the specific language governing permissions and # limitations under the License. +import functools +import inspect from urllib.parse import parse_qs -import ajsonrpc.utils +import ajsonrpc.manager import click from ajsonrpc.core import JSONRPC20Error, JSONRPC20Request from ajsonrpc.dispatcher import Dispatcher from ajsonrpc.manager import AsyncJSONRPCResponseManager, JSONRPC20Response from starlette.endpoints import WebSocketEndpoint -from platformio.compat import aio_create_task, aio_get_running_loop +from platformio.compat import aio_create_task, aio_get_running_loop, aio_to_thread from platformio.http import InternetConnectionError from platformio.proc import force_exit # Remove this line when PR is merged # https://github.com/pavlov99/ajsonrpc/pull/22 -ajsonrpc.utils.is_invalid_params = lambda: False +ajsonrpc.manager.is_invalid_params = lambda *args, **kwargs: False class JSONRPCServerFactoryBase: @@ -44,9 +46,18 @@ class JSONRPCServerFactoryBase: def __call__(self, *args, **kwargs): raise NotImplementedError - def add_object_handler(self, handler, namespace): - handler.factory = self - self.manager.dispatcher.add_object(handler, prefix="%s." % namespace) + def add_object_handler(self, obj): + obj.factory = self + namespace = obj.NAMESPACE or obj.__class__.__name__ + for name in dir(obj): + method = getattr(obj, name) + if name.startswith("_") or not ( + inspect.ismethod(method) or inspect.isfunction(method) + ): + continue + if not inspect.iscoroutinefunction(method): + method = functools.partial(aio_to_thread, method) + self.manager.dispatcher.add_function(method, name=f"{namespace}.{name}") def on_client_connect(self, connection, actor=None): self._clients[connection] = {"actor": actor} diff --git a/platformio/home/run.py b/platformio/home/run.py index df3fa47e..c593c68e 100644 --- a/platformio/home/run.py +++ b/platformio/home/run.py @@ -67,15 +67,15 @@ def run_server(host, port, no_open, shutdown_timeout, home_url): raise PlatformioException("Invalid path to PIO Home Contrib") ws_rpc_factory = WebSocketJSONRPCServerFactory(shutdown_timeout) - ws_rpc_factory.add_object_handler(AccountRPC(), namespace="account") - ws_rpc_factory.add_object_handler(AppRPC(), namespace="app") - ws_rpc_factory.add_object_handler(IDERPC(), namespace="ide") - ws_rpc_factory.add_object_handler(MiscRPC(), namespace="misc") - ws_rpc_factory.add_object_handler(OSRPC(), namespace="os") - ws_rpc_factory.add_object_handler(PIOCoreRPC(), namespace="core") - ws_rpc_factory.add_object_handler(ProjectRPC(), namespace="project") - ws_rpc_factory.add_object_handler(PlatformRPC(), namespace="platform") - ws_rpc_factory.add_object_handler(RegistryRPC(), namespace="registry") + ws_rpc_factory.add_object_handler(AccountRPC()) + ws_rpc_factory.add_object_handler(AppRPC()) + ws_rpc_factory.add_object_handler(IDERPC()) + ws_rpc_factory.add_object_handler(MiscRPC()) + ws_rpc_factory.add_object_handler(OSRPC()) + ws_rpc_factory.add_object_handler(PIOCoreRPC()) + ws_rpc_factory.add_object_handler(ProjectRPC()) + ws_rpc_factory.add_object_handler(PlatformRPC()) + ws_rpc_factory.add_object_handler(RegistryRPC()) path = urlparse(home_url).path routes = [