基本流程就绪

This commit is contained in:
MeiK2333 2021-01-15 17:45:58 +08:00
parent 33f93913d6
commit 04fe473175
10 changed files with 311 additions and 42 deletions

1
runtime/data/1000/1.in Normal file
View File

@ -0,0 +1 @@
1 2

1
runtime/data/1000/1.out Normal file
View File

@ -0,0 +1 @@
3

0
runtime/data/1001/1.in Normal file
View File

1
runtime/data/1001/1.out Normal file
View File

@ -0,0 +1 @@
Hello World!

View File

@ -4,9 +4,6 @@ use std::fs::{read_to_string, remove_dir};
use std::path::PathBuf;
use tempfile::tempdir_in;
#[cfg(test)]
use std::println as debug;
pub struct CGroupOptions {
path: PathBuf,
}
@ -14,7 +11,6 @@ pub struct CGroupOptions {
impl CGroupOptions {
pub fn new(base_path: &str) -> Result<CGroupOptions> {
let pwd = try_io!(tempdir_in(base_path));
debug!("register cgroup {:?}", pwd);
Ok(CGroupOptions {
path: pwd.path().to_path_buf(),
})
@ -22,13 +18,11 @@ impl CGroupOptions {
/// 将指定进程加入该 cgroup 组
pub fn apply(&self, pid: i32) -> Result<()> {
debug!("add {} to cgroup {:?}", pid, self.path);
self.set("cgroup.procs", &format!("{}", pid))
}
/// e.g `set("memory.limit_in_bytes", "67108864")`
pub fn set(&self, key: &str, value: &str) -> Result<()> {
debug!("set {} values {}", key, value);
try_io!(fs::write(self.path.join(key), value));
Ok(())
}
@ -41,7 +35,6 @@ impl CGroupOptions {
impl Drop for CGroupOptions {
fn drop(&mut self) {
debug!("remove cgroup {:?}", self.path);
remove_dir(&self.path).unwrap();
}
}

View File

@ -16,6 +16,7 @@ pub enum Error {
CreateTempDirError(io::Error),
CustomError(String),
LanguageNotFound(String),
SystemError(String),
}
pub type Result<T> = result::Result<T, Error>;
@ -37,6 +38,7 @@ impl fmt::Display for Error {
Error::IOError(ref e) => write!(f, "IOError: `{}`", errno_str(e.raw_os_error())),
Error::CustomError(ref e) => write!(f, "Internal Server Error: `{}`", e),
Error::LanguageNotFound(ref e) => write!(f, "Language Not Fount: `{}`", e),
Error::SystemError(ref e) => write!(f, "System Error: `{}`", e),
_ => write!(f, "{:?}", self),
}
}

View File

@ -1,28 +1,22 @@
use crate::config::{CONFIG, STDERR_FILENAME, STDOUT_FILENAME};
use crate::config::{CONFIG, STDERR_FILENAME, STDIN_FILENAME, STDOUT_FILENAME};
use crate::error::{Error, Result};
use crate::process::{Process, Runner};
use crate::result::{compile_error, compile_success};
use crate::river;
use crate::river::JudgeResponse;
use std::fs;
use std::fs::read_to_string;
use crate::result::{
accepted, compile_error, compile_success, memory_limit_exceeded, runtime_error,
standard_result, time_limit_exceeded, wrong_answer,
};
use crate::river::{JudgeResponse, JudgeResultEnum, JudgeType};
use std::path::Path;
#[cfg(test)]
use std::println as debug;
use tokio::fs;
use tokio::fs::read_to_string;
pub async fn compile(language: &str, code: &str, path: &Path) -> Result<JudgeResponse> {
debug!("language: {}", language);
debug!("code: {}", code);
debug!("path: {:?}", path);
let lang = match CONFIG.languages.get(language) {
Some(val) => val,
None => return Err(Error::LanguageNotFound(String::from(language))),
};
debug!("write file to {:?}", path.join(&lang.code_file));
try_io!(fs::write(path.join(&lang.code_file), &code));
try_io!(fs::write(path.join(&lang.code_file), &code).await);
debug!("build command: {}", lang.compile_cmd);
let process = Process::new(
String::from(&lang.compile_cmd),
10000,
@ -41,10 +35,9 @@ pub async fn compile(language: &str, code: &str, path: &Path) -> Result<JudgeRes
// 因为不同的语言、不同的编译器,错误信息输出到了不同的地方
let errmsg = format!(
"{}\n{}",
try_io!(read_to_string(path.join(STDOUT_FILENAME))),
try_io!(read_to_string(path.join(STDERR_FILENAME))),
try_io!(read_to_string(path.join(STDOUT_FILENAME)).await),
try_io!(read_to_string(path.join(STDERR_FILENAME)).await),
);
debug!("{}", errmsg);
return Ok(compile_error(status.time_used, mem_used, &errmsg));
} else {
return Ok(compile_success(status.time_used, mem_used));
@ -60,16 +53,62 @@ pub async fn judge(
judge_type: i32,
path: &Path,
) -> Result<JudgeResponse> {
debug!("language: {}", language);
debug!("in_file: {}", in_file);
debug!("out_file: {}", out_file);
debug!("time_limit: {}", time_limit);
debug!("memory_limit: {}", memory_limit);
debug!("judge_type: {}", judge_type);
debug!("path: {:?}", path);
Ok(JudgeResponse {
state: Some(river::judge_response::State::Status(
river::JudgeStatus::Pending as i32,
)),
})
let data_dir = Path::new(&CONFIG.data_dir);
// 复制输入文件
try_io!(fs::copy(data_dir.join(&in_file), path.join(STDIN_FILENAME)).await);
let lang = match CONFIG.languages.get(language) {
Some(val) => val,
None => return Err(Error::LanguageNotFound(String::from(language))),
};
let process = Process::new(
String::from(&lang.run_cmd),
time_limit,
memory_limit,
path.to_path_buf(),
);
let result = Runner::from(process)?;
let status = result.await?;
// 为了更好的体验,此处内存用量取 getrusage 报告与 cgroup 报告中较小的一个
let mem_used = if status.memory_used < status.cgroup_memory_used {
status.memory_used
} else {
status.cgroup_memory_used
};
if status.time_used > time_limit.into() {
// TLE
return Ok(time_limit_exceeded(status.time_used, mem_used));
} else if mem_used > memory_limit.into() {
// MLE
return Ok(memory_limit_exceeded(status.time_used, mem_used));
} else if status.signal != 0 {
// RE
return Ok(runtime_error(
status.time_used,
mem_used,
&format!("Program was interrupted by signal: `{}`", status.signal),
));
} else if status.exit_code != 0 {
// RE
// 就算是用户自己返回的非零,也算 RE
return Ok(runtime_error(
status.time_used,
mem_used,
&format!("Exceptional program return code: `{}`", status.exit_code),
));
} else if judge_type == JudgeType::Standard as i32 {
// 答案对比
let out = try_io!(fs::read(path.join(STDOUT_FILENAME)).await);
let ans = try_io!(fs::read(data_dir.join(&out_file)).await);
let res = standard_result(&out, &ans)?;
if res == JudgeResultEnum::Accepted {
return Ok(accepted(status.time_used, mem_used));
} else {
return Ok(wrong_answer(status.time_used, mem_used));
}
}
Err(Error::CustomError(String::from(format!(
"Unknown JudgeType: {}",
judge_type
))))
}

View File

@ -51,15 +51,14 @@ impl River for RiverService {
};
// 是否通过编译
let mut compile_success = false;
let mut language = String::from("");
while let Some(req) = stream.next().await {
yield result::pending();
// TODO: 限制并发数量
yield result::running();
let req = req?;
let mut language = String::from("");
let result = match &req.data {
Some(Data::CompileData(data)) => {
debug!("compile request");
// 因为评测时还需要 language 的信息,因此此处进行复制保存
language = String::from(&data.language);
let res = judger::compile(&language, &data.code, &pwd.path()).await;
@ -75,7 +74,6 @@ impl River for RiverService {
res
},
Some(Data::JudgeData(data)) => {
debug!("judge request");
// 必须通过编译才能运行
if language == "" || !compile_success {
Err(error::Error::CustomError(String::from("not compiled")))

View File

@ -149,7 +149,7 @@ impl Future for Runner {
runner.cgroup_set.apply(pid).unwrap();
runner.cgroup_set.memory.set(
"memory.limit_in_bytes",
&format!("{}", runner.process.memory_limit as i64 * 1024),
&format!("{}", runner.process.memory_limit as i64 * 1536), // 本来应该乘以 1024此处略微放宽限制从而让用户体验更好
)?;
let tx = runner.tx.clone();
@ -182,6 +182,9 @@ impl Future for Runner {
debug!("real time used: {} ms", status.real_time_used);
debug!("cgroup memory used: {} KiB", status.cgroup_memory_used);
debug!("rusage memory used: {} KiB", status.memory_used);
if status.signal < 0 {
return Poll::Ready(Err(Error::SystemError(status.errmsg)));
}
return Poll::Ready(Ok(status));
}
}

View File

@ -1,4 +1,5 @@
use crate::error::Error;
use crate::error::Result;
use crate::river::judge_response::State;
use crate::river::{JudgeResponse, JudgeResult, JudgeResultEnum, JudgeStatus};
@ -38,12 +39,242 @@ pub fn compile_error(time_used: i64, memory_used: i64, errmsg: &str) -> JudgeRes
}
pub fn compile_success(time_used: i64, memory_used: i64) -> JudgeResponse {
judge_result(time_used, memory_used, JudgeResultEnum::CompileSuccess)
}
pub fn accepted(time_used: i64, memory_used: i64) -> JudgeResponse {
judge_result(time_used, memory_used, JudgeResultEnum::Accepted)
}
pub fn wrong_answer(time_used: i64, memory_used: i64) -> JudgeResponse {
judge_result(time_used, memory_used, JudgeResultEnum::WrongAnswer)
}
pub fn time_limit_exceeded(time_used: i64, memory_used: i64) -> JudgeResponse {
judge_result(time_used, memory_used, JudgeResultEnum::TimeLimitExceeded)
}
pub fn memory_limit_exceeded(time_used: i64, memory_used: i64) -> JudgeResponse {
judge_result(time_used, memory_used, JudgeResultEnum::MemoryLimitExceeded)
}
pub fn runtime_error(time_used: i64, memory_used: i64, errmsg: &str) -> JudgeResponse {
JudgeResponse {
state: Some(State::Result(JudgeResult {
time_used: time_used,
memory_used: memory_used,
result: JudgeResultEnum::CompileSuccess as i32,
result: JudgeResultEnum::RuntimeError as i32,
errmsg: String::from(errmsg),
})),
}
}
fn judge_result(time_used: i64, memory_used: i64, result: JudgeResultEnum) -> JudgeResponse {
JudgeResponse {
state: Some(State::Result(JudgeResult {
time_used: time_used,
memory_used: memory_used,
result: result as i32,
errmsg: String::from(""),
})),
}
}
pub fn standard_result(out: &[u8], ans: &[u8]) -> Result<JudgeResultEnum> {
let out_len = out.len();
let ans_len = ans.len();
let mut out_offset = 0;
let mut ans_offset = 0;
// 没有 PEPE 直接 WA
let mut r = JudgeResultEnum::Accepted;
while out_offset <= out_len && ans_offset <= ans_len {
let (out_start, out_end, out_exists) = next_line(&out, out_offset, out_len);
let (ans_start, ans_end, ans_exists) = next_line(&ans, ans_offset, ans_len);
if !out_exists || !ans_exists {
// 如果一个已经读取完但另一个还有数据,则结果为 WA
if out_exists != ans_exists {
r = JudgeResultEnum::WrongAnswer;
}
break;
}
// 如果两个数据当前行长度不同,则结果为 WA这个长度已经排除了末尾空白符号
if out_end - out_start != ans_end - ans_start {
r = JudgeResultEnum::WrongAnswer;
break;
}
let line_len = out_end - out_start;
for i in 0..line_len {
// 逐个对比
if out[out_start + i] != ans[ans_start + i] {
r = JudgeResultEnum::WrongAnswer;
break;
}
}
// 如果结果出来了,则退出循环
if r != JudgeResultEnum::Accepted {
break;
}
out_offset = out_end;
ans_offset = ans_end;
}
Ok(r)
}
/**
*
*
* "Hello ; "
* " "
* " World"
* -----------------
* "Hello ;"
* " World"
*/
fn next_line(v: &[u8], offset: usize, len: usize) -> (usize, usize, bool) {
let mut line_offset = offset;
let mut left = 0;
let mut right = len;
let mut has_line = false;
while line_offset < len {
let ch = v[line_offset] as char;
// 当读取到某行结束时
if ch == '\n' {
if has_line {
// 如果已经有新行的数据,则在这个位置结束
right = line_offset;
break;
} else {
// 如果还没有数据,说明整行为空,忽略当前行,将下一行设为起点重复过程
left = line_offset + 1;
}
}
if ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' {
// 空白字符
} else {
// 非空白字符
has_line = true;
}
line_offset += 1;
}
// 排除该行末尾的空白字符
while left < right {
let ch = v[right - 1] as char;
if ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' {
// 空白字符
} else {
// 非空白字符
break;
}
right -= 1;
}
(left, right, has_line)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test1() {
let v: &[u8] = "Hello World!".as_bytes();
let (l, r, e) = next_line(v, 0, v.len());
assert_eq!(l, 0);
assert_eq!(r, 12);
assert!(e);
let (_l, _r, e) = next_line(v, r, v.len());
assert!(!e);
}
#[test]
fn test2() {
let v: &[u8] = "Hello World! ".as_bytes();
let (l, r, e) = next_line(v, 0, v.len());
assert_eq!(l, 0);
assert_eq!(r, 12);
assert!(e);
}
#[test]
fn test3() {
let v: &[u8] = " Hello World!".as_bytes();
let (l, r, e) = next_line(v, 0, v.len());
assert_eq!(l, 0);
assert_eq!(r, 15);
assert!(e);
}
#[test]
fn test4() {
let v: &[u8] =
" Hello World!\n Hello World!\n\n\n\n \n\n\n\n\n\n \t\t\t \t\n\n\n\n"
.as_bytes();
let (l, r, e) = next_line(v, 0, v.len());
assert_eq!(l, 0);
assert_eq!(r, 15);
assert!(e);
let (l, r, e) = next_line(v, r, v.len());
assert_eq!(l, 16);
assert_eq!(r, 31);
assert!(e);
let (_l, _r, e) = next_line(v, r, v.len());
assert!(!e);
}
#[test]
fn test5() {
let ans: &[u8] = "Hello World!".as_bytes();
let out: &[u8] = "Hello World!".as_bytes();
assert_eq!(
standard_result(out, ans).unwrap(),
JudgeResultEnum::Accepted
);
}
#[test]
fn test6() {
let ans: &[u8] = "Hello World!".as_bytes();
let out: &[u8] = "Hello World! ".as_bytes();
assert_eq!(
standard_result(out, ans).unwrap(),
JudgeResultEnum::Accepted
);
}
#[test]
fn test7() {
let ans: &[u8] = "Hello World! \n\n\n\n \n\n\n\n".as_bytes();
let out: &[u8] = "Hello World!\t\t\t\t\n\n\n\n \n\n\n\n\t\t\t\t".as_bytes();
assert_eq!(
standard_result(out, ans).unwrap(),
JudgeResultEnum::Accepted
);
}
#[test]
fn test8() {
let ans: &[u8] = "Hello World!".as_bytes();
let out: &[u8] = "".as_bytes();
assert_eq!(
standard_result(out, ans).unwrap(),
JudgeResultEnum::WrongAnswer
);
}
#[test]
fn test9() {
let ans: &[u8] = "".as_bytes();
let out: &[u8] = "".as_bytes();
assert_eq!(
standard_result(out, ans).unwrap(),
JudgeResultEnum::Accepted
);
}
#[test]
fn test10() {
let ans: &[u8] = "Hello World!".as_bytes();
let out: &[u8] = "Hello World!\n".as_bytes();
assert_eq!(
standard_result(out, ans).unwrap(),
JudgeResultEnum::Accepted
);
}
}