Skip to content

Commit b1db3a6

Browse files
perf: simplify initialization of ToolExecutor.
1 parent 9539082 commit b1db3a6

1 file changed

Lines changed: 30 additions & 36 deletions

File tree

apps/common/utils/tool_code.py

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,40 +10,32 @@
1010
import tempfile
1111
import pwd
1212
import resource
13+
import getpass
1314
import uuid_utils.compat as uuid
1415
from common.utils.logger import maxkb_logger
1516
from django.utils.translation import gettext_lazy as _
1617
from maxkb.const import BASE_DIR, CONFIG
1718
from maxkb.const import PROJECT_DIR
1819
from textwrap import dedent
1920

20-
python_directory = sys.executable
21-
22-
2321
class ToolExecutor:
2422

23+
enable_sandbox = bool(CONFIG.get('SANDBOX', 0))
24+
sandbox_path = CONFIG.get("SANDBOX_HOME", '/opt/maxkb-app/sandbox') if enable_sandbox else os.path.join(PROJECT_DIR, 'data', 'sandbox')
25+
process_timeout_seconds = int(CONFIG.get("SANDBOX_PYTHON_PROCESS_TIMEOUT_SECONDS", '3600'))
26+
process_limit_mem_mb = int(CONFIG.get("SANDBOX_PYTHON_PROCESS_LIMIT_MEM_MB", '256'))
27+
2528
def __init__(self, sandbox=False):
2629
self.sandbox = sandbox
2730
if sandbox:
28-
self.sandbox_path = CONFIG.get("SANDBOX_HOME", '/opt/maxkb-app/sandbox')
2931
self.user = 'sandbox'
3032
else:
31-
self.sandbox_path = os.path.join(PROJECT_DIR, 'data', 'sandbox')
32-
self.user = None
33-
self.sandbox_so_path = f'{self.sandbox_path}/lib/sandbox.so'
34-
self.process_timeout_seconds = int(CONFIG.get("SANDBOX_PYTHON_PROCESS_TIMEOUT_SECONDS", '3600'))
35-
self.process_limit_mem_mb = int(CONFIG.get("SANDBOX_PYTHON_PROCESS_LIMIT_MEM_MB", '256'))
36-
try:
37-
self._init_sandbox_dir()
38-
except Exception as e:
39-
# 本机忽略异常,容器内不忽略
40-
maxkb_logger.error(f'Exception: {e}', exc_info=True)
41-
if self.sandbox:
42-
raise e
43-
44-
def _init_sandbox_dir(self):
45-
if not self.sandbox:
46-
# 不是sandbox就不初始化目录
33+
self.user = getpass.getuser()
34+
35+
@staticmethod
36+
def init_sandbox_dir():
37+
if not ToolExecutor.enable_sandbox:
38+
# 不启用sandbox就不初始化目录
4739
return
4840
try:
4941
# 只初始化一次
@@ -63,7 +55,7 @@ def _init_sandbox_dir(self):
6355
if CONFIG.get("SANDBOX_TMP_DIR_ENABLED", '0') == "1":
6456
os.system("chmod g+rwx /tmp")
6557
# 初始化sandbox配置文件
66-
sandbox_lib_path = os.path.dirname(self.sandbox_so_path)
58+
sandbox_lib_path = os.path.dirname(f'{ToolExecutor.sandbox_path}/lib/sandbox.so')
6759
sandbox_conf_file_path = f'{sandbox_lib_path}/.sandbox.conf'
6860
if os.path.exists(sandbox_conf_file_path):
6961
os.remove(sandbox_conf_file_path)
@@ -76,7 +68,8 @@ def _init_sandbox_dir(self):
7668
with open(sandbox_conf_file_path, "w") as f:
7769
f.write(f"SANDBOX_PYTHON_BANNED_HOSTS={banned_hosts}\n")
7870
f.write(f"SANDBOX_PYTHON_ALLOW_SUBPROCESS={allow_subprocess}\n")
79-
os.system(f"chmod -R 550 {self.sandbox_path}")
71+
os.system(f"chmod -R 550 {ToolExecutor.sandbox_path}")
72+
8073

