mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2024-09-21 08:23:20 +00:00
Merge branch 'new-judger'
* new-judger: java 不再使用沙箱 更换判题为新的 judger 编译器使用绝对路径,否则报找不到文件的异常。 使用 judger 运行编译器
This commit is contained in:
commit
413d5adaf8
109
judge/client.py
109
judge/client.py
@ -3,13 +3,13 @@ import os
|
|||||||
import json
|
import json
|
||||||
import commands
|
import commands
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import judger
|
||||||
from multiprocessing import Pool
|
from multiprocessing import Pool
|
||||||
|
|
||||||
from settings import max_running_number, lrun_gid, lrun_uid, judger_workspace
|
from settings import max_running_number, lrun_gid, lrun_uid, judger_workspace
|
||||||
from language import languages
|
from language import languages
|
||||||
from result import result
|
from result import result
|
||||||
from judge_exceptions import JudgeClientError
|
from judge_exceptions import JudgeClientError
|
||||||
from utils import parse_lrun_output
|
|
||||||
from logger import logger
|
from logger import logger
|
||||||
|
|
||||||
|
|
||||||
@ -20,13 +20,11 @@ def _run(instance, test_case_id):
|
|||||||
|
|
||||||
|
|
||||||
class JudgeClient(object):
|
class JudgeClient(object):
|
||||||
def __init__(self, language_code, exe_path, max_cpu_time,
|
def __init__(self, language_code, exe_path, max_cpu_time, max_memory, test_case_dir, judge_base_path):
|
||||||
max_real_time, max_memory, test_case_dir):
|
|
||||||
"""
|
"""
|
||||||
:param language_code: 语言编号
|
:param language_code: 语言编号
|
||||||
:param exe_path: 可执行文件路径
|
:param exe_path: 可执行文件路径
|
||||||
:param max_cpu_time: 最大cpu时间,单位ms
|
:param max_cpu_time: 最大cpu时间,单位ms
|
||||||
:param max_real_time: 最大执行时间,单位ms
|
|
||||||
:param max_memory: 最大内存,单位MB
|
:param max_memory: 最大内存,单位MB
|
||||||
:param test_case_dir: 测试用例文件夹路径
|
:param test_case_dir: 测试用例文件夹路径
|
||||||
:return:返回结果list
|
:return:返回结果list
|
||||||
@ -34,13 +32,13 @@ class JudgeClient(object):
|
|||||||
self._language = languages[language_code]
|
self._language = languages[language_code]
|
||||||
self._exe_path = exe_path
|
self._exe_path = exe_path
|
||||||
self._max_cpu_time = max_cpu_time
|
self._max_cpu_time = max_cpu_time
|
||||||
self._max_real_time = max_real_time
|
|
||||||
self._max_memory = max_memory
|
self._max_memory = max_memory
|
||||||
self._test_case_dir = test_case_dir
|
self._test_case_dir = test_case_dir
|
||||||
# 进程池
|
# 进程池
|
||||||
self._pool = Pool(processes=max_running_number)
|
self._pool = Pool(processes=max_running_number)
|
||||||
# 测试用例配置项
|
# 测试用例配置项
|
||||||
self._test_case_info = self._load_test_case_info()
|
self._test_case_info = self._load_test_case_info()
|
||||||
|
self._judge_base_path = judge_base_path
|
||||||
|
|
||||||
def _load_test_case_info(self):
|
def _load_test_case_info(self):
|
||||||
# 读取测试用例信息 转换为dict
|
# 读取测试用例信息 转换为dict
|
||||||
@ -52,55 +50,9 @@ class JudgeClient(object):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
raise JudgeClientError("Test case config file format error")
|
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):
|
def _compare_output(self, test_case_id):
|
||||||
test_case_config = self._test_case_info["test_cases"][str(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:
|
try:
|
||||||
f = open(output_path, "rb")
|
f = open(output_path, "rb")
|
||||||
@ -130,40 +82,31 @@ class JudgeClient(object):
|
|||||||
return output_md5, output_md5 == test_case_config["striped_output_md5"]
|
return output_md5, output_md5 == test_case_config["striped_output_md5"]
|
||||||
|
|
||||||
def _judge_one(self, test_case_id):
|
def _judge_one(self, test_case_id):
|
||||||
# 运行lrun程序 接收返回值
|
execute_command = self._language["execute_command"].format(exe_path=self._exe_path).split(" ")
|
||||||
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)
|
|
||||||
|
|
||||||
run_result["test_case_id"] = test_case_id
|
run_result = judger.run(path=execute_command[0],
|
||||||
|
max_cpu_time=self._max_cpu_time,
|
||||||
# 代表内存或者时间超过限制了 程序被终止掉 要在runtime error 之前判断
|
max_memory=self._max_memory,
|
||||||
if run_result["exceed"]:
|
in_file=os.path.join(self._test_case_dir, str(test_case_id) + ".in"),
|
||||||
if run_result["exceed"] == "memory":
|
out_file=os.path.join(self._judge_base_path, str(test_case_id) + ".out"),
|
||||||
run_result["result"] = result["memory_limit_exceeded"]
|
args=execute_command[1:],
|
||||||
elif run_result["exceed"] in ["cpu_time", "real_time"]:
|
env=["PATH=" + os.environ["PATH"]],
|
||||||
run_result["result"] = result["time_limit_exceeded"]
|
use_sandbox=self._language["use_sandbox"])
|
||||||
|
if run_result["flag"] == 0:
|
||||||
|
output_md5, r = self._compare_output(test_case_id)
|
||||||
|
if r:
|
||||||
|
run_result["result"] = result["accepted"]
|
||||||
else:
|
else:
|
||||||
logger.error("Error exceeded type: " + run_result["exceed"])
|
run_result["result"] = result["wrong_answer"]
|
||||||
logger.error(output)
|
run_result["output_md5"] = output_md5
|
||||||
raise JudgeClientError("Error exceeded type: " + run_result["exceed"])
|
elif run_result["flag"] in [1, 2]:
|
||||||
return run_result
|
run_result["result"] = result["time_limit_exceeded"]
|
||||||
|
elif run_result["flag"] == 3:
|
||||||
# 如果返回值非0 或者信号量不是0 或者程序的stderr有输出 代表非正常结束
|
run_result["result"] = result["memory_limit_exceeded"]
|
||||||
if run_result["exit_code"] or run_result["term_sig"] or run_result["siginaled"] or error:
|
elif run_result["flag"] == 4:
|
||||||
run_result["result"] = result["runtime_error"]
|
run_result["result"] = result["runtime_error"]
|
||||||
return run_result
|
elif run_result["flag"] == 5:
|
||||||
|
run_result["result"] = result["system_error"]
|
||||||
# 下面就是代码正常运行了 需要判断代码的输出是否正确
|
|
||||||
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
|
|
||||||
|
|
||||||
return run_result
|
return run_result
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
@ -1,42 +1,37 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
import commands
|
import time
|
||||||
|
import os
|
||||||
from settings import lrun_uid, lrun_gid
|
import judger
|
||||||
from judge_exceptions import CompileError, JudgeClientError
|
from judge_exceptions import CompileError, JudgeClientError
|
||||||
from utils import parse_lrun_output
|
|
||||||
from logger import logger
|
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)
|
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(judge_base_path, "compiler.out")
|
||||||
|
|
||||||
# 防止编译器卡死 或者 include </dev/random>等
|
compile_result = judger.run(path=compiler,
|
||||||
execute_command = "lrun" + \
|
in_file="/dev/null",
|
||||||
" --max-real-time 5" + \
|
out_file=compiler_output_file,
|
||||||
" --uid " + str(lrun_uid) + \
|
max_cpu_time=2000,
|
||||||
" --gid " + str(lrun_gid) + \
|
max_memory=2000000000,
|
||||||
" " + \
|
args=compile_args,
|
||||||
compile_command + \
|
env=["PATH=" + os.environ["PATH"]],
|
||||||
" 3>&2"
|
use_sandbox=False)
|
||||||
status, output = commands.getstatusoutput(execute_command)
|
|
||||||
|
|
||||||
output_start = output.rfind("MEMORY")
|
compile_output_handler = open(compiler_output_file)
|
||||||
|
compile_output = compile_output_handler.read()
|
||||||
|
compile_output_handler.close()
|
||||||
|
|
||||||
if output_start == -1:
|
if compile_result["flag"] != 0:
|
||||||
logger.error("Compiler error")
|
logger.error("Compiler error")
|
||||||
logger.error(output)
|
logger.error(compile_output)
|
||||||
raise JudgeClientError("Error running compiler in lrun")
|
logger.error(str(compile_result))
|
||||||
|
raise CompileError("Compile error, info: " + str(compile_result))
|
||||||
# 返回值不为 0 或者 stderr 中 lrun 的输出之前有 erro r字符串
|
else:
|
||||||
# 判断 error 字符串的原因是链接的时候可能会有一些不推荐使用的函数的的警告,
|
if "error" in compile_output:
|
||||||
# 但是 -w 参数并不能关闭链接时的警告
|
raise CompileError(compile_output)
|
||||||
if status or "error" in output[0:output_start]:
|
return exe_path
|
||||||
raise CompileError(output[0:output_start])
|
|
||||||
|
|
||||||
parse_result = parse_lrun_output(output[output_start:])
|
|
||||||
|
|
||||||
if parse_result["exit_code"] or parse_result["term_sig"] or parse_result["siginaled"] or parse_result["exceed"]:
|
|
||||||
logger.error("Compiler error")
|
|
||||||
logger.error(output)
|
|
||||||
raise CompileError("Compile error")
|
|
||||||
return exe_path
|
|
||||||
|
@ -6,25 +6,25 @@ languages = {
|
|||||||
"name": "c",
|
"name": "c",
|
||||||
"src_name": "main.c",
|
"src_name": "main.c",
|
||||||
"code": 1,
|
"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",
|
||||||
"compile_command": "gcc -DONLINE_JUDGE -O2 -w -std=c99 {src_path} -lm -o {exe_path}/main",
|
"execute_command": "{exe_path}/main",
|
||||||
"execute_command": "{exe_path}/main"
|
"use_sandbox": True
|
||||||
},
|
},
|
||||||
2: {
|
2: {
|
||||||
"name": "cpp",
|
"name": "cpp",
|
||||||
"src_name": "main.cpp",
|
"src_name": "main.cpp",
|
||||||
"code": 2,
|
"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",
|
||||||
"compile_command": "g++ -DONLINE_JUDGE -O2 -w -std=c++11 {src_path} -lm -o {exe_path}/main",
|
"execute_command": "{exe_path}/main",
|
||||||
"execute_command": "{exe_path}/main"
|
"use_sandbox": True
|
||||||
},
|
},
|
||||||
3: {
|
3: {
|
||||||
"name": "java",
|
"name": "java",
|
||||||
"src_name": "Main.java",
|
"src_name": "Main.java",
|
||||||
"code": 3,
|
"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}",
|
||||||
"compile_command": "javac {src_path} -d {exe_path}",
|
"execute_command": "/usr/bin/java -cp {exe_path} -Djava.security.manager -Djava.security.policy==policy Main",
|
||||||
"execute_command": "java -cp {exe_path} -Djava.security.manager -Djava.security.policy==policy Main"
|
"use_sandbox": False
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
|
||||||
|
|
||||||
|
# 这个映射关系是前后端通用的,判题服务器提供接口,也应该遵守这个,可能需要一些转换
|
||||||
result = {
|
result = {
|
||||||
"accepted": 0,
|
"accepted": 0,
|
||||||
"runtime_error": 1,
|
"runtime_error": 1,
|
||||||
|
@ -35,7 +35,7 @@ class JudgeInstanceRunner(object):
|
|||||||
|
|
||||||
# 编译
|
# 编译
|
||||||
try:
|
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:
|
except Exception as e:
|
||||||
shutil.rmtree(judge_base_path, ignore_errors=True)
|
shutil.rmtree(judge_base_path, ignore_errors=True)
|
||||||
return {"code": 1, "data": {"error": str(e), "server": host_name}}
|
return {"code": 1, "data": {"error": str(e), "server": host_name}}
|
||||||
@ -45,14 +45,14 @@ class JudgeInstanceRunner(object):
|
|||||||
client = JudgeClient(language_code=language_code,
|
client = JudgeClient(language_code=language_code,
|
||||||
exe_path=exe_path,
|
exe_path=exe_path,
|
||||||
max_cpu_time=int(time_limit),
|
max_cpu_time=int(time_limit),
|
||||||
max_real_time=int(time_limit) * 2,
|
max_memory=int(memory_limit) * 1024 * 1024,
|
||||||
max_memory=int(memory_limit),
|
test_case_dir=judger_workspace + "test_case/" + test_case_id + "/",
|
||||||
test_case_dir=judger_workspace + "test_case/" + test_case_id + "/")
|
judge_base_path=judge_base_path)
|
||||||
judge_result = {"result": result["accepted"], "info": client.run(),
|
judge_result = {"result": result["accepted"], "info": client.run(),
|
||||||
"accepted_answer_time": None, "server": host_name}
|
"accepted_answer_time": None, "server": host_name}
|
||||||
|
|
||||||
for item in judge_result["info"]:
|
for item in judge_result["info"]:
|
||||||
if item["result"]:
|
if item["result"] != 0:
|
||||||
judge_result["result"] = item["result"]
|
judge_result["result"] = item["result"]
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
@ -14,11 +14,3 @@ lrun_gid = 1002
|
|||||||
|
|
||||||
# judger工作目录
|
# judger工作目录
|
||||||
judger_workspace = "/var/judger/"
|
judger_workspace = "/var/judger/"
|
||||||
|
|
||||||
submission_db = {
|
|
||||||
"host": os.environ.get("MYSQL_PORT_3306_TCP_ADDR", "127.0.0.1"),
|
|
||||||
"port": 3306,
|
|
||||||
"db": "oj_submission",
|
|
||||||
"user": "root",
|
|
||||||
"password": os.environ.get("MYSQL_ENV_MYSQL_ROOT_PASSWORD", "root")
|
|
||||||
}
|
|
||||||
|
@ -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
|
|
Loading…
Reference in New Issue
Block a user