Python子进程中运行Rust编译器并设置内存限制时的链接错误排查
Python子进程中运行Rust编译器并设置内存限制时的链接错误排查
看起来你踩了一个很容易混淆的坑——把内存限制错加到了Rust编译器(rustc)本身,而不是你最终要运行的Rust程序上。咱们一步步拆解问题和解决方法:
问题根源分析
你当前的代码是在调用rustc编译Rust代码的子进程里,通过preexec_fn=limit_virtual_memory设置了100MB的内存限制。但你可能忽略了两个关键事实:
- Rust编译器(尤其是链接阶段,比如你用的lld链接器)本身需要大量内存来处理符号、依赖库、代码优化等操作,100MB的限制远远不够支撑链接过程,所以直接触发了内存不足的崩溃,也就是你看到的
terminate called after throwing an instance of 'std::system_error' what(): Resource temporarily unavailable错误。 - 你的真实需求应该是限制最终生成的Rust可执行程序的运行内存,而不是限制编译它的rustc进程的内存。
修正方案:拆分编译与运行阶段的内存控制
我们需要把流程拆成完全独立的两步:
- 无内存限制编译Rust代码:先正常调用
rustc生成可执行文件,这时候不给rustc加任何内存限制,让它有足够资源完成编译链接。 - 带内存限制运行生成的程序:运行编译好的可执行文件时,再通过
preexec_fn设置内存限制,这才是你真正要限制的目标。
代码修改示例
调整你的test_simple_stdin_program方法,拆分编译和运行步骤:
def test_simple_stdin_program(self): """Test successful execution with correct memory limiting""" program = """ use std::io; fn read_and_display_variables() { let mut input1 = String::new(); io::stdin() .read_line(&mut input1) .expect("Error reading input 1"); let first_variable = input1.trim(); let mut input2 = String::new(); io::stdin() .read_line(&mut input2) .expect("Error reading input 2"); let second_variable = input2.trim(); println!("{}", first_variable); println!("{}", second_variable); } fn main() { read_and_display_variables(); } """ cwd = "/home/jovyan/ipetrov/bench/LiveCodeBench-mult/tests/assets" code_path = os.path.join(cwd, "main.rs") exec_name = os.path.join(cwd, "main") # 先把代码写入文件 with open(code_path, "w") as f: f.write(program) # -------------------------- # 第一步:编译Rust代码(无内存限制) # -------------------------- args_compile = ["rustc", str(code_path), "-o", exec_name] new_env = { 'PATH': os.environ['PATH'], 'GCC': os.environ['GCC'], 'CXX': os.environ['CXX'], 'CXXFLAGS': os.environ['CXXFLAGS'], 'LD_LIBRARY_PATH': os.environ['LD_LIBRARY_PATH'], 'LD': os.environ['LD'], 'RUST_BACKTRACE': "1", 'RUST_LIB_BACKTRACE': "1" } print(f"Compiling Rust code on {platform.uname().system}...") p_compile = subprocess.Popen( args_compile, env=new_env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd ) compile_stdout, compile_stderr = p_compile.communicate(timeout=TIMEOUT) try: compile_stdout = compile_stdout.decode("utf-8") compile_stderr = compile_stderr.decode("utf-8") except UnicodeDecodeError: compile_stderr = compile_stderr.decode("latin-1") print("Compile stdout:\n", compile_stdout) print("Compile stderr:\n", compile_stderr) # 确保编译阶段无错误 assert p_compile.returncode == 0, f"Compilation failed with code {p_compile.returncode}: {compile_stderr}" # -------------------------- # 第二步:运行生成的可执行文件(设置内存限制) # -------------------------- input_data = b"test_input_1\ntest_input_2\n" # 对应Rust程序的stdin输入(需为bytes类型) p_run = subprocess.Popen( [exec_name], env=new_env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=limit_virtual_memory # 仅在运行阶段应用内存限制 ) set_nonblocking(p_run.stdout) set_nonblocking(p_run.stderr) run_stdout, run_stderr = p_run.communicate(input=input_data, timeout=TIMEOUT) try: run_stdout = run_stdout.decode("utf-8").strip() run_stderr = run_stderr.decode("utf-8") except UnicodeDecodeError: run_stderr = run_stderr.decode("latin-1") print("Run stdout:\n", run_stdout) print("Run stderr:\n", run_stderr) # 验证运行结果符合预期 assert run_stdout == "test_input_1\ntest_input_2", f"Unexpected output: {run_stdout}" assert not run_stderr, f"Runtime error: {run_stderr}" # 清理临时文件 os.remove(code_path) os.remove(exec_name)
额外优化建议
- 内存限制参数的合理选择:
- 如果你想限制程序的总虚拟内存(包括堆、栈、映射文件等),用
RLIMIT_AS比RLIMIT_DATA更合适。修改limit_virtual_memory函数:def limit_virtual_memory(): # 限制总虚拟内存为100MB resource.setrlimit(resource.RLIMIT_AS, (MAX_PRC_VIRT_MEM, MAX_PRC_VIRT_MEM)) if not platform.uname().system == "Darwin": resource.setrlimit( resource.RLIMIT_STACK, (MAX_PRC_STACK_MEM, MAX_PRC_STACK_MEM) )
- 如果你想限制程序的总虚拟内存(包括堆、栈、映射文件等),用
- 输入数据的正确性:Rust程序的
read_line会等待换行符,所以你需要传入包含换行的bytes数据,否则程序会卡在stdin读取阶段超时。 - 编译阶段的错误处理:编译失败时要保留错误信息,方便排查问题,比如添加断言检查返回码和错误输出。
这样修改后,就能同时实现你的两个目标:正常编译Rust代码,并且限制最终Rust程序的运行内存啦!




