Skip to content

Commit 19bc2a4

Browse files
authored
Merge pull request #8 from modelscope/update/sandbox_pool_doc
update sandbox pool doc
2 parents 13770a0 + b5f93f5 commit 19bc2a4

File tree

4 files changed

+232
-3
lines changed

4 files changed

+232
-3
lines changed

README.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,56 @@ asyncio.run(main())
9393
- Using LocalSandboxManager: Uniformly orchestrate lifecycle/cleanup of multiple sandboxes on local machine; suitable for service-oriented, multi-task parallel scenarios
9494
- Using HttpSandboxManager: Manage sandboxes uniformly through remote HTTP service; suitable for cross-machine/distributed or stronger isolation deployments
9595

96+
### 0) Manager Factory: SandboxManagerFactory (Automatic Local/HTTP selection)
97+
98+
When to use:
99+
- You want a single entry point that chooses Local or HTTP manager automatically.
100+
- You prefer central registration and discovery of available manager types.
101+
102+
Key points:
103+
- If manager_type is provided, it is used directly.
104+
- If base_url is provided (in config or kwargs), HTTP manager is created.
105+
- Otherwise, Local manager is created by default.
106+
107+
Example: implicit selection by base_url
108+
```python
109+
import asyncio
110+
from ms_enclave.sandbox.manager import SandboxManagerFactory
111+
112+
async def main():
113+
async with SandboxManagerFactory.create_manager(base_url='http://127.0.0.1:8000') as m:
114+
# Use exactly like HttpSandboxManager
115+
# e.g., create a DOCKER sandbox and execute a tool
116+
# ... your code ...
117+
pass
118+
119+
asyncio.run(main())
120+
```
121+
122+
Example: explicit selection + custom config
123+
```python
124+
import asyncio
125+
from ms_enclave.sandbox.manager import SandboxManagerFactory
126+
from ms_enclave.sandbox.model import SandboxManagerConfig, SandboxManagerType
127+
128+
async def main():
129+
cfg = SandboxManagerConfig(cleanup_interval=600)
130+
async with SandboxManagerFactory.create_manager(
131+
manager_type=SandboxManagerType.LOCAL, config=cfg
132+
) as m:
133+
# Use exactly like LocalSandboxManager
134+
# ... your code ...
135+
pass
136+
137+
asyncio.run(main())
138+
```
139+
140+
Discover registered manager types:
141+
```python
142+
from ms_enclave.sandbox.manager import SandboxManagerFactory
143+
print(SandboxManagerFactory.get_registered_types())
144+
```
145+
96146
### 1) Direct Sandbox Creation: SandboxFactory (Lightweight, Temporary)
97147

98148
Use Cases:
@@ -189,6 +239,66 @@ async def main():
189239
asyncio.run(main())
190240
```
191241

242+
### 4) Pooled Sandboxes: Pre-warmed workers (Sandbox Pool)
243+
244+
Why:
245+
- Amortize container startup by keeping a fixed-size pool of ready sandboxes.
246+
- Each execution borrows a sandbox and returns it; requests queue FIFO when all are busy.
247+
248+
Local pool example:
249+
250+
```python
251+
import asyncio
252+
from ms_enclave.sandbox.manager import LocalSandboxManager
253+
from ms_enclave.sandbox.model import DockerSandboxConfig, SandboxType
254+
255+
async def main():
256+
async with LocalSandboxManager() as m:
257+
cfg = DockerSandboxConfig(
258+
image='python:3.11-slim',
259+
tools_config={'python_executor': {}}
260+
)
261+
# Create a pool of 2 pre-warmed sandboxes
262+
await m.initialize_pool(pool_size=2, sandbox_type=SandboxType.DOCKER, config=cfg)
263+
264+
# Execute multiple tasks; sandboxes are reused and queued FIFO when busy
265+
tasks = [
266+
m.execute_tool_in_pool('python_executor', {'code': f'print("task {i}")', 'timeout': 30})
267+
for i in range(5)
268+
]
269+
results = await asyncio.gather(*tasks)
270+
print([r.output.strip() for r in results])
271+
272+
# Pool stats
273+
stats = await m.get_stats()
274+
print('pool_size =', stats['pool_size'])
275+
276+
asyncio.run(main())
277+
```
278+
279+
HTTP pool example:
280+
281+
```python
282+
import asyncio
283+
from ms_enclave.sandbox.manager import HttpSandboxManager
284+
from ms_enclave.sandbox.model import DockerSandboxConfig, SandboxType
285+
286+
async def main():
287+
async with HttpSandboxManager(base_url='http://127.0.0.1:8000') as m:
288+
cfg = DockerSandboxConfig(image='python:3.11-slim', tools_config={'python_executor': {}})
289+
await m.initialize_pool(pool_size=2, sandbox_type=SandboxType.DOCKER, config=cfg)
290+
291+
r = await m.execute_tool_in_pool('python_executor', {'code': 'print("hello from pool")', 'timeout': 30})
292+
print(r.output)
293+
294+
asyncio.run(main())
295+
```
296+
297+
Notes:
298+
- Waiting timeout: `await m.execute_tool_in_pool(..., timeout=1.0)` raises `TimeoutError` if no sandbox is available in time.
299+
- FIFO behavior: pool borrows/returns in FIFO order under load.
300+
- Errors: even if a tool execution fails, the sandbox is returned to the pool.
301+
192302
---
193303

194304
## Sandbox Types & Tool Support
@@ -233,6 +343,10 @@ DockerNotebookConfig(tools_config={'notebook_executor': {}})
233343
- `network_enabled`: Whether to enable network (Notebook sandbox requires True)
234344
- `remove_on_exit`: Whether to delete container on exit (default True)
235345

346+
Manager Config (SandboxManagerConfig):
347+
- `base_url`: If set, HttpSandboxManager is selected automatically
348+
- `cleanup_interval`: Background cleanup interval in seconds (local manager)
349+
236350
**Example of Installing Additional Dependencies in Sandbox**
237351
```python
238352
async with SandboxFactory.create_sandbox(SandboxType.DOCKER, config) as sandbox:

