diff --git a/README.md b/README.md index aa6afae..0993cb7 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,56 @@ asyncio.run(main()) - Using LocalSandboxManager: Uniformly orchestrate lifecycle/cleanup of multiple sandboxes on local machine; suitable for service-oriented, multi-task parallel scenarios - Using HttpSandboxManager: Manage sandboxes uniformly through remote HTTP service; suitable for cross-machine/distributed or stronger isolation deployments +### 0) Manager Factory: SandboxManagerFactory (Automatic Local/HTTP selection) + +When to use: +- You want a single entry point that chooses Local or HTTP manager automatically. +- You prefer central registration and discovery of available manager types. + +Key points: +- If manager_type is provided, it is used directly. +- If base_url is provided (in config or kwargs), HTTP manager is created. +- Otherwise, Local manager is created by default. + +Example: implicit selection by base_url +```python +import asyncio +from ms_enclave.sandbox.manager import SandboxManagerFactory + +async def main(): + async with SandboxManagerFactory.create_manager(base_url='http://127.0.0.1:8000') as m: + # Use exactly like HttpSandboxManager + # e.g., create a DOCKER sandbox and execute a tool + # ... your code ... + pass + +asyncio.run(main()) +``` + +Example: explicit selection + custom config +```python +import asyncio +from ms_enclave.sandbox.manager import SandboxManagerFactory +from ms_enclave.sandbox.model import SandboxManagerConfig, SandboxManagerType + +async def main(): + cfg = SandboxManagerConfig(cleanup_interval=600) + async with SandboxManagerFactory.create_manager( + manager_type=SandboxManagerType.LOCAL, config=cfg + ) as m: + # Use exactly like LocalSandboxManager + # ... your code ... + pass + +asyncio.run(main()) +``` + +Discover registered manager types: +```python +from ms_enclave.sandbox.manager import SandboxManagerFactory +print(SandboxManagerFactory.get_registered_types()) +``` + ### 1) Direct Sandbox Creation: SandboxFactory (Lightweight, Temporary) Use Cases: @@ -189,6 +239,66 @@ async def main(): asyncio.run(main()) ``` +### 4) Pooled Sandboxes: Pre-warmed workers (Sandbox Pool) + +Why: +- Amortize container startup by keeping a fixed-size pool of ready sandboxes. +- Each execution borrows a sandbox and returns it; requests queue FIFO when all are busy. + +Local pool example: + +```python +import asyncio +from ms_enclave.sandbox.manager import LocalSandboxManager +from ms_enclave.sandbox.model import DockerSandboxConfig, SandboxType + +async def main(): + async with LocalSandboxManager() as m: + cfg = DockerSandboxConfig( + image='python:3.11-slim', + tools_config={'python_executor': {}} + ) + # Create a pool of 2 pre-warmed sandboxes + await m.initialize_pool(pool_size=2, sandbox_type=SandboxType.DOCKER, config=cfg) + + # Execute multiple tasks; sandboxes are reused and queued FIFO when busy + tasks = [ + m.execute_tool_in_pool('python_executor', {'code': f'print("task {i}")', 'timeout': 30}) + for i in range(5) + ] + results = await asyncio.gather(*tasks) + print([r.output.strip() for r in results]) + + # Pool stats + stats = await m.get_stats() + print('pool_size =', stats['pool_size']) + +asyncio.run(main()) +``` + +HTTP pool example: + +```python +import asyncio +from ms_enclave.sandbox.manager import HttpSandboxManager +from ms_enclave.sandbox.model import DockerSandboxConfig, SandboxType + +async def main(): + async with HttpSandboxManager(base_url='http://127.0.0.1:8000') as m: + cfg = DockerSandboxConfig(image='python:3.11-slim', tools_config={'python_executor': {}}) + await m.initialize_pool(pool_size=2, sandbox_type=SandboxType.DOCKER, config=cfg) + + r = await m.execute_tool_in_pool('python_executor', {'code': 'print("hello from pool")', 'timeout': 30}) + print(r.output) + +asyncio.run(main()) +``` + +Notes: +- Waiting timeout: `await m.execute_tool_in_pool(..., timeout=1.0)` raises `TimeoutError` if no sandbox is available in time. +- FIFO behavior: pool borrows/returns in FIFO order under load. +- Errors: even if a tool execution fails, the sandbox is returned to the pool. + --- ## Sandbox Types & Tool Support @@ -233,6 +343,10 @@ DockerNotebookConfig(tools_config={'notebook_executor': {}}) - `network_enabled`: Whether to enable network (Notebook sandbox requires True) - `remove_on_exit`: Whether to delete container on exit (default True) +Manager Config (SandboxManagerConfig): +- `base_url`: If set, HttpSandboxManager is selected automatically +- `cleanup_interval`: Background cleanup interval in seconds (local manager) + **Example of Installing Additional Dependencies in Sandbox** ```python async with SandboxFactory.create_sandbox(SandboxType.DOCKER, config) as sandbox: diff --git a/README_zh.md b/README_zh.md index d4c330b..d1ed19d 100644 --- a/README_zh.md +++ b/README_zh.md @@ -95,6 +95,56 @@ asyncio.run(main()) - 使用 LocalSandboxManager:在本机统一编排多个沙箱的生命周期/清理;适合服务化、多任务并行场景 - 使用 HttpSandboxManager:通过远程 HTTP 服务统一管理沙箱;适合跨机/分布式或隔离更强的部署 +### 0) 管理器工厂:SandboxManagerFactory(自动选择本地/HTTP) + +适用场景: +- 希望用一个入口根据参数自动选择本地或 HTTP 管理器 +- 需要查询已注册的管理器类型,或统一构造逻辑 + +要点: +- 显式传入 manager_type 时,按类型创建 +- 当提供 base_url(在 config 或 kwargs)时,创建 HTTP 管理器 +- 两者都未提供时,默认创建本地管理器 + +示例:通过 base_url 隐式选择 HTTP 管理器 +```python +import asyncio +from ms_enclave.sandbox.manager import SandboxManagerFactory + +async def main(): + async with SandboxManagerFactory.create_manager(base_url='http://127.0.0.1:8000') as m: + # 与 HttpSandboxManager 用法一致 + # 例如:创建 DOCKER 沙箱并执行工具 + # ... 你的代码 ... + pass + +asyncio.run(main()) +``` + +示例:显式选择 + 自定义配置 +```python +import asyncio +from ms_enclave.sandbox.manager import SandboxManagerFactory +from ms_enclave.sandbox.model import SandboxManagerConfig, SandboxManagerType + +async def main(): + cfg = SandboxManagerConfig(cleanup_interval=600) + async with SandboxManagerFactory.create_manager( + manager_type=SandboxManagerType.LOCAL, config=cfg + ) as m: + # 与 LocalSandboxManager 用法一致 + # ... 你的代码 ... + pass + +asyncio.run(main()) +``` + +查看已注册类型: +```python +from ms_enclave.sandbox.manager import SandboxManagerFactory +print(SandboxManagerFactory.get_registered_types()) +``` + ### 1) 直接创建沙箱:SandboxFactory(轻量、临时) 适用场景: @@ -191,6 +241,66 @@ async def main(): asyncio.run(main()) ``` +### 4) 沙箱池:预热复用的工作进程(Sandbox Pool) + +为何使用: +- 通过预热固定数量的沙箱,摊销容器启动开销,提高吞吐。 +- 每次执行从池中借出沙箱并在完成后归还;当全部忙碌时按 FIFO 排队。 + +本地管理示例: + +```python +import asyncio +from ms_enclave.sandbox.manager import LocalSandboxManager +from ms_enclave.sandbox.model import DockerSandboxConfig, SandboxType + +async def main(): + async with LocalSandboxManager() as m: + cfg = DockerSandboxConfig( + image='python:3.11-slim', + tools_config={'python_executor': {}} + ) + # 预热 2 个沙箱 + await m.initialize_pool(pool_size=2, sandbox_type=SandboxType.DOCKER, config=cfg) + + # 多次执行;忙时按 FIFO 排队,执行完成后归还至池中 + tasks = [ + m.execute_tool_in_pool('python_executor', {'code': f'print("task {i}")', 'timeout': 30}) + for i in range(5) + ] + results = await asyncio.gather(*tasks) + print([r.output.strip() for r in results]) + + # 查看统计 + stats = await m.get_stats() + print('pool_size =', stats['pool_size']) + +asyncio.run(main()) +``` + +HTTP 管理示例: + +```python +import asyncio +from ms_enclave.sandbox.manager import HttpSandboxManager +from ms_enclave.sandbox.model import DockerSandboxConfig, SandboxType + +async def main(): + async with HttpSandboxManager(base_url='http://127.0.0.1:8000') as m: + cfg = DockerSandboxConfig(image='python:3.11-slim', tools_config={'python_executor': {}}) + await m.initialize_pool(pool_size=2, sandbox_type=SandboxType.DOCKER, config=cfg) + + r = await m.execute_tool_in_pool('python_executor', {'code': 'print("hello from pool")', 'timeout': 30}) + print(r.output) + +asyncio.run(main()) +``` + +说明: +- 等待超时:`await m.execute_tool_in_pool(..., timeout=1.0)` 若在超时时间内无可用沙箱将抛出 `TimeoutError`。 +- FIFO 行为:在并发负载下,借还顺序遵循 FIFO。 +- 错误处理:即使执行失败,沙箱也会归还至池中。 + --- ## 沙箱类型与工具支持 @@ -235,6 +345,10 @@ DockerNotebookConfig(tools_config={'notebook_executor': {}}) - `network_enabled`: 是否启用网络(Notebook 沙箱需 True) - `remove_on_exit`: 退出后是否删除容器(默认 True) +管理器配置(SandboxManagerConfig): +- `base_url`:若设置则自动选择 HttpSandboxManager +- `cleanup_interval`:本地管理器的后台清理间隔(秒) + **Sandbox中安装额外依赖示例** ```python async with SandboxFactory.create_sandbox(SandboxType.DOCKER, config) as sandbox: diff --git a/ms_enclave/sandbox/manager/local_manager.py b/ms_enclave/sandbox/manager/local_manager.py index 117f47e..5b60a4f 100644 --- a/ms_enclave/sandbox/manager/local_manager.py +++ b/ms_enclave/sandbox/manager/local_manager.py @@ -1,10 +1,11 @@ """Sandbox environment manager.""" import asyncio -from collections import Counter, deque +from collections import Counter from datetime import datetime, timedelta from typing import Any, Dict, List, Optional, Union +from ms_enclave.sandbox.model.constants import DEFAULT_POOL_EXECUTION_TIMEOUT from ms_enclave.utils import get_logger from ..boxes import Sandbox, SandboxFactory @@ -372,7 +373,6 @@ async def execute_tool_in_pool( Args: tool_name: Tool name to execute parameters: Tool parameters - timeout: Optional timeout for waiting for available sandbox Returns: Tool execution result @@ -387,7 +387,7 @@ async def execute_tool_in_pool( if not self._pool_condition: raise RuntimeError('Sandbox manager not started') - timeout = timeout or self.config.timeout + timeout = timeout or self.config.timeout or DEFAULT_POOL_EXECUTION_TIMEOUT async with self._pool_condition: # Wait for an available sandbox diff --git a/ms_enclave/sandbox/model/constants.py b/ms_enclave/sandbox/model/constants.py new file mode 100644 index 0000000..c880982 --- /dev/null +++ b/ms_enclave/sandbox/model/constants.py @@ -0,0 +1 @@ +DEFAULT_POOL_EXECUTION_TIMEOUT = 3600 # 1 hour