xxx
This commit is contained in:
26
src/main/java/cn/meowrain/aioj/CodeSandbox.java
Normal file
26
src/main/java/cn/meowrain/aioj/CodeSandbox.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2026-2026-2026 MeowRain AI Online Judge
|
||||
* Author: MeowRain
|
||||
* Github: https://github.com/meowrain
|
||||
* Blog: https://blog.meowrain.cn
|
||||
* Build With Love❤️
|
||||
*/
|
||||
package cn.meowrain.aioj;
|
||||
|
||||
|
||||
import cn.meowrain.aioj.dto.req.ExecuteCodeRequestDTO;
|
||||
import cn.meowrain.aioj.dto.resp.ExecuteCodeResponseDTO;
|
||||
|
||||
/**
|
||||
* 代码沙箱接口
|
||||
*/
|
||||
public interface CodeSandbox {
|
||||
|
||||
/**
|
||||
* 执行代码
|
||||
* @param request 执行请求
|
||||
* @return 执行响应
|
||||
*/
|
||||
ExecuteCodeResponseDTO executeCode(ExecuteCodeRequestDTO request);
|
||||
|
||||
}
|
||||
132
src/main/java/cn/meowrain/aioj/JavaDockerCodeSandBox.java
Normal file
132
src/main/java/cn/meowrain/aioj/JavaDockerCodeSandBox.java
Normal file
@@ -0,0 +1,132 @@
|
||||
package cn.meowrain.aioj;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.meowrain.aioj.dto.req.ExecuteCodeRequestDTO;
|
||||
import cn.meowrain.aioj.dto.resp.ExecuteCodeResponseDTO;
|
||||
import cn.meowrain.aioj.dto.resp.JudgeInfoDTO;
|
||||
import cn.meowrain.aioj.utils.ProcessUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class JavaDockerCodeSandBox implements CodeSandbox {
|
||||
|
||||
private static final long DEFAULT_TIME_LIMIT = 5000L;
|
||||
|
||||
public static void main(String[] args) {
|
||||
JavaDockerCodeSandBox javaDockerCodeSandBox = new JavaDockerCodeSandBox();
|
||||
|
||||
String code = ResourceUtil.readStr("testCode.multiargs/Main.java", StandardCharsets.UTF_8);
|
||||
|
||||
String language = "java";
|
||||
ExecuteCodeRequestDTO executeCodeRequestDTO = ExecuteCodeRequestDTO.builder()
|
||||
.code(code)
|
||||
.language(language)
|
||||
.inputList(Arrays.asList("1,2","1,3"))
|
||||
.build();
|
||||
|
||||
ExecuteCodeResponseDTO response = javaDockerCodeSandBox.executeCode(executeCodeRequestDTO);
|
||||
System.out.println("状态: " + response.getStatus());
|
||||
System.out.println("消息: " + response.getMessage());
|
||||
System.out.println("输出: " + response.getOutputList());
|
||||
if (response.getJudgeInfo() != null) {
|
||||
System.out.println("判题信息: " + response.getJudgeInfo().getMessage());
|
||||
System.out.println("耗时: " + response.getJudgeInfo().getTime() + "ms");
|
||||
System.out.println("内存: " + response.getJudgeInfo().getMemory() + "KB");
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecuteCodeResponseDTO executeCode(ExecuteCodeRequestDTO request) {
|
||||
String code = request.getCode();
|
||||
List<String> inputList = request.getInputList();
|
||||
long timeLimit = request.getTimeLimit() != null ? request.getTimeLimit() : DEFAULT_TIME_LIMIT;
|
||||
|
||||
if (inputList == null) {
|
||||
inputList = Collections.emptyList();
|
||||
}
|
||||
|
||||
ExecuteCodeResponseDTO response = new ExecuteCodeResponseDTO();
|
||||
|
||||
// 1. 保存用户代码到临时目录
|
||||
String userDir = System.getProperty("user.dir");
|
||||
String globalCodePathName = userDir + File.separator + "tempcode";
|
||||
if (!FileUtil.exist(globalCodePathName)) {
|
||||
FileUtil.mkdir(globalCodePathName);
|
||||
}
|
||||
|
||||
String userCodeParentPath = globalCodePathName + File.separator + UUID.randomUUID();
|
||||
String userCodePath = userCodeParentPath + File.separator + "Main.java";
|
||||
File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);
|
||||
|
||||
try {
|
||||
// 2. 编译
|
||||
String compileError = ProcessUtil.compile(userCodeFile);
|
||||
if (compileError != null) {
|
||||
response.setStatus(2);
|
||||
response.setMessage(compileError);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 3. 逐个用例执行
|
||||
List<String> outputList = new ArrayList<>();
|
||||
long maxTime = 0;
|
||||
long maxMemory = 0;
|
||||
|
||||
for (String input : inputList.isEmpty() ? Collections.singletonList("") : inputList) {
|
||||
// 构建命令:java -cp <classpath> Main <args...>
|
||||
// inputList 中每个元素按空格拆分为多个命令行参数
|
||||
List<String> runCommand = new ArrayList<>(Arrays.asList("java", "-Xmx256m", "-cp", userCodeParentPath, "Main"));
|
||||
if (input != null && !input.trim().isEmpty()) {
|
||||
runCommand.addAll(Arrays.asList(input.trim().split("\\s+")));
|
||||
}
|
||||
ProcessUtil.ExecuteResult result = ProcessUtil.run(runCommand, null, timeLimit);
|
||||
|
||||
if (result.isTimeout()) {
|
||||
response.setStatus(4);
|
||||
response.setMessage("运行超时");
|
||||
return response;
|
||||
}
|
||||
|
||||
if (result.getExitCode() != 0) {
|
||||
response.setStatus(3);
|
||||
response.setMessage(result.getStderr().isEmpty()
|
||||
? "运行错误,退出码: " + result.getExitCode()
|
||||
: result.getStderr());
|
||||
return response;
|
||||
}
|
||||
|
||||
outputList.add(result.getStdout());
|
||||
maxTime = Math.max(maxTime, result.getTime());
|
||||
maxMemory = Math.max(maxMemory, result.getMemory());
|
||||
}
|
||||
|
||||
// 4. 全部执行成功
|
||||
response.setStatus(1);
|
||||
response.setMessage("成功");
|
||||
response.setOutputList(outputList);
|
||||
|
||||
JudgeInfoDTO judgeInfo = new JudgeInfoDTO();
|
||||
judgeInfo.setMessage("Accepted");
|
||||
judgeInfo.setTime(maxTime);
|
||||
judgeInfo.setMemory(maxMemory);
|
||||
response.setJudgeInfo(judgeInfo);
|
||||
|
||||
} catch (Exception e) {
|
||||
response.setStatus(3);
|
||||
response.setMessage("系统错误: " + e.getMessage());
|
||||
} finally {
|
||||
// 5. 清理临时文件
|
||||
FileUtil.del(userCodeParentPath);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
132
src/main/java/cn/meowrain/aioj/JavaNativeCodeSandbox.java
Normal file
132
src/main/java/cn/meowrain/aioj/JavaNativeCodeSandbox.java
Normal file
@@ -0,0 +1,132 @@
|
||||
package cn.meowrain.aioj;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.meowrain.aioj.dto.req.ExecuteCodeRequestDTO;
|
||||
import cn.meowrain.aioj.dto.resp.ExecuteCodeResponseDTO;
|
||||
import cn.meowrain.aioj.dto.resp.JudgeInfoDTO;
|
||||
import cn.meowrain.aioj.utils.ProcessUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class JavaNativeCodeSandbox implements CodeSandbox {
|
||||
|
||||
private static final long DEFAULT_TIME_LIMIT = 5000L;
|
||||
|
||||
public static void main(String[] args) {
|
||||
JavaNativeCodeSandbox javaNativeCodeSandbox = new JavaNativeCodeSandbox();
|
||||
|
||||
String code = ResourceUtil.readStr("testCode.multiargs/Main.java", StandardCharsets.UTF_8);
|
||||
|
||||
String language = "java";
|
||||
ExecuteCodeRequestDTO executeCodeRequestDTO = ExecuteCodeRequestDTO.builder()
|
||||
.code(code)
|
||||
.language(language)
|
||||
.inputList(Arrays.asList("1,2","1,3"))
|
||||
.build();
|
||||
|
||||
ExecuteCodeResponseDTO response = javaNativeCodeSandbox.executeCode(executeCodeRequestDTO);
|
||||
System.out.println("状态: " + response.getStatus());
|
||||
System.out.println("消息: " + response.getMessage());
|
||||
System.out.println("输出: " + response.getOutputList());
|
||||
if (response.getJudgeInfo() != null) {
|
||||
System.out.println("判题信息: " + response.getJudgeInfo().getMessage());
|
||||
System.out.println("耗时: " + response.getJudgeInfo().getTime() + "ms");
|
||||
System.out.println("内存: " + response.getJudgeInfo().getMemory() + "KB");
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecuteCodeResponseDTO executeCode(ExecuteCodeRequestDTO request) {
|
||||
String code = request.getCode();
|
||||
List<String> inputList = request.getInputList();
|
||||
long timeLimit = request.getTimeLimit() != null ? request.getTimeLimit() : DEFAULT_TIME_LIMIT;
|
||||
|
||||
if (inputList == null) {
|
||||
inputList = Collections.emptyList();
|
||||
}
|
||||
|
||||
ExecuteCodeResponseDTO response = new ExecuteCodeResponseDTO();
|
||||
|
||||
// 1. 保存用户代码到临时目录
|
||||
String userDir = System.getProperty("user.dir");
|
||||
String globalCodePathName = userDir + File.separator + "tempcode";
|
||||
if (!FileUtil.exist(globalCodePathName)) {
|
||||
FileUtil.mkdir(globalCodePathName);
|
||||
}
|
||||
|
||||
String userCodeParentPath = globalCodePathName + File.separator + UUID.randomUUID();
|
||||
String userCodePath = userCodeParentPath + File.separator + "Main.java";
|
||||
File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);
|
||||
|
||||
try {
|
||||
// 2. 编译
|
||||
String compileError = ProcessUtil.compile(userCodeFile);
|
||||
if (compileError != null) {
|
||||
response.setStatus(2);
|
||||
response.setMessage(compileError);
|
||||
return response;
|
||||
}
|
||||
|
||||
// 3. 逐个用例执行
|
||||
List<String> outputList = new ArrayList<>();
|
||||
long maxTime = 0;
|
||||
long maxMemory = 0;
|
||||
|
||||
for (String input : inputList.isEmpty() ? Collections.singletonList("") : inputList) {
|
||||
// 构建命令:java -cp <classpath> Main <args...>
|
||||
// inputList 中每个元素按空格拆分为多个命令行参数
|
||||
List<String> runCommand = new ArrayList<>(Arrays.asList("java", "-Xmx256m", "-cp", userCodeParentPath, "Main"));
|
||||
if (input != null && !input.trim().isEmpty()) {
|
||||
runCommand.addAll(Arrays.asList(input.trim().split("\\s+")));
|
||||
}
|
||||
ProcessUtil.ExecuteResult result = ProcessUtil.run(runCommand, null, timeLimit);
|
||||
|
||||
if (result.isTimeout()) {
|
||||
response.setStatus(4);
|
||||
response.setMessage("运行超时");
|
||||
return response;
|
||||
}
|
||||
|
||||
if (result.getExitCode() != 0) {
|
||||
response.setStatus(3);
|
||||
response.setMessage(result.getStderr().isEmpty()
|
||||
? "运行错误,退出码: " + result.getExitCode()
|
||||
: result.getStderr());
|
||||
return response;
|
||||
}
|
||||
|
||||
outputList.add(result.getStdout());
|
||||
maxTime = Math.max(maxTime, result.getTime());
|
||||
maxMemory = Math.max(maxMemory, result.getMemory());
|
||||
}
|
||||
|
||||
// 4. 全部执行成功
|
||||
response.setStatus(1);
|
||||
response.setMessage("成功");
|
||||
response.setOutputList(outputList);
|
||||
|
||||
JudgeInfoDTO judgeInfo = new JudgeInfoDTO();
|
||||
judgeInfo.setMessage("Accepted");
|
||||
judgeInfo.setTime(maxTime);
|
||||
judgeInfo.setMemory(maxMemory);
|
||||
response.setJudgeInfo(judgeInfo);
|
||||
|
||||
} catch (Exception e) {
|
||||
response.setStatus(3);
|
||||
response.setMessage("系统错误: " + e.getMessage());
|
||||
} finally {
|
||||
// 5. 清理临时文件
|
||||
FileUtil.del(userCodeParentPath);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
17
src/main/java/cn/meowrain/aioj/Main.java
Normal file
17
src/main/java/cn/meowrain/aioj/Main.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package cn.meowrain.aioj;
|
||||
|
||||
//TIP 要<b>运行</b>代码,请按 <shortcut actionId="Run"/> 或
|
||||
// 点击装订区域中的 <icon src="AllIcons.Actions.Execute"/> 图标。
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
//TIP 当文本光标位于高亮显示的文本处时按 <shortcut actionId="ShowIntentionActions"/>
|
||||
// 查看 IntelliJ IDEA 建议如何修正。
|
||||
System.out.printf("Hello and welcome!");
|
||||
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
//TIP 按 <shortcut actionId="Debug"/> 开始调试代码。我们已经设置了一个 <icon src="AllIcons.Debugger.Db_set_breakpoint"/> 断点
|
||||
// 但您始终可以通过按 <shortcut actionId="ToggleLineBreakpoint"/> 添加更多断点。
|
||||
System.out.println("i = " + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2026-2026-2026 MeowRain AI Online Judge
|
||||
* Author: MeowRain
|
||||
* Github: https://github.com/meowrain
|
||||
* Blog: https://blog.meowrain.cn
|
||||
* Build With Love❤️
|
||||
*/
|
||||
package cn.meowrain.aioj.dto.req;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 判题请求 DTO
|
||||
*/
|
||||
@Data
|
||||
public class DoJudgeRequestDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 题目提交ID
|
||||
*/
|
||||
private Long questionSubmitId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2026-2026-2026 MeowRain AI Online Judge
|
||||
* Author: MeowRain
|
||||
* Github: https://github.com/meowrain
|
||||
* Blog: https://blog.meowrain.cn
|
||||
* Build With Love❤️
|
||||
*/
|
||||
package cn.meowrain.aioj.dto.req;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 代码沙箱执行请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class ExecuteCodeRequestDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 编程语言
|
||||
*/
|
||||
private String language;
|
||||
|
||||
/**
|
||||
* 用户代码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 输入用例列表
|
||||
*/
|
||||
private List<String> inputList;
|
||||
|
||||
/**
|
||||
* 时间限制(ms)
|
||||
*/
|
||||
private Long timeLimit;
|
||||
|
||||
/**
|
||||
* 内存限制(KB)
|
||||
*/
|
||||
private Long memoryLimit;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2026-2026-2026 MeowRain AI Online Judge
|
||||
* Author: MeowRain
|
||||
* Github: https://github.com/meowrain
|
||||
* Blog: https://blog.meowrain.cn
|
||||
* Build With Love❤️
|
||||
*/
|
||||
package cn.meowrain.aioj.dto.resp;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 代码沙箱执行响应 DTO
|
||||
*/
|
||||
@Data
|
||||
public class ExecuteCodeResponseDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 输出结果列表(与输入用例一一对应)
|
||||
*/
|
||||
private List<String> outputList;
|
||||
|
||||
/**
|
||||
* 执行消息(如编译错误信息、运行错误信息等)
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 执行状态(1-成功,2-编译错误,3-运行错误,4-超时,5-内存超限)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 判题信息
|
||||
*/
|
||||
private JudgeInfoDTO judgeInfo;
|
||||
|
||||
}
|
||||
39
src/main/java/cn/meowrain/aioj/dto/resp/JudgeInfoDTO.java
Normal file
39
src/main/java/cn/meowrain/aioj/dto/resp/JudgeInfoDTO.java
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2026-2026-2026 MeowRain AI Online Judge
|
||||
* Author: MeowRain
|
||||
* Github: https://github.com/meowrain
|
||||
* Blog: https://blog.meowrain.cn
|
||||
* Build With Love❤️
|
||||
*/
|
||||
package cn.meowrain.aioj.dto.resp;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 判题信息 DTO
|
||||
*/
|
||||
@Data
|
||||
public class JudgeInfoDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 程序执行消息(如 Accepted、Wrong Answer 等)
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 消耗内存(KB)
|
||||
*/
|
||||
private Long memory;
|
||||
|
||||
/**
|
||||
* 消耗时间(ms)
|
||||
*/
|
||||
private Long time;
|
||||
|
||||
}
|
||||
254
src/main/java/cn/meowrain/aioj/utils/ProcessUtil.java
Normal file
254
src/main/java/cn/meowrain/aioj/utils/ProcessUtil.java
Normal file
@@ -0,0 +1,254 @@
|
||||
package cn.meowrain.aioj.utils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 进程执行工具类,封装编译和运行操作
|
||||
*/
|
||||
public class ProcessUtil {
|
||||
|
||||
/**
|
||||
* 执行结果
|
||||
*/
|
||||
public static class ExecuteResult {
|
||||
private final boolean timeout;
|
||||
private final int exitCode;
|
||||
private final String stdout;
|
||||
private final String stderr;
|
||||
private final long time;
|
||||
private final long memory;
|
||||
|
||||
public ExecuteResult(boolean timeout, int exitCode, String stdout, String stderr, long time, long memory) {
|
||||
this.timeout = timeout;
|
||||
this.exitCode = exitCode;
|
||||
this.stdout = stdout;
|
||||
this.stderr = stderr;
|
||||
this.time = time;
|
||||
this.memory = memory;
|
||||
}
|
||||
|
||||
public boolean isTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public int getExitCode() {
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
public String getStdout() {
|
||||
return stdout;
|
||||
}
|
||||
|
||||
public String getStderr() {
|
||||
return stderr;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public long getMemory() {
|
||||
return memory;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译 Java 代码
|
||||
*
|
||||
* @param userCodeFile 源代码文件
|
||||
* @param timeoutMs 编译超时时间(毫秒)
|
||||
* @return 编译成功返回 null,失败返回错误信息
|
||||
*/
|
||||
public static String compile(File userCodeFile, long timeoutMs) throws IOException, InterruptedException {
|
||||
ProcessBuilder pb = new ProcessBuilder("javac", "-encoding", "UTF-8", userCodeFile.getAbsolutePath());
|
||||
pb.redirectErrorStream(true);
|
||||
Process process = pb.start();
|
||||
|
||||
// 异步读取输出流,避免阻塞导致 waitFor 超时失效
|
||||
StringBuilder outputBuilder = new StringBuilder();
|
||||
Thread readerThread = new Thread(() -> {
|
||||
try {
|
||||
outputBuilder.append(readStream(process.getInputStream()));
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
});
|
||||
readerThread.setDaemon(true);
|
||||
readerThread.start();
|
||||
|
||||
boolean finished = process.waitFor(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
if (!finished) {
|
||||
process.destroyForcibly();
|
||||
return "编译超时";
|
||||
}
|
||||
|
||||
readerThread.join(2000);
|
||||
String output = outputBuilder.toString();
|
||||
|
||||
if (process.exitValue() != 0) {
|
||||
return "编译失败:\n" + output;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译 Java 代码,默认 10 秒超时
|
||||
*/
|
||||
public static String compile(File userCodeFile) throws IOException, InterruptedException {
|
||||
return compile(userCodeFile, 10_000L);
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行程序
|
||||
*
|
||||
* @param command 命令及参数列表
|
||||
* @param input 标准输入内容(可为 null 或空)
|
||||
* @param timeLimitMs 运行超时时间(毫秒)
|
||||
* @return 执行结果
|
||||
*/
|
||||
public static ExecuteResult run(List<String> command, String input, long timeLimitMs) throws IOException, InterruptedException {
|
||||
ProcessBuilder pb = new ProcessBuilder(command);
|
||||
pb.redirectErrorStream(false);
|
||||
Process process = pb.start();
|
||||
|
||||
// 写入标准输入
|
||||
if (input != null && !input.isEmpty()) {
|
||||
try (OutputStream os = process.getOutputStream()) {
|
||||
os.write(input.getBytes(StandardCharsets.UTF_8));
|
||||
os.flush();
|
||||
}
|
||||
} else {
|
||||
process.getOutputStream().close();
|
||||
}
|
||||
|
||||
// 异步读取 stdout 和 stderr,避免管道阻塞
|
||||
StringBuilder stdoutBuilder = new StringBuilder();
|
||||
StringBuilder stderrBuilder = new StringBuilder();
|
||||
|
||||
Thread stdoutThread = new Thread(() -> {
|
||||
try {
|
||||
stdoutBuilder.append(readStream(process.getInputStream()));
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
});
|
||||
stdoutThread.setDaemon(true);
|
||||
Thread stderrThread = new Thread(() -> {
|
||||
try {
|
||||
stderrBuilder.append(readStream(process.getErrorStream()));
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
});
|
||||
stderrThread.setDaemon(true);
|
||||
|
||||
// 内存采样线程:定期采样进程 PID 对应的内存使用(KB)
|
||||
long[] maxMemory = {0};
|
||||
Thread memoryThread = new Thread(() -> {
|
||||
try {
|
||||
long pid = process.pid();
|
||||
while (process.isAlive()) {
|
||||
long mem = getProcessMemoryKB(pid);
|
||||
if (mem > maxMemory[0]) {
|
||||
maxMemory[0] = mem;
|
||||
}
|
||||
Thread.sleep(50);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
});
|
||||
memoryThread.setDaemon(true);
|
||||
|
||||
stdoutThread.start();
|
||||
stderrThread.start();
|
||||
memoryThread.start();
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
boolean finished = process.waitFor(timeLimitMs, TimeUnit.MILLISECONDS);
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
|
||||
if (!finished) {
|
||||
|
||||
// 杀死进程
|
||||
process.destroyForcibly();
|
||||
return new ExecuteResult(true, -1, "", "", elapsed, maxMemory[0]);
|
||||
}
|
||||
|
||||
stdoutThread.join(2000);
|
||||
stderrThread.join(2000);
|
||||
memoryThread.join(1000);
|
||||
|
||||
String stdout = stdoutBuilder.toString().trim();
|
||||
String stderr = stderrBuilder.toString().trim();
|
||||
|
||||
return new ExecuteResult(false, process.exitValue(), stdout, stderr, elapsed, maxMemory[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 PID 进程的内存使用(KB)
|
||||
* Windows 使用 tasklist,Linux/Mac 使用 /proc 或 ps
|
||||
*/
|
||||
private static long getProcessMemoryKB(long pid) {
|
||||
Process proc = null;
|
||||
try {
|
||||
String os = System.getProperty("os.name").toLowerCase();
|
||||
ProcessBuilder pb;
|
||||
if (os.contains("win")) {
|
||||
pb = new ProcessBuilder("tasklist", "/FI", "PID eq " + pid, "/FO", "CSV", "/NH");
|
||||
} else {
|
||||
pb = new ProcessBuilder("ps", "-o", "rss=", "-p", String.valueOf(pid));
|
||||
}
|
||||
pb.redirectErrorStream(true);
|
||||
proc = pb.start();
|
||||
|
||||
boolean finished = proc.waitFor(2, TimeUnit.SECONDS);
|
||||
if (!finished) {
|
||||
proc.destroyForcibly();
|
||||
return 0;
|
||||
}
|
||||
|
||||
String output = readStream(proc.getInputStream());
|
||||
|
||||
if (os.contains("win")) {
|
||||
// tasklist CSV 格式: "java.exe","12345","Console","1","123,456 K"
|
||||
String[] parts = output.split("\"");
|
||||
for (int i = parts.length - 1; i >= 0; i--) {
|
||||
String part = parts[i].trim();
|
||||
if (part.toUpperCase().endsWith("K")) {
|
||||
String numStr = part.replace("K", "").replace(",", "").replace(" ", "").trim();
|
||||
return Long.parseLong(numStr);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ps 输出的直接就是 KB 数值
|
||||
String trimmed = output.trim();
|
||||
if (!trimmed.isEmpty()) {
|
||||
return Long.parseLong(trimmed);
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
} finally {
|
||||
if (proc != null) {
|
||||
proc.destroyForcibly();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取输入流内容为字符串
|
||||
*/
|
||||
public static String readStream(InputStream is) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(System.lineSeparator());
|
||||
}
|
||||
sb.append(line);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
BIN
src/main/resources/testCode.multiargs/Main.class
Normal file
BIN
src/main/resources/testCode.multiargs/Main.class
Normal file
Binary file not shown.
15
src/main/resources/testCode.multiargs/Main.java
Normal file
15
src/main/resources/testCode.multiargs/Main.java
Normal file
@@ -0,0 +1,15 @@
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
List<byte[]> holder = new ArrayList<>();
|
||||
int mb = 1024 * 1024;
|
||||
long i = 0;
|
||||
while (true) {
|
||||
holder.add(new byte[10 * mb]);
|
||||
i++;
|
||||
System.out.println("allocated ~" + (i * 10) + "MB");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user