README_zh.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,56 @@ asyncio.run(main())
9595
- 使用 LocalSandboxManager:在本机统一编排多个沙箱的生命周期/清理;适合服务化、多任务并行场景
9696
- 使用 HttpSandboxManager:通过远程 HTTP 服务统一管理沙箱;适合跨机/分布式或隔离更强的部署
9797

98+
### 0) 管理器工厂:SandboxManagerFactory(自动选择本地/HTTP)
99+
100+
适用场景:
101+
- 希望用一个入口根据参数自动选择本地或 HTTP 管理器
102+
- 需要查询已注册的管理器类型,或统一构造逻辑
103+
104+
要点:
105+
- 显式传入 manager_type 时,按类型创建
106+
- 当提供 base_url(在 config 或 kwargs)时,创建 HTTP 管理器
107+
- 两者都未提供时,默认创建本地管理器
108+
109+
示例:通过 base_url 隐式选择 HTTP 管理器
110+
```python
111+
import asyncio
112+
from ms_enclave.sandbox.manager import SandboxManagerFactory
113+
114+
async def main():
115+
async with SandboxManagerFactory.create_manager(base_url='http://127.0.0.1:8000') as m:
116+
# 与 HttpSandboxManager 用法一致
117+
# 例如:创建 DOCKER 沙箱并执行工具
118+
# ... 你的代码 ...
119+
pass
120+
121+
asyncio.run(main())
122+
```
123+
124+
示例:显式选择 + 自定义配置
125+
```python
126+
import asyncio
127+
from ms_enclave.sandbox.manager import SandboxManagerFactory
128+
from ms_enclave.sandbox.model import SandboxManagerConfig, SandboxManagerType
129+
130+
async def main():
131+
cfg = SandboxManagerConfig(cleanup_interval=600)
132+
async with SandboxManagerFactory.create_manager(
133+
manager_type=SandboxManagerType.LOCAL, config=cfg
134+
) as m:
135+
# 与 LocalSandboxManager 用法一致
136+
# ... 你的代码 ...
137+
pass
138+
139+
asyncio.run(main())
140+
```
141+
142+
查看已注册类型:
143+
```python
144+
from ms_enclave.sandbox.manager import SandboxManagerFactory
145+
print(SandboxManagerFactory.get_registered_types())
146+
```
147+
98148
### 1) 直接创建沙箱:SandboxFactory(轻量、临时)
99149

