Merge branch 'new-judger'

* new-judger:
  java 不再使用沙箱
  更换判题为新的 judger
  编译器使用绝对路径,否则报找不到文件的异常。
  使用 judger 运行编译器
This commit is contained in:
virusdefender 2016-02-02 11:56:17 +08:00
commit 413d5adaf8
7 changed files with 69 additions and 178 deletions

View File

@ -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):

View File

@ -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

View File

@ -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
} }
} }

View File

@ -1,6 +1,7 @@
# coding=utf-8 # coding=utf-8
# 这个映射关系是前后端通用的,判题服务器提供接口,也应该遵守这个,可能需要一些转换
result = { result = {
"accepted": 0, "accepted": 0,
"runtime_error": 1, "runtime_error": 1,

View File

@ -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:

View File

@ -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")
}

View File

@ -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