diff --git a/.gitignore b/.gitignore index 2446ab5..c206870 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ Cargo.lock .idea .vscode +.devcontainer diff --git a/Cargo.toml b/Cargo.toml index d95e9f5..82040de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ async-stream = "0.2" tower = "0.3" tempfile = "3" libc = "0.2.76" +log = "0.4.0" +env_logger = "0.8.1" [build-dependencies] tonic-build ={ version = "0.3" } diff --git a/README.md b/README.md index d6bad2c..6076160 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,25 @@ ## 环境要求 - linux + +## Example + +```json +{ + "language": 0, + "judge_type": 0, + "compile_data": { + "code": "I2luY2x1ZGUgPHN0ZGlvLmg+CmludCBtYWluKCkgewogIHByaW50ZigiSGVsbG8gV29ybGQhXG4iKTsKICByZXR1cm4gMDsKfQ==" + }, + "judge_data": { + "in_data": "SGVsbG8gV29ybGQh", + "out_data": "SGVsbG8gV29ybGQh", + "time_limit": 1000, + "memory_limit": 65535 + } +} +``` + +## TODOs + +- 修改为文件形式的输入输出,为了这部分的优化我浪费了太多的时间,最后发现过早的优化果然是万恶之源。 diff --git a/proto/river.proto b/proto/river.proto index fe2459c..f139838 100644 --- a/proto/river.proto +++ b/proto/river.proto @@ -62,11 +62,13 @@ enum JudgeStatus { message JudgeResponse { int64 time_used = 1; int64 memory_used = 2; - JudgeResult result = 3; + oneof state { + JudgeResult result = 3; + JudgeStatus status = 9; + } // int32 errno = 4; // int32 exit_code = 5; // string stdout = 6; // string stderr = 7; string errmsg = 8; - JudgeStatus status = 9; } diff --git a/src/error.rs b/src/error.rs index 081df49..02f3e6c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,6 @@ +use crate::river::judge_response::State; use crate::river::JudgeResponse; -use crate::river::{JudgeResult, JudgeStatus}; +use crate::river::JudgeResult; use libc::strerror; use std::ffi::{CStr, NulError, OsString}; use std::fmt; @@ -21,6 +22,7 @@ pub enum Error { UnknownRequestData, RequestDataNotFound, SyscallError(String), + CustomError(String), } pub type Result = result::Result; @@ -44,6 +46,7 @@ impl fmt::Display for Error { let reason = errno_str(errno); write!(f, "SyscallError: `{}` {}", syscall, reason) } + Error::CustomError(ref reason) => write!(f, "{}", reason), _ => write!(f, "{:?}", self), } } @@ -53,8 +56,7 @@ pub fn system_error(err: Error) -> JudgeResponse { JudgeResponse { time_used: 0, memory_used: 0, - result: JudgeResult::SystemError as i32, + state: Some(State::Result(JudgeResult::SystemError as i32)), errmsg: format!("{}", err).into(), - status: JudgeStatus::Ended as i32, } } diff --git a/src/judger.rs b/src/judger.rs index 62c9d61..37e17b6 100644 --- a/src/judger.rs +++ b/src/judger.rs @@ -1,6 +1,7 @@ use super::error::{Error, Result}; use super::process::Process; use super::runner::RunnerStatus; +use crate::river::judge_response::State; use crate::river::Language; use crate::river::{CompileData, JudgeData, JudgeResult, JudgeStatus}; use crate::river::{JudgeRequest, JudgeResponse}; @@ -12,9 +13,8 @@ impl JudgeResponse { JudgeResponse { time_used: 0, memory_used: 0, - result: JudgeResult::Accepted as i32, errmsg: "".into(), - status: JudgeStatus::Running as i32, + state: Some(State::Status(JudgeStatus::Running as i32)), } } @@ -62,13 +62,11 @@ pub async fn judger( let mut buf: [u8; 1024] = [0; 1024]; reader.readline(&mut buf)?; - resp.status = JudgeStatus::Ended as i32; // TODO: 根据返回值等判断 tle、mle、re 等状态 // TODO: 对比答案,检查结果 if status.exit_code != 0 { resp.set_process_status(&status); - resp.result = JudgeResult::WrongAnswer as i32; - resp.status = JudgeStatus::Ended as i32; + resp.state = Some(State::Result(JudgeResult::WrongAnswer as i32)); } Ok(resp) } @@ -79,7 +77,6 @@ pub async fn compile( path: &Path, ) -> Result { let mut resp = JudgeResponse::new(); - resp.status = JudgeStatus::Ended as i32; // 写入代码 let filename = match Language::from_i32(request.language) { Some(Language::C) => "main.c", @@ -112,17 +109,26 @@ pub async fn compile( let runner = process.runner.clone(); let status = runner.await?; + resp.set_process_status(&status); if status.exit_code != 0 { - resp.set_process_status(&status); - // resp.errmsg = format!("{}{}", status.stdout, status.stderr); - resp.result = JudgeResult::CompileError as i32; + debug!("compile exit code: {}", status.exit_code); + let reader = process.stdout_reader()?; + let stdout = reader.read()?; + debug!("stdout {}", stdout); + let reader = process.stderr_reader()?; + let stderr = reader.read()?; + debug!("stderr {}", stderr); + resp.errmsg = stderr; + resp.state = Some(State::Result(JudgeResult::CompileError as i32)); + } else { + resp.state = Some(State::Result(JudgeResult::Accepted as i32)); } Ok(resp) } pub fn pending() -> JudgeResponse { let mut resp = JudgeResponse::new(); - resp.status = JudgeStatus::Pending as i32; + resp.state = Some(State::Status(JudgeStatus::Pending as i32)); resp } diff --git a/src/main.rs b/src/main.rs index be5e9f1..2f8b9f6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,8 @@ #![recursion_limit = "256"] +#[macro_use] +extern crate log; +use env_logger::Env; use futures::StreamExt; use futures_core::Stream; use river::judge_request::Data; @@ -36,7 +39,6 @@ impl River for RiverService { let mut stream = request.into_inner(); let output = async_stream::try_stream! { - // 创建评测使用的临时目录 // 无需主动删除,变量在 drop 之后会自动删除临时文件夹 let pwd = match tempdir_in("./runner") { @@ -46,6 +48,7 @@ impl River for RiverService { return }, }; + debug!("create tempdir: {:?}", pwd); while let Some(req) = stream.next().await { // TODO: 使用锁或者资源量等机制限制并发 @@ -55,8 +58,14 @@ impl River for RiverService { yield judger::running(); let result = match &req.data { - Some(Data::CompileData(data)) => judger::compile(&req, &data, &pwd.path()).await, - Some(Data::JudgeData(data)) => judger::judger(&req, &data, &pwd.path()).await, + Some(Data::CompileData(data)) => { + debug!("compile request"); + judger::compile(&req, &data, &pwd.path()).await + }, + Some(Data::JudgeData(data)) => { + debug!("judge request"); + judger::judger(&req, &data, &pwd.path()).await + }, None => Err(error::Error::RequestDataNotFound), _ => Err(error::Error::UnknownRequestData), }; @@ -75,9 +84,17 @@ impl River for RiverService { #[tokio::main] async fn main() -> Result<(), Box> { + let env = Env::default() + .filter_or("MY_LOG_LEVEL", "debug") + .write_style_or("MY_LOG_STYLE", "always"); + + env_logger::init_from_env(env); + let addr = "127.0.0.1:4003".parse()?; let river = RiverService::default(); + info!("listen on: {}", addr); + Server::builder() .concurrency_limit_per_connection(5) .add_service(RiverServer::new(river)) diff --git a/src/process.rs b/src/process.rs index 2d6f57b..7f81b3d 100644 --- a/src/process.rs +++ b/src/process.rs @@ -78,6 +78,9 @@ impl Process { // 为进程设置 stdin 的数据 pub fn set_stdin(&mut self, in_data: &Vec) -> Result<()> { + if in_data.len() == 0 { + return Ok(()) + } let memfile = path_buf_str(&self.runner.workdir)?; let memfile = format!("{}{}", "stdin", memfile); // 打开内存文件 diff --git a/src/reader.rs b/src/reader.rs index 134d0a8..899e30c 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -2,8 +2,10 @@ use super::error::{Error, Result}; use libc; use std::ffi::CString; use std::os::raw::c_char; +use std::str; pub struct Reader { + fd: i32, stream: *mut libc::FILE, } @@ -14,7 +16,7 @@ impl Reader { Err(e) => return Err(Error::StringToCStringError(e)), }; let stream = unsafe { libc::fdopen(fd, mode.as_ptr()) }; - Ok(Reader { stream: stream }) + Ok(Reader { fd, stream: stream }) } pub fn readline(&self, buf: &mut [u8]) -> Result<()> { let res = unsafe { @@ -29,6 +31,19 @@ impl Reader { } Ok(()) } + pub fn read(&self) -> Result { + let mut result: String = "".to_owned(); + let mut buf: [u8; 1024] = [0; 1024]; + while unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut libc::c_void, 1024) } != 0 { + let s = match str::from_utf8(&buf) { + Ok(val) => (val), + Err(_) => return Err(Error::SyscallError("read".to_string())), + }; + debug!("{}", s); + result.push_str(s); + } + Ok(result) + } } impl Drop for Reader {