100150
适用场景:
@@ -191,6 +241,66 @@ async def main():
191241
asyncio.run(main())
192242
```
193243

244+
### 4) 沙箱池:预热复用的工作进程(Sandbox Pool)
245+
246+
为何使用:
247+
- 通过预热固定数量的沙箱,摊销容器启动开销,提高吞吐。
248+
- 每次执行从池中借出沙箱并在完成后归还;当全部忙碌时按 FIFO 排队。
249+
250+
本地管理示例:
251+
252+
```python
253+
import asyncio
254+
from ms_enclave.sandbox.manager import LocalSandboxManager
255+
from ms_enclave.sandbox.model import DockerSandboxConfig, SandboxType
256+
257+
async def main():
258+
async with LocalSandboxManager() as m:
259+
cfg = DockerSandboxConfig(
260+
image='python:3.11-slim',
261+
tools_config={'python_executor': {}}
262+
)
263+
# 预热 2 个沙箱
264+
await m.initialize_pool(pool_size=2, sandbox_type=SandboxType.DOCKER, config=cfg)
265+
266+
# 多次执行;忙时按 FIFO 排队,执行完成后归还至池中
267+
tasks = [
268+
m.execute_tool_in_pool('python_executor', {'code': f'print("task {i}")', 'timeout': 30})
269+
for i in range(5)
270+
]
271+
results = await asyncio.gather(*tasks)
272+
print([r.output.strip() for r in results])
273+
274+
# 查看统计
275+
stats = await m.get_stats()
276+
print('pool_size =', stats['pool_size'])
277+
278+
asyncio.run(main())
279+
```
280+
281+
HTTP 管理示例:
282+
283+
```python
284+
import asyncio
285+
from ms_enclave.sandbox.manager import HttpSandboxManager
286+
from ms_enclave.sandbox.model import DockerSandboxConfig, SandboxType
287+
288+
async def main():
289+
async with HttpSandboxManager(base_url='http://127.0.0.1:8000') as m:
290+
cfg = DockerSandboxConfig(image='python:3.11-slim', tools_config={'python_executor': {}})
291+
await m.initialize_pool(pool_size=2, sandbox_type=SandboxType.DOCKER, config=cfg)
292+
293+
r = await m.execute_tool_in_pool('python_executor', {'code': 'print("hello from pool")', 'timeout': 30})
294+
print(r.output)
295+
296+
asyncio.run(main())
297+
```
298+
299+
说明:
300+
- 等待超时:`await m.execute_tool_in_pool(..., timeout=1.0)` 若在超时时间内无可用沙箱将抛出 `TimeoutError`
301+
- FIFO 行为:在并发负载下,借还顺序遵循 FIFO。
302+
- 错误处理:即使执行失败,沙箱也会归还至池中。
303+
194304
---
195305

196306
## 沙箱类型与工具支持
@@ -235,6 +345,10 @@ DockerNotebookConfig(tools_config={'notebook_executor': {}})
235345
- `network_enabled`: 是否启用网络(Notebook 沙箱需 True)
236346
- `remove_on_exit`: 退出后是否删除容器(默认 True)
237347

348+
管理器配置(SandboxManagerConfig):
349+
- `base_url`:若设置则自动选择 HttpSandboxManager
350+
- `cleanup_interval`:本地管理器的后台清理间隔(秒)
351+
238352
**Sandbox中安装额外依赖示例**
239353
```python
240354
async with SandboxFactory.create_sandbox(SandboxType.DOCKER, config) as sandbox:

ms_enclave/sandbox/manager/local_manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
"""Sandbox environment manager."""
22

33
import asyncio
4-
from collections import Counter, deque
4+
from collections import Counter
55
from datetime import datetime, timedelta
66
from typing import Any, Dict, List, Optional, Union
77

8+
from ms_enclave.sandbox.model.constants import DEFAULT_POOL_EXECUTION_TIMEOUT
89
from ms_enclave.utils import get_logger
910

1011
from ..boxes import Sandbox, SandboxFactory
@@ -372,7 +373,6 @@ async def execute_tool_in_pool(
372373
Args:
373374
tool_name: Tool name to execute
374375
parameters: Tool parameters
375-
timeout: Optional timeout for waiting for available sandbox
376376
377377
Returns:
378378
Tool execution result
@@ -387,7 +387,7 @@ async def execute_tool_in_pool(
387387
if not self._pool_condition:
388388
raise RuntimeError('Sandbox manager not started')
389389

390-
timeout = timeout or self.config.timeout
390+
timeout = timeout or self.config.timeout or DEFAULT_POOL_EXECUTION_TIMEOUT
391391

392392
async with self._pool_condition:
393393
# Wait for an available sandbox
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DEFAULT_POOL_EXECUTION_TIMEOUT = 3600 # 1 hour

0 commit comments

Comments
 (0)