mirror of
https://github.com/MeiK2333/river.git
synced 2025-09-26 22:49:11 +08:00
基本流程就绪
This commit is contained in:
parent
33f93913d6
commit
04fe473175
1
runtime/data/1000/1.in
Normal file
1
runtime/data/1000/1.in
Normal file
@ -0,0 +1 @@
|
||||
1 2
|
1
runtime/data/1000/1.out
Normal file
1
runtime/data/1000/1.out
Normal file
@ -0,0 +1 @@
|
||||
3
|
0
runtime/data/1001/1.in
Normal file
0
runtime/data/1001/1.in
Normal file
1
runtime/data/1001/1.out
Normal file
1
runtime/data/1001/1.out
Normal file
@ -0,0 +1 @@
|
||||
Hello World!
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
))))
|
||||
}
|
||||
|
@ -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")))
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
233
src/result.rs
233
src/result.rs
@ -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;
|
||||
// 没有 PE,PE 直接 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user