feat(plugin-runtime): add plugin isolation IPC infrastructure
- Protocol layer: Envelope model with Pydantic schema, MsgPack/JSON codecs, unified error codes - Transport layer: cross-platform IPC abstraction with 4-byte length-prefixed framing (UDS + TCP fallback) - Host: RPC server, policy engine, circuit breaker, capability service, supervisor with hot-reload - Runner: RPC client, plugin loader, process entry point - Tests: 16 passing tests covering protocol, transport, host, and E2E handshake
This commit is contained in:
71
src/plugin_runtime/transport/uds.py
Normal file
71
src/plugin_runtime/transport/uds.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""Unix Domain Socket 传输实现
|
||||
|
||||
适用于 Linux / macOS 平台。
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from .base import Connection, ConnectionHandler, TransportClient, TransportServer
|
||||
|
||||
|
||||
class UDSConnection(Connection):
|
||||
"""基于 UDS 的连接"""
|
||||
pass # 直接复用 Connection 基类的分帧读写
|
||||
|
||||
|
||||
class UDSTransportServer(TransportServer):
|
||||
"""UDS 传输服务端"""
|
||||
|
||||
def __init__(self, socket_path: str | None = None):
|
||||
if socket_path is None:
|
||||
# 默认放在临时目录
|
||||
socket_path = os.path.join(tempfile.gettempdir(), f"maibot-plugin-{os.getpid()}.sock")
|
||||
self._socket_path = socket_path
|
||||
self._server: asyncio.AbstractServer | None = None
|
||||
|
||||
async def start(self, handler: ConnectionHandler) -> None:
|
||||
# 清理残留 socket 文件
|
||||
if os.path.exists(self._socket_path):
|
||||
os.unlink(self._socket_path)
|
||||
|
||||
# 确保父目录存在
|
||||
Path(self._socket_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
async def _on_connect(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
|
||||
conn = UDSConnection(reader, writer)
|
||||
try:
|
||||
await handler(conn)
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
self._server = await asyncio.start_unix_server(_on_connect, path=self._socket_path)
|
||||
|
||||
# 设置文件权限为仅当前用户可访问
|
||||
os.chmod(self._socket_path, 0o600)
|
||||
|
||||
async def stop(self) -> None:
|
||||
if self._server:
|
||||
self._server.close()
|
||||
await self._server.wait_closed()
|
||||
self._server = None
|
||||
# 清理 socket 文件
|
||||
if os.path.exists(self._socket_path):
|
||||
os.unlink(self._socket_path)
|
||||
|
||||
def get_address(self) -> str:
|
||||
return self._socket_path
|
||||
|
||||
|
||||
class UDSTransportClient(TransportClient):
|
||||
"""UDS 传输客户端"""
|
||||
|
||||
def __init__(self, socket_path: str):
|
||||
self._socket_path = socket_path
|
||||
|
||||
async def connect(self) -> Connection:
|
||||
reader, writer = await asyncio.open_unix_connection(self._socket_path)
|
||||
return UDSConnection(reader, writer)
|
||||
Reference in New Issue
Block a user