基本流程就绪

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

View File

@ -16,6 +16,7 @@ pub enum Error {
CreateTempDirError(io::Error), CreateTempDirError(io::Error),
CustomError(String), CustomError(String),
LanguageNotFound(String), LanguageNotFound(String),
SystemError(String),
} }
pub type Result<T> = result::Result<T, Error>; 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::IOError(ref e) => write!(f, "IOError: `{}`", errno_str(e.raw_os_error())),
Error::CustomError(ref e) => write!(f, "Internal Server Error: `{}`", e), Error::CustomError(ref e) => write!(f, "Internal Server Error: `{}`", e),
Error::LanguageNotFound(ref e) => write!(f, "Language Not Fount: `{}`", e), Error::LanguageNotFound(ref e) => write!(f, "Language Not Fount: `{}`", e),
Error::SystemError(ref e) => write!(f, "System Error: `{}`", e),
_ => write!(f, "{:?}", self), _ => 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::error::{Error, Result};
use crate::process::{Process, Runner}; use crate::process::{Process, Runner};
use crate::result::{compile_error, compile_success}; use crate::result::{
use crate::river; accepted, compile_error, compile_success, memory_limit_exceeded, runtime_error,
use crate::river::JudgeResponse; standard_result, time_limit_exceeded, wrong_answer,
use std::fs; };
use std::fs::read_to_string; use crate::river::{JudgeResponse, JudgeResultEnum, JudgeType};
use std::path::Path; use std::path::Path;
use tokio::fs;
#[cfg(test)] use tokio::fs::read_to_string;
use std::println as debug;
pub async fn compile(language: &str, code: &str, path: &Path) -> Result<JudgeResponse> { 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) { let lang = match CONFIG.languages.get(language) {
Some(val) => val, Some(val) => val,
None => return Err(Error::LanguageNotFound(String::from(language))), 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).await);
try_io!(fs::write(path.join(&lang.code_file), &code));
debug!("build command: {}", lang.compile_cmd);
let process = Process::new( let process = Process::new(
String::from(&lang.compile_cmd), String::from(&lang.compile_cmd),
10000, 10000,
@ -41,10 +35,9 @@ pub async fn compile(language: &str, code: &str, path: &Path) -> Result<JudgeRes
// 因为不同的语言、不同的编译器,错误信息输出到了不同的地方 // 因为不同的语言、不同的编译器,错误信息输出到了不同的地方
let errmsg = format!( let errmsg = format!(
"{}\n{}", "{}\n{}",
try_io!(read_to_string(path.join(STDOUT_FILENAME))), try_io!(read_to_string(path.join(STDOUT_FILENAME)).await),
try_io!(read_to_string(path.join(STDERR_FILENAME))), try_io!(read_to_string(path.join(STDERR_FILENAME)).await),
); );
debug!("{}", errmsg);
return Ok(compile_error(status.time_used, mem_used, &errmsg)); return Ok(compile_error(status.time_used, mem_used, &errmsg));
} else { } else {
return Ok(compile_success(status.time_used, mem_used)); return Ok(compile_success(status.time_used, mem_used));
@ -60,16 +53,62 @@ pub async fn judge(
judge_type: i32, judge_type: i32,
path: &Path, path: &Path,
) -> Result<JudgeResponse> { ) -> Result<JudgeResponse> {
debug!("language: {}", language); let data_dir = Path::new(&CONFIG.data_dir);
debug!("in_file: {}", in_file); // 复制输入文件
debug!("out_file: {}", out_file); try_io!(fs::copy(data_dir.join(&in_file), path.join(STDIN_FILENAME)).await);
debug!("time_limit: {}", time_limit);
debug!("memory_limit: {}", memory_limit); let lang = match CONFIG.languages.get(language) {
debug!("judge_type: {}", judge_type); Some(val) => val,
debug!("path: {:?}", path); None => return Err(Error::LanguageNotFound(String::from(language))),
Ok(JudgeResponse { };
state: Some(river::judge_response::State::Status( let process = Process::new(
river::JudgeStatus::Pending as i32, 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 compile_success = false;
let mut language = String::from("");
while let Some(req) = stream.next().await { while let Some(req) = stream.next().await {
yield result::pending(); yield result::pending();
// TODO: 限制并发数量 // TODO: 限制并发数量
yield result::running(); yield result::running();
let req = req?; let req = req?;
let mut language = String::from("");
let result = match &req.data { let result = match &req.data {
Some(Data::CompileData(data)) => { Some(Data::CompileData(data)) => {
debug!("compile request");
// 因为评测时还需要 language 的信息,因此此处进行复制保存 // 因为评测时还需要 language 的信息,因此此处进行复制保存
language = String::from(&data.language); language = String::from(&data.language);
let res = judger::compile(&language, &data.code, &pwd.path()).await; let res = judger::compile(&language, &data.code, &pwd.path()).await;
@ -75,7 +74,6 @@ impl River for RiverService {
res res
}, },
Some(Data::JudgeData(data)) => { Some(Data::JudgeData(data)) => {
debug!("judge request");
// 必须通过编译才能运行 // 必须通过编译才能运行
if language == "" || !compile_success { if language == "" || !compile_success {
Err(error::Error::CustomError(String::from("not compiled"))) 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.apply(pid).unwrap();
runner.cgroup_set.memory.set( runner.cgroup_set.memory.set(
"memory.limit_in_bytes", "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(); let tx = runner.tx.clone();
@ -182,6 +182,9 @@ impl Future for Runner {
debug!("real time used: {} ms", status.real_time_used); debug!("real time used: {} ms", status.real_time_used);
debug!("cgroup memory used: {} KiB", status.cgroup_memory_used); debug!("cgroup memory used: {} KiB", status.cgroup_memory_used);
debug!("rusage memory used: {} KiB", status.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)); return Poll::Ready(Ok(status));
} }
} }

View File

@ -1,4 +1,5 @@
use crate::error::Error; use crate::error::Error;
use crate::error::Result;
use crate::river::judge_response::State; use crate::river::judge_response::State;
use crate::river::{JudgeResponse, JudgeResult, JudgeResultEnum, JudgeStatus}; 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 { 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 { JudgeResponse {
state: Some(State::Result(JudgeResult { state: Some(State::Result(JudgeResult {
time_used: time_used, time_used: time_used,
memory_used: memory_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(""), 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
);
}
}