diff --git a/judge/client.py b/judge/client.py index f5b12966..c39c840d 100644 --- a/judge/client.py +++ b/judge/client.py @@ -3,13 +3,13 @@ import os import json import commands import hashlib +import judger from multiprocessing import Pool from settings import max_running_number, lrun_gid, lrun_uid, judger_workspace from language import languages from result import result from judge_exceptions import JudgeClientError -from utils import parse_lrun_output from logger import logger @@ -20,13 +20,11 @@ def _run(instance, test_case_id): class JudgeClient(object): - def __init__(self, language_code, exe_path, max_cpu_time, - max_real_time, max_memory, test_case_dir): + def __init__(self, language_code, exe_path, max_cpu_time, max_memory, test_case_dir, judge_base_path): """ :param language_code: 语言编号 :param exe_path: 可执行文件路径 :param max_cpu_time: 最大cpu时间,单位ms - :param max_real_time: 最大执行时间,单位ms :param max_memory: 最大内存,单位MB :param test_case_dir: 测试用例文件夹路径 :return:返回结果list @@ -34,13 +32,13 @@ class JudgeClient(object): self._language = languages[language_code] self._exe_path = exe_path self._max_cpu_time = max_cpu_time - self._max_real_time = max_real_time self._max_memory = max_memory self._test_case_dir = test_case_dir # 进程池 self._pool = Pool(processes=max_running_number) # 测试用例配置项 self._test_case_info = self._load_test_case_info() + self._judge_base_path = judge_base_path def _load_test_case_info(self): # 读取测试用例信息 转换为dict @@ -52,55 +50,9 @@ class JudgeClient(object): except ValueError: raise JudgeClientError("Test case config file format error") - def _generate_command(self, test_case_id): - """ - 设置相关运行限制 进制访问网络 如果启用tmpfs 就把代码输出写入tmpfs,否则写入硬盘 - """ - # todo 系统调用白名单 chroot等参数 - command = "lrun" + \ - " --max-cpu-time " + str(self._max_cpu_time / 1000.0) + \ - " --max-real-time " + str(self._max_real_time / 1000.0 * 2) + \ - " --max-memory " + str(self._max_memory * 1000 * 1000) + \ - " --network false" + \ - " --remount-dev true " + \ - " --reset-env true " + \ - " --syscalls '" + self._language["syscalls"] + "'" + \ - " --max-nprocess 20" + \ - " --uid " + str(lrun_uid) + \ - " --gid " + str(lrun_gid) - - execute_command = self._language["execute_command"].format(exe_path=self._exe_path) - - command += (" " + - execute_command + - # 0就是stdin - " 0<" + self._test_case_dir + str(test_case_id) + ".in" + - # 1就是stdout - " 1>" + judger_workspace + str(test_case_id) + ".out" + - # 3是stderr,包含lrun的输出和程序的异常输出 - " 3>&2") - return command - - def _parse_lrun_output(self, output): - # 要注意的是 lrun把结果输出到了stderr,所以有些情况下lrun的输出可能与程序的一些错误输出的混合的,要先分离一下 - error = None - # 倒序找到MEMORY的位置,lrun的 MEMORY 输出后面有3个空格,而 EXCEEDED 也有可能是MEMORY,所以需要判断空格 - output_start = output.rfind("MEMORY ") - if output_start == -1: - logger.error("Lrun result parse error") - logger.error(output) - raise JudgeClientError("Lrun result parse error") - # 如果不是0,说明lrun输出前面有输出,也就是程序的stderr有内容 - if output_start != 0: - error = output[0:output_start] - # 分离出lrun的输出 - output = output[output_start:] - - return error, parse_lrun_output(output) - def _compare_output(self, test_case_id): test_case_config = self._test_case_info["test_cases"][str(test_case_id)] - output_path = judger_workspace + str(test_case_id) + ".out" + output_path = os.path.join(self._judge_base_path, str(test_case_id) + ".out") try: f = open(output_path, "rb") @@ -130,40 +82,31 @@ class JudgeClient(object): return output_md5, output_md5 == test_case_config["striped_output_md5"] def _judge_one(self, test_case_id): - # 运行lrun程序 接收返回值 - command = self._generate_command(test_case_id) - status_code, output = commands.getstatusoutput(command) - if status_code: - raise JudgeClientError(output) - error, run_result = self._parse_lrun_output(output) + execute_command = self._language["execute_command"].format(exe_path=self._exe_path).split(" ") - run_result["test_case_id"] = test_case_id - - # 代表内存或者时间超过限制了 程序被终止掉 要在runtime error 之前判断 - if run_result["exceed"]: - if run_result["exceed"] == "memory": - run_result["result"] = result["memory_limit_exceeded"] - elif run_result["exceed"] in ["cpu_time", "real_time"]: - run_result["result"] = result["time_limit_exceeded"] + run_result = judger.run(path=execute_command[0], + max_cpu_time=self._max_cpu_time, + max_memory=self._max_memory, + in_file=os.path.join(self._test_case_dir, str(test_case_id) + ".in"), + out_file=os.path.join(self._judge_base_path, str(test_case_id) + ".out"), + args=execute_command[1:], + env=["PATH=" + os.environ["PATH"]], + use_sandbox=True) + if run_result["flag"] == 0: + output_md5, r = self._compare_output(test_case_id) + if r: + run_result["result"] = result["accepted"] else: - logger.error("Error exceeded type: " + run_result["exceed"]) - logger.error(output) - raise JudgeClientError("Error exceeded type: " + run_result["exceed"]) - return run_result - - # 如果返回值非0 或者信号量不是0 或者程序的stderr有输出 代表非正常结束 - if run_result["exit_code"] or run_result["term_sig"] or run_result["siginaled"] or error: + run_result["result"] = result["wrong_answer"] + run_result["output_md5"] = output_md5 + elif run_result["flag"] in [1, 2]: + run_result["result"] = result["time_limit_exceeded"] + elif run_result["flag"] == 3: + run_result["result"] = result["memory_limit_exceeded"] + elif run_result["flag"] == 4: run_result["result"] = result["runtime_error"] - return run_result - - # 下面就是代码正常运行了 需要判断代码的输出是否正确 - output_md5, r = self._compare_output(test_case_id) - if r: - run_result["result"] = result["accepted"] - else: - run_result["result"] = result["wrong_answer"] - run_result["output_md5"] = output_md5 - + elif run_result["flag"] == 5: + run_result["result"] = result["system_error"] return run_result def run(self): diff --git a/judge/compiler.py b/judge/compiler.py index 8e741ecb..42b575ab 100644 --- a/judge/compiler.py +++ b/judge/compiler.py @@ -7,11 +7,11 @@ from logger import logger from settings import judger_workspace -def compile_(language_item, src_path, exe_path): +def compile_(language_item, src_path, exe_path, judge_base_path): compile_command = language_item["compile_command"].format(src_path=src_path, exe_path=exe_path).split(" ") compiler = compile_command[0] compile_args = compile_command[1:] - compiler_output_file = os.path.join(judger_workspace, str(time.time()) + ".out") + compiler_output_file = os.path.join(judge_base_path, "compiler.out") compile_result = judger.run(path=compiler, in_file="/dev/null", @@ -25,13 +25,12 @@ def compile_(language_item, src_path, exe_path): compile_output_handler = open(compiler_output_file) compile_output = compile_output_handler.read() compile_output_handler.close() - os.remove(compiler_output_file) if compile_result["flag"] != 0: logger.error("Compiler error") logger.error(compile_output) logger.error(str(compile_result)) - raise CompileError("Compile error") + raise CompileError("Compile error, info: " + str(compile_result)) else: if "error" in compile_output: raise CompileError(compile_output) diff --git a/judge/language.py b/judge/language.py index 4f5658bb..d0a08770 100644 --- a/judge/language.py +++ b/judge/language.py @@ -6,7 +6,6 @@ languages = { "name": "c", "src_name": "main.c", "code": 1, - "syscalls": "!execve:k,flock:k,ptrace:k,sync:k,fdatasync:k,fsync:k,msync,sync_file_range:k,syncfs:k,unshare:k,setns:k,clone:k,query_module:k,sysinfo:k,syslog:k,sysfs:k", "compile_command": "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -std=c99 {src_path} -lm -o {exe_path}/main", "execute_command": "{exe_path}/main" }, @@ -14,7 +13,6 @@ languages = { "name": "cpp", "src_name": "main.cpp", "code": 2, - "syscalls": "!execve:k,flock:k,ptrace:k,sync:k,fdatasync:k,fsync:k,msync,sync_file_range:k,syncfs:k,unshare:k,setns:k,clone:k,query_module:k,sysinfo:k,syslog:k,sysfs:k", "compile_command": "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -std=c++11 {src_path} -lm -o {exe_path}/main", "execute_command": "{exe_path}/main" }, @@ -22,9 +20,8 @@ languages = { "name": "java", "src_name": "Main.java", "code": 3, - "syscalls": "!execve:k,flock:k,ptrace:k,sync:k,fdatasync:k,fsync:k,msync,sync_file_range:k,syncfs:k,unshare:k,setns:k,clone[a&268435456==268435456]:k,query_module:k,sysinfo:k,syslog:k,sysfs:k", "compile_command": "/usr/bin/javac {src_path} -d {exe_path}", - "execute_command": "java -cp {exe_path} -Djava.security.manager -Djava.security.policy==policy Main" + "execute_command": "/usr/bin/java -cp {exe_path} -Djava.security.manager -Djava.security.policy==policy Main" } } diff --git a/judge/result.py b/judge/result.py index ee0d4798..f42e63fd 100644 --- a/judge/result.py +++ b/judge/result.py @@ -1,6 +1,7 @@ # coding=utf-8 +# 这个映射关系是前后端通用的,判题服务器提供接口,也应该遵守这个,可能需要一些转换 result = { "accepted": 0, "runtime_error": 1, diff --git a/judge/runner.py b/judge/runner.py index 5ca5c205..e023e87a 100644 --- a/judge/runner.py +++ b/judge/runner.py @@ -35,7 +35,7 @@ class JudgeInstanceRunner(object): # 编译 try: - exe_path = compile_(language, src_path, judge_base_path) + exe_path = compile_(language, src_path, judge_base_path, judge_base_path) except Exception as e: shutil.rmtree(judge_base_path, ignore_errors=True) return {"code": 1, "data": {"error": str(e), "server": host_name}} @@ -45,14 +45,14 @@ class JudgeInstanceRunner(object): client = JudgeClient(language_code=language_code, exe_path=exe_path, max_cpu_time=int(time_limit), - max_real_time=int(time_limit) * 2, - max_memory=int(memory_limit), - test_case_dir=judger_workspace + "test_case/" + test_case_id + "/") + max_memory=int(memory_limit) * 1024 * 1024, + test_case_dir=judger_workspace + "test_case/" + test_case_id + "/", + judge_base_path=judge_base_path) judge_result = {"result": result["accepted"], "info": client.run(), "accepted_answer_time": None, "server": host_name} for item in judge_result["info"]: - if item["result"]: + if item["result"] != 0: judge_result["result"] = item["result"] break else: diff --git a/judge/utils.py b/judge/utils.py deleted file mode 100644 index aed1057c..00000000 --- a/judge/utils.py +++ /dev/null @@ -1,40 +0,0 @@ -# coding=utf-8 -from judge_exceptions import JudgeClientError - - -def parse_lrun_output(output): - lines = output.split("\n") - if len(lines) != 7: - raise JudgeClientError("Lrun result parse error") - result = {} - # 将lrun输出的各种带下划线 不带下划线的字符串统一处理 - translate = {"MEMORY": "memory", - "CPUTIME": "cpu_time", - "CPU_TIME": "cpu_time", - "REALTIME": "real_time", - "REAL_TIME": "real_time", - "TERMSIG": "term_sig", - "SIGNALED": "siginaled", - "EXITCODE": "exit_code", - "EXCEED": "exceed"} - for line in lines: - name = line[:9].strip(" ") - value = line[9:] - if name == "MEMORY": - result[translate[name]] = int(value) - elif name == "CPUTIME": - result[translate[name]] = int(float(value) * 1000) - elif name == "REALTIME": - result[translate[name]] = int(float(value) * 1000) - elif name == "EXITCODE": - result[translate[name]] = int(value) - elif name == "TERMSIG": - result[translate[name]] = int(value) - elif name == "SIGNALED": - result[translate[name]] = int(value) - elif name == "EXCEED": - if value == "none": - result[translate[name]] = None - else: - result[translate[name]] = translate[value] - return result