8174
def exec_code(self, code_str, keywords, function_name=None):
8275
_id = str(uuid.uuid7())
@@ -103,7 +96,7 @@ def exec_code(self, code_str, keywords, function_name=None):
10396
exec_result=f(**keywords)
10497
builtins.print("\\n{_id}:"+base64.b64encode(json.dumps({success}, default=str).encode()).decode(), flush=True)
10598
except Exception as e:
106-
if isinstance(e, MemoryError): e = Exception("Cannot allocate more memory: exceeded the limit of {self.process_limit_mem_mb} MB.")
99+
if isinstance(e, MemoryError): e = Exception("Cannot allocate more memory: exceeded the limit of {ToolExecutor.process_limit_mem_mb} MB.")
107100
builtins.print("\\n{_id}:"+base64.b64encode(json.dumps({err}, default=str).encode()).decode(), flush=True)
108101
"""
109102
maxkb_logger.debug(f"Sandbox execute code: {_exec_code}")
@@ -213,37 +206,38 @@ def get_tool_mcp_config(self, code, params):
213206
'-c',
214207
f'import base64,gzip; exec(gzip.decompress(base64.b64decode(\'{compressed_and_base64_encoded_code_str}\')).decode())',
215208
],
216-
'cwd': self.sandbox_path,
209+
'cwd': ToolExecutor.sandbox_path,
217210
'env': {
218-
'LD_PRELOAD': self.sandbox_so_path,
211+
'LD_PRELOAD': f'{ToolExecutor.sandbox_path}/lib/sandbox.so',
219212
},
220213
'transport': 'stdio',
221214
}
222215
return tool_config
223216

224217
def _exec(self, execute_file):
225218
kwargs = {'cwd': BASE_DIR, 'env': {
226-
'LD_PRELOAD': self.sandbox_so_path,
219+
'LD_PRELOAD': f'{ToolExecutor.sandbox_path}/lib/sandbox.so',
227220
}}
228221
try:
229-
def set_resource_limit():
230-
if not self.sandbox:
231-
return
232-
mem_limit = self.process_limit_mem_mb * 1024 * 1024
233-
resource.setrlimit(resource.RLIMIT_AS, (mem_limit, mem_limit))
234222
subprocess_result = subprocess.run(
235-
[python_directory, execute_file],
236-
preexec_fn=set_resource_limit,
237-
timeout=self.process_timeout_seconds,
223+
[sys.executable, execute_file],
224+
timeout=ToolExecutor.process_timeout_seconds,
238225
text=True,
239-
capture_output=True, **kwargs)
226+
capture_output=True,
227+
**kwargs,
228+
preexec_fn=lambda: (None if not self.sandbox else resource.setrlimit(resource.RLIMIT_AS, (ToolExecutor.process_limit_mem_mb * 1024 * 1024,) * 2))
229+
)
240230
return subprocess_result
241231
except subprocess.TimeoutExpired:
242-
raise Exception(_(f"Process execution timed out after {self.process_timeout_seconds} seconds."))
232+
raise Exception(_(f"Process execution timed out after {ToolExecutor.process_timeout_seconds} seconds."))
243233

244234
def validate_mcp_transport(self, code_str):
245235
servers = json.loads(code_str)
246236
for server, config in servers.items():
247237
if config.get('transport') not in ['sse', 'streamable_http']:
248238
raise Exception(_('Only support transport=sse or transport=streamable_http'))
249239

240+
try:
241+
ToolExecutor.init_sandbox_dir()
242+
except Exception as e:
243+
maxkb_logger.error(f'Exception: {e}', exc_info=True)

0 commit comments

Comments
 (0)