diff --git a/tests/protocol/test_conn_writer.py b/tests/protocol/test_conn_writer.py index c3c25b8..254a168 100644 --- a/tests/protocol/test_conn_writer.py +++ b/tests/protocol/test_conn_writer.py @@ -11,8 +11,8 @@ import threading from tests.protocol._fakes import FakeTransport -from trpc_sdk.protocol import ConnWriter -from trpc_sdk.protocol import _conn_writer +from trpc_sdk.transport import ConnWriter +from trpc_sdk.transport._server import _conn_writer def test_still_importable_from_runtime_package(): diff --git a/tests/protocol/test_session_id.py b/tests/protocol/test_session_id.py index 9c5a7a3..0a58ae9 100644 --- a/tests/protocol/test_session_id.py +++ b/tests/protocol/test_session_id.py @@ -17,8 +17,8 @@ from tests.protocol._fakes import stream_frame from tests.protocol._fakes import unary_frame from trpc_sdk.abc import get_message -from trpc_sdk.protocol import TCPConnection -from trpc_sdk.protocol._cython import _tcp_protocol +from trpc_sdk.transport import TCPConnection +from trpc_sdk.transport._server._cython import _tcp_protocol from trpc_sdk.transport import TcpConnectionOption diff --git a/tests/server/test_stream_thread_model.py b/tests/server/test_stream_thread_model.py index 621f146..760e7cd 100644 --- a/tests/server/test_stream_thread_model.py +++ b/tests/server/test_stream_thread_model.py @@ -17,8 +17,8 @@ from tests.protocol._fakes import stream_frame from tests.protocol._fakes import unary_frame from trpc_sdk.abc import STREAM_WRITER_CTX_KEY -from trpc_sdk.protocol import ConnWriter -from trpc_sdk.protocol import TCPConnection +from trpc_sdk.transport import ConnWriter +from trpc_sdk.transport import TCPConnection from trpc_sdk.server._server import Server from trpc_sdk.transport import TcpConnectionOption diff --git a/tests/transport/test_default_io_connection.py b/tests/transport/test_default_io_connection.py index 9d79016..f9b9b34 100644 --- a/tests/transport/test_default_io_connection.py +++ b/tests/transport/test_default_io_connection.py @@ -13,7 +13,7 @@ from tests.protocol._fakes import ParserFactory from tests.protocol._fakes import FakeTransport from trpc_sdk.abc import BaseServerTransportOption -from trpc_sdk.protocol import TCPConnection +from trpc_sdk.transport import TCPConnection from trpc_sdk.transport import ConnectionOption from trpc_sdk.transport import DefaultIoConnection from trpc_sdk.transport import TrpcServerTransport diff --git a/trpc_sdk/protocol/__init__.py b/trpc_sdk/protocol/__init__.py deleted file mode 100644 index 12dba0f..0000000 --- a/trpc_sdk/protocol/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# Tencent is pleased to support the open source community by making tRPC-Python available. -# -# Copyright (C) 2026 Tencent. All rights reserved. -# -# tRPC-Python is licensed under Apache-2.0. - -"""tRPC-Python protocol package: asyncio Protocol bound to a listen socket. - -Public symbols are re-exported here. Callers should ``from trpc.protocol -import ...``. - -This package owns the **in-process TCP Protocol** used by merge workers -and multi-listener executors: the Protocol is attached to the accepted -client socket, a per-connection parser splits the byte stream into -frames, and each complete frame is scheduled as an asyncio task that -runs the service handler and writes the response on the same -connection. - -It is not the codec (what a frame means) and not the transport process -model (how frames travel between dispatcher / worker / task). Those -live in :mod:`trpc.codec` and :mod:`trpc.transport`. The UDP counterpart -on the same listen path is -:class:`trpc.transport.DefaultDatagramConnection`. - -Modules: - -- :mod:`~trpc_sdk.protocol._cython._tcp_protocol` — :class:`TCPConnection` and - :func:`handle_request` -- :mod:`~trpc_sdk.protocol._conn_writer` — :class:`ConnWriter`, the handle - that writes on a connection's transport from any thread -""" - -from __future__ import annotations - -from ._conn_writer import ConnWriter -from ._cython import TCPConnection # pylint: disable=no-name-in-module -from ._cython import handle_request # pylint: disable=no-name-in-module - -__all__ = [ - "ConnWriter", - "TCPConnection", - "handle_request", -] diff --git a/trpc_sdk/runtime/__init__.py b/trpc_sdk/runtime/__init__.py index 7f14cf9..69497e4 100644 --- a/trpc_sdk/runtime/__init__.py +++ b/trpc_sdk/runtime/__init__.py @@ -54,7 +54,7 @@ :class:`~trpc_sdk.runtime._threadmodel._separate_thread.SeparateThread` for merge mode with the separate thread model (trpc-cpp ``runtime/threadmodel`` and ``separate_runtime``); responses cross back - to the I/O loops through :class:`~trpc_sdk.protocol.ConnWriter`, + to the I/O loops through :class:`~trpc_sdk.transport.ConnWriter`, re-exported here - :mod:`~trpc_sdk.runtime._common` — SIGTERM callbacks, process metadata and the exit template every mode shares (trpc-cpp ``runtime/common``) @@ -77,7 +77,7 @@ re-exports :func:`register_protocol_handle`. """ -from trpc_sdk.protocol import ConnWriter +from trpc_sdk.transport import ConnWriter from trpc_sdk.transport import register_protocol_handle from ._common import ProcessInfo diff --git a/trpc_sdk/runtime/_threadmodel/_loop_thread.py b/trpc_sdk/runtime/_threadmodel/_loop_thread.py index 30147fd..c7e32ea 100644 --- a/trpc_sdk/runtime/_threadmodel/_loop_thread.py +++ b/trpc_sdk/runtime/_threadmodel/_loop_thread.py @@ -27,7 +27,7 @@ ``transport.write()`` from another thread is not safe. That is why every serving loop opens its own connections to the task processes, and why the separate thread model hands responses back through -:class:`~trpc_sdk.protocol.ConnWriter`. +:class:`~trpc_sdk.transport.ConnWriter`. A loop thread that dies while the worker is serving is not left behind silently: its ``on_exit`` callback tells the worker, which shuts the process diff --git a/trpc_sdk/runtime/_threadmodel/_separate_thread.py b/trpc_sdk/runtime/_threadmodel/_separate_thread.py index f063e48..441fdea 100644 --- a/trpc_sdk/runtime/_threadmodel/_separate_thread.py +++ b/trpc_sdk/runtime/_threadmodel/_separate_thread.py @@ -23,7 +23,7 @@ thread through :class:`~trpc_sdk.runtime._threadmodel._handler_pool.SharedHandlerPool`, and the handle thread hands the response back through the connection's -:class:`~trpc_sdk.protocol.ConnWriter`. Requests of one connection +:class:`~trpc_sdk.transport.ConnWriter`. Requests of one connection may therefore run on several handle threads and complete out of order, which tRPC already allows: responses are matched by request id. diff --git a/trpc_sdk/transport/__init__.py b/trpc_sdk/transport/__init__.py index 73e9697..e95f512 100644 --- a/trpc_sdk/transport/__init__.py +++ b/trpc_sdk/transport/__init__.py @@ -21,11 +21,10 @@ accepts the client TCP, splits frames, and writes them to an rpc task pipe; :class:`RpcTaskConnection` on the rpc task runs the handler and writes the response back -- merge mode: :class:`DefaultDatagramConnection` and the re-exported - :class:`TCPConnection` listen on the merge worker. The TCP Protocol is - owned by :mod:`trpc_sdk.protocol`; this package only forwards the name - so :class:`~trpc_sdk.runtime.MergeWorker` can import both listen - protocols from here. +- merge mode: :class:`DefaultDatagramConnection` and + :class:`TCPConnection` listen on the merge worker. Both live in + :mod:`trpc_sdk.transport._server`, so a merge worker imports them from + here. The package is split by responsibility: @@ -44,9 +43,11 @@ ``UserProtocolTransport`` with the ``register_protocol_handle`` registry, the plugin factory (``DefaultServerTransport*``, :func:`get_server_transport`), and the accepted connections: - ``TcpConnectionOption`` for the Cython ``TCPConnection``, - ``DefaultIoConnection`` with its ``ConnectionOption`` for subclasses - calling ``set_io_connection``, ``DefaultDatagramConnection`` for UDP listen + ``TcpConnectionOption`` and the Cython ``TCPConnection`` + (:mod:`~trpc_sdk.transport._server._cython`), ``ConnWriter`` for + cross-thread writes, ``DefaultIoConnection`` with its ``ConnectionOption`` + for subclasses calling ``set_io_connection``, + ``DefaultDatagramConnection`` for UDP listen sockets (built from the same ``ConnectionOption``), and ``ServerConnection``, the peer view both hand to ``_msg_handler`` - :mod:`~trpc_sdk.transport._separate` — the asyncio protocols on both ends @@ -68,8 +69,6 @@ from __future__ import annotations -from importlib import import_module - from trpc_sdk.abc import BaseServerTransportOption from ._cython import InnerStreamParser # pylint: disable=no-name-in-module @@ -103,6 +102,7 @@ from ._client import get_client_transport from ._client import get_default_client_transport from ._client import open_datagram_endpoint +from ._server import ConnWriter from ._server import ConnectionCloseHandler from ._server import ConnectionIdleCleanHandler from ._server import ConnectionInitHandler @@ -115,6 +115,7 @@ from ._server import MsgHandler from ._server import ProtocolStarter from ._server import ServerConnection +from ._server import TCPConnection from ._server import TcpConnectionOption from ._server import TrpcServerStreamTransport from ._server import TrpcServerTransport @@ -152,6 +153,8 @@ "TrpcServerStreamTransport", "UserProtocolTransport", "IpcStreamWriter", + "ConnWriter", + "TCPConnection", "TcpConnectionOption", "DefaultIoConnection", "ConnectionOption", @@ -177,8 +180,6 @@ "SeparateRpczConnection", # Separate-mode rpc task protocol "RpcTaskConnection", - # Executor / merge-worker listen protocols - "TCPConnection", # Shared types and hooks "ClientConnectionInfo", "SchedulerParams", @@ -192,24 +193,3 @@ "InnerStreamParser", "encode_worker_msg_req", ] - - -_LAZY_EXPORTS = { - "TCPConnection": ("trpc_sdk.protocol", "TCPConnection"), -} - - -def __getattr__(name: str): - """Lazily export symbols that would otherwise load too early. - - :class:`TCPConnection` is owned by :mod:`trpc_sdk.protocol` and is only - needed once a merge worker is already serving. Resolving it on first - access is enough. - """ - spec = _LAZY_EXPORTS.get(name) - if spec is None: - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") - module_name, attr_name = spec - value = getattr(import_module(module_name, package=__name__), attr_name) - globals()[name] = value - return value diff --git a/trpc_sdk/transport/_server/__init__.py b/trpc_sdk/transport/_server/__init__.py index a9a766e..ca4be9e 100644 --- a/trpc_sdk/transport/_server/__init__.py +++ b/trpc_sdk/transport/_server/__init__.py @@ -33,6 +33,11 @@ ``set_io_connection``, with its :class:`ConnectionOption` and the ``MsgHandler`` / ``Connection*Handler`` callback types it holds; and :class:`ServerConnection`, the peer view ``_msg_handler`` reads +- :mod:`~trpc_sdk.transport._server._cython` — :class:`TCPConnection`, the + Cython asyncio Protocol for one accepted client TCP socket, and + :func:`handle_request` +- :mod:`~trpc_sdk.transport._server._conn_writer` — :class:`ConnWriter`, the + handle that writes on a connection's transport from any thread - :mod:`~trpc_sdk.transport._server._datagram_connection` — :class:`DefaultDatagramConnection`, the datagram protocol of a tRPC-over-UDP listen socket, which builds one peer view per datagram @@ -53,6 +58,8 @@ from ._acceptor import Acceptor from ._acceptor import StreamListener from ._acceptor import serve_stream +from ._conn_writer import ConnWriter +from ._cython import TCPConnection # pylint: disable=no-name-in-module from ._datagram_connection import DefaultDatagramConnection from ._stream_connection import BaseConnection from ._stream_connection import ConnectionCloseHandler @@ -81,6 +88,7 @@ "AcceptTarget", "Acceptor", "BaseConnection", + "ConnWriter", "ConnectionCloseHandler", "ConnectionIdleCleanHandler", "ConnectionInitHandler", @@ -92,6 +100,7 @@ "MsgHandler", "ServerConnection", "StreamListener", + "TCPConnection", "TcpConnectionOption", "TrpcServerTransport", "TrpcServerStreamTransport", diff --git a/trpc_sdk/protocol/_conn_writer.py b/trpc_sdk/transport/_server/_conn_writer.py similarity index 95% rename from trpc_sdk/protocol/_conn_writer.py rename to trpc_sdk/transport/_server/_conn_writer.py index dacea6d..2a3c9cf 100644 --- a/trpc_sdk/protocol/_conn_writer.py +++ b/trpc_sdk/transport/_server/_conn_writer.py @@ -22,8 +22,10 @@ keep writing (DATA, CLOSE, RESET) after the client left, and the writer's ``closed`` flag is what drops those writes. -It lives in the protocol package because the TCP protocol builds it itself; -the protocol package does not import the server package. +It lives beside the TCP protocol because that protocol builds the writer +itself. The Cython module imports it with a relative import and does not +import :mod:`trpc_sdk.transport`: that package's ``__init__`` loads this +module. Every cross-thread write costs one ``call_soon_threadsafe``; wake-ups are not coalesced in this version. diff --git a/trpc_sdk/protocol/_cython/__init__.py b/trpc_sdk/transport/_server/_cython/__init__.py similarity index 100% rename from trpc_sdk/protocol/_cython/__init__.py rename to trpc_sdk/transport/_server/_cython/__init__.py diff --git a/trpc_sdk/protocol/_cython/_tcp_protocol.pxd b/trpc_sdk/transport/_server/_cython/_tcp_protocol.pxd similarity index 100% rename from trpc_sdk/protocol/_cython/_tcp_protocol.pxd rename to trpc_sdk/transport/_server/_cython/_tcp_protocol.pxd diff --git a/trpc_sdk/protocol/_cython/_tcp_protocol.pyi b/trpc_sdk/transport/_server/_cython/_tcp_protocol.pyi similarity index 100% rename from trpc_sdk/protocol/_cython/_tcp_protocol.pyi rename to trpc_sdk/transport/_server/_cython/_tcp_protocol.pyi diff --git a/trpc_sdk/protocol/_cython/_tcp_protocol.pyx b/trpc_sdk/transport/_server/_cython/_tcp_protocol.pyx similarity index 98% rename from trpc_sdk/protocol/_cython/_tcp_protocol.pyx rename to trpc_sdk/transport/_server/_cython/_tcp_protocol.pyx index f85f1c3..5632cc4 100644 --- a/trpc_sdk/protocol/_cython/_tcp_protocol.pyx +++ b/trpc_sdk/transport/_server/_cython/_tcp_protocol.pyx @@ -43,7 +43,7 @@ from trpc_sdk.abc import TrpcContext from trpc_sdk.abc import get_message from trpc_sdk.exceptions import ServerDecodeError from trpc_sdk.log import logger -from trpc_sdk.protocol._conn_writer import ConnWriter +from .._conn_writer import ConnWriter from trpc_sdk.rpcz import RPCZ_SERVER_REQ_SIZE_CTX_KEY from trpc_sdk.rpcz import RPCZ_SERVER_RSP_SIZE_CTX_KEY from trpc_sdk.rpcz import submit_server_span_on_executor_post_io @@ -178,8 +178,9 @@ cdef class TCPConnection: """Copy the listener's options into this connection's C-level slots. ``option`` is read by attribute name only, so this module does not - import :mod:`trpc_sdk.transport`. It is not kept: the hot path reads - the copied slots. + import :mod:`trpc_sdk.transport`. That package's ``__init__`` loads + this module; a back-import would run while it is still loading. + The option is not kept: the hot path reads the copied slots. Args: option: A :class:`~trpc_sdk.transport.ConnectionOption`. diff --git a/trpc_sdk/transport/_server/_datagram_connection.py b/trpc_sdk/transport/_server/_datagram_connection.py index 7579085..c6928fb 100644 --- a/trpc_sdk/transport/_server/_datagram_connection.py +++ b/trpc_sdk/transport/_server/_datagram_connection.py @@ -28,9 +28,9 @@ from trpc_sdk.exceptions import ServerDecodeError from trpc_sdk.log import logger -from trpc_sdk.protocol import ConnWriter from trpc_sdk.telemetry import report_attr +from ._conn_writer import ConnWriter from ._stream_connection import ConnectionOption from ._stream_connection import _to_host_port diff --git a/trpc_sdk/transport/_server/_stream_transport.py b/trpc_sdk/transport/_server/_stream_transport.py index de163ab..ea623d0 100644 --- a/trpc_sdk/transport/_server/_stream_transport.py +++ b/trpc_sdk/transport/_server/_stream_transport.py @@ -15,7 +15,7 @@ business: - merge mode (every thread model): the connection's - :class:`~trpc_sdk.protocol.ConnWriter`, which writes the raw frame on the + :class:`~trpc_sdk.transport.ConnWriter`, which writes the raw frame on the client TCP from whichever thread the stream runs on, and drops writes once the connection is closed; - ``server.mode: separate``: an :class:`IpcStreamWriter` on the rpc task's diff --git a/trpc_sdk/transport/_server/_trpc_transport.py b/trpc_sdk/transport/_server/_trpc_transport.py index 715cc1d..12cf045 100644 --- a/trpc_sdk/transport/_server/_trpc_transport.py +++ b/trpc_sdk/transport/_server/_trpc_transport.py @@ -47,7 +47,6 @@ from trpc_sdk.abc import TrpcContext from trpc_sdk.abc import get_message from trpc_sdk.log import logger -from trpc_sdk.protocol import TCPConnection from trpc_sdk.rpcz import RPCZ_SERVER_REQ_SIZE_CTX_KEY from trpc_sdk.rpcz import RPCZ_SERVER_RSP_SIZE_CTX_KEY from trpc_sdk.rpcz import submit_server_span_on_executor_post_io @@ -63,6 +62,7 @@ from ._acceptor import StreamListener from ._acceptor import serve_stream from ._constants import SERVER_CONN_KEY +from ._cython import TCPConnection # pylint: disable=no-name-in-module from ._datagram_connection import DefaultDatagramConnection from ._stream_connection import ConnectionOption from ._stream_connection import DefaultIoConnection