Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slhmy/judge server v1 #121

Merged
merged 5 commits into from
Sep 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
{
"recommendations": [
"swellaby.rust-pack",
"lizebang.bash-extension-pack"
]
"recommendations": ["swellaby.rust-pack", "lizebang.bash-extension-pack", "pkief.material-icon-theme"]
}
9 changes: 9 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"material-icon-theme.folders.associations": {
"judge-core": "core",
"judge-cli": "command",
"judge-service": "helper",
"judge-server": "server",
"postman": "api"
}
}
1 change: 1 addition & 0 deletions dev-problem-package/hello-world/.timelimit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1
1 change: 1 addition & 0 deletions dev-problem-package/hello-world/data/secret/0.ans
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello! world!
1 change: 1 addition & 0 deletions dev-problem-package/hello-world/data/secret/0.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
world!
1 change: 1 addition & 0 deletions dev-problem-package/hello-world/data/secret/1.ans
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello! oj-lab!
1 change: 1 addition & 0 deletions dev-problem-package/hello-world/data/secret/1.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
oj-lab!
13 changes: 13 additions & 0 deletions dev-problem-package/hello-world/problem.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: Hello world

limits:
time_multiplier: 5
time_safety_margin: 2
memory: 2048
output: 8
code: 128
compilation_time: 60
compilation_memory: 2048
validation_time: 60
validation_memory: 2048
validation_output: 8
14 changes: 12 additions & 2 deletions judge-core/src/compiler.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::error::JudgeCoreError;
use crate::utils::get_pathbuf_str;
use anyhow::anyhow;
use serde_derive::Serialize;
use serde_derive::{Serialize, Deserialize};
use std::fmt;
use std::path::PathBuf;
use std::{process::Command, str::FromStr};
Expand Down Expand Up @@ -37,14 +37,24 @@ impl CommandBuilder {
}
}

#[derive(Debug, Clone, PartialEq, Copy, Serialize)]
#[derive(Debug, Clone, PartialEq, Copy, Serialize, Deserialize)]
pub enum Language {
Rust,
Cpp,
Python,
// add other supported languages here
}

impl Language {
pub fn get_extension(&self) -> String {
match self {
Self::Rust => "rs".to_string(),
Self::Cpp => "cpp".to_string(),
Self::Python => "py".to_string(),
}
}
}

impl fmt::Display for Language {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Expand Down
24 changes: 12 additions & 12 deletions judge-core/src/judge/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,47 +87,47 @@ pub fn run_checker(config: &JudgeConfig) -> Result<(JudgeVerdict, i32), JudgeCor
}
}

pub fn run_judge(config: &JudgeConfig) -> Result<Option<JudgeResultInfo>, JudgeCoreError> {
pub fn run_judge(config: &JudgeConfig) -> Result<JudgeResultInfo, JudgeCoreError> {
let (user_verdict, user_time, max_mem, user_exit_status) = run_user(config)?;
if let Some(verdict) = user_verdict {
return Ok(Some(JudgeResultInfo {
return Ok(JudgeResultInfo {
verdict,
time_usage: user_time,
memory_usage_bytes: max_mem,
exit_status: user_exit_status,
checker_exit_status: 0,
}));
});
}

log::debug!("Creating sandbox for checker process");
if let Some(_checker_executor) = config.checker.executor.clone() {
let (verdict, checker_exit_status) = run_checker(config)?;
Ok(Some(JudgeResultInfo {
Ok(JudgeResultInfo {
verdict,
time_usage: user_time,
memory_usage_bytes: max_mem,
exit_status: user_exit_status,
checker_exit_status,
}))
})
} else if compare_files(
&PathBuf::from(&config.program.output_file_path),
&PathBuf::from(&config.test_data.answer_file_path),
) {
Ok(Some(JudgeResultInfo {
Ok(JudgeResultInfo {
verdict: JudgeVerdict::Accepted,
time_usage: user_time,
memory_usage_bytes: max_mem,
exit_status: user_exit_status,
checker_exit_status: 0,
}))
})
} else {
Ok(Some(JudgeResultInfo {
Ok(JudgeResultInfo {
verdict: JudgeVerdict::WrongAnswer,
time_usage: user_time,
memory_usage_bytes: max_mem,
exit_status: user_exit_status,
checker_exit_status: 0,
}))
})
}
}

Expand Down Expand Up @@ -188,7 +188,7 @@ pub mod common_judge_tests {

let runner_config = build_test_config(program_executor);
let result = run_judge(&runner_config);
if let Ok(Some(result)) = result {
if let Ok(result) = result {
log::debug!("{:?}", result);
assert_eq!(result.verdict, JudgeVerdict::Accepted);
} else {
Expand All @@ -206,7 +206,7 @@ pub mod common_judge_tests {
let runner_config = build_test_config(program_executor);
let result = run_judge(&runner_config);
assert!(result.is_ok());
if let Ok(Some(result)) = result {
if let Ok(result) = result {
log::debug!("{:?}", result);
assert_eq!(result.verdict, JudgeVerdict::TimeLimitExceeded);
}
Expand All @@ -221,7 +221,7 @@ pub mod common_judge_tests {
let runner_config = build_test_config(program_executor);
let result = run_judge(&runner_config);
assert!(result.is_ok());
if let Ok(Some(result)) = result {
if let Ok(result) = result {
log::debug!("{:?}", result);
assert_eq!(result.verdict, JudgeVerdict::RuntimeError);
}
Expand Down
6 changes: 4 additions & 2 deletions judge-core/src/judge/result.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use serde_derive::Serialize;

use crate::run::sandbox::RawRunResultInfo;
use std::{fmt, ops::Add, time::Duration};

#[derive(Debug)]
#[derive(Debug, Serialize)]
pub struct JudgeResultInfo {
pub verdict: JudgeVerdict,
pub time_usage: Duration,
Expand All @@ -10,7 +12,7 @@ pub struct JudgeResultInfo {
pub checker_exit_status: i32,
}

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Serialize)]
pub enum JudgeVerdict {
Accepted,
WrongAnswer,
Expand Down
13 changes: 5 additions & 8 deletions judge-core/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,14 @@ use std::io::{BufRead, BufReader};
use crate::error::JudgeCoreError;

pub fn compare_files(file_path1: &PathBuf, file_path2: &PathBuf) -> bool {
log::debug!("Comparing output files");
let file1 = BufReader::new(File::open(file_path1).unwrap());
let file2 = BufReader::new(File::open(file_path2).unwrap());

file1.lines().zip(file2.lines()).all(|(line1, line2)| {
// Ignore any trailing whitespace or newline characters
let line1_string = line1.unwrap();
let line2_string: String = line2.unwrap();
let trimed1 = line1_string.trim_end();
let trimed2 = line2_string.trim_end();
trimed1 == trimed2
})
let file1_content: String = file1.lines().map(|l| l.unwrap()).collect();
let file2_content: String = file2.lines().map(|l| l.unwrap()).collect();

file1_content.trim_end() == file2_content.trim_end()
}

pub fn get_pathbuf_str(path: &PathBuf) -> Result<String, JudgeCoreError> {
Expand Down
2 changes: 2 additions & 0 deletions judge-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@ chrono = { version = "0.4", features = ["serde"] }
anyhow = "1"
thiserror = "1"

uuid = { version = "1.4", features = ["serde", "v4"] }

judge-core = { path = "../judge-core" }
7 changes: 6 additions & 1 deletion judge-server/src/environment/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::path::PathBuf;

use structopt::StructOpt;

#[derive(StructOpt, Debug, Clone)]
Expand All @@ -9,6 +11,9 @@ pub struct JudgeServerOpt {
/// Port to listen to
#[structopt(env = "PORT", default_value = "8000")]
pub port: u16,

#[structopt(long, default_value = "dev-problem-package")]
pub problem_package_dir: PathBuf,
}

pub fn load_option() -> JudgeServerOpt {
Expand All @@ -27,5 +32,5 @@ pub fn load_option() -> JudgeServerOpt {
}

pub fn setup_logger() {
env_logger::init()
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init();
}
36 changes: 21 additions & 15 deletions judge-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mod service;
extern crate serde_derive;
extern crate lazy_static;

use actix_web::{App, HttpServer};
use actix_web::{App, HttpServer, web::Data};
use utoipa::OpenApi;

#[actix_web::main] // or #[tokio::main]
Expand All @@ -15,22 +15,28 @@ async fn main() -> std::io::Result<()> {
environment::setup_logger();

// Suppose to send heartbeat here to a remote host
tokio::spawn(async move {
loop {
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
log::debug!("JudgeSever heartbeat")
}
});
// tokio::spawn(async move {
// loop {
// tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
// log::debug!("JudgeSever heartbeat")
// }
// });

HttpServer::new(|| {
App::new().configure(service::route).service(
utoipa_swagger_ui::SwaggerUi::new("/swagger-ui/{_:.*}").urls(vec![(
utoipa_swagger_ui::Url::new("api", "/api-docs/openapi.json"),
service::ApiDoc::openapi(),
)]),
)
let port = opt.port;

HttpServer::new(move || {
App::new()
.wrap(actix_web::middleware::Logger::default())
.app_data(Data::new(opt.problem_package_dir.clone()))
.configure(service::route)
.service(
utoipa_swagger_ui::SwaggerUi::new("/swagger-ui/{_:.*}").urls(vec![(
utoipa_swagger_ui::Url::new("api", "/api-docs/openapi.json"),
service::ApiDoc::openapi(),
)]),
)
})
.bind(("127.0.0.1", opt.port))?
.bind(("127.0.0.1", port))?
.run()
.await
}
88 changes: 82 additions & 6 deletions judge-server/src/service/judge.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
use std::{fs, path::PathBuf};

use crate::error::ServiceError;
use actix_web::{post, web, HttpResponse};

use judge_core::{
compiler::Language,
error::JudgeCoreError,
judge::{
self,
builder::{JudgeBuilder, JudgeBuilderInput},
result::JudgeResultInfo,
JudgeConfig,
},
package::PackageType,
};
use tokio::task::JoinHandle;
use utoipa::ToSchema;

#[derive(utoipa::OpenApi)]
Expand All @@ -13,20 +28,81 @@ pub fn route(cfg: &mut web::ServiceConfig) {
// TODO: Remove the first `_` when the segment is actually used
#[derive(Debug, ToSchema, Deserialize)]
pub struct RunJudgeBody {
_src: String,
_test_case_id: String,
src: String,
src_language: Language
}

#[utoipa::path(
context_path = "/api/judge",
context_path = "/api/v1/judge",
request_body(content = RunJudgeBody, content_type = "application/json", description = "The info a judge task should refer to"),
responses(
(status = 200, description = "Judge run successfully")
)
)]
#[post("")]
pub async fn run_judge(body: web::Json<RunJudgeBody>) -> Result<HttpResponse, ServiceError> {
#[post("/{package_slug}")]
pub async fn run_judge(
path: web::Path<String>,
body: web::Json<RunJudgeBody>,
problem_package_dir: web::Data<PathBuf>,
) -> Result<HttpResponse, ServiceError> {
let package_slug = path.into_inner();
log::debug!("receive body: {:?}", body);

Ok(HttpResponse::Ok().finish())
let uuid = uuid::Uuid::new_v4();
let runtime_path = PathBuf::from("/tmp").join(uuid.to_string());
let src_file_name = format!("src.{}", body.src_language.get_extension());
println!("runtime_path: {:?}", runtime_path);
fs::create_dir_all(runtime_path.clone()).map_err(|e| {
println!("Failed to create runtime dir: {:?}", e);
ServiceError::InternalError(anyhow::anyhow!("Failed to create runtime dir"))
})?;
fs::write(runtime_path.clone().join(&src_file_name), body.src.clone()).map_err(
|e| {
println!("Failed to write src file: {:?}", e);
ServiceError::InternalError(anyhow::anyhow!("Failed to write src file"))
},
)?;

let handle: JoinHandle<Result<Vec<JudgeResultInfo>, JudgeCoreError>> =
tokio::spawn(async move {
let new_builder_result = JudgeBuilder::new(JudgeBuilderInput {
package_type: PackageType::ICPC,
package_path: problem_package_dir.join(package_slug.clone()),
runtime_path: runtime_path.clone(),
src_language: body.src_language,
src_path: runtime_path.clone().join(&src_file_name),
});
if new_builder_result.is_err() {
println!(
"Failed to new builder result: {:?}",
new_builder_result.err()
);
return Ok(vec![]);
}
let builder = new_builder_result?;
println!("Builder created: {:?}", builder);
let mut results: Vec<JudgeResultInfo> = vec![];
for idx in 0..builder.testdata_configs.len() {
let judge_config = JudgeConfig {
test_data: builder.testdata_configs[idx].clone(),
program: builder.program_config.clone(),
checker: builder.checker_config.clone(),
runtime: builder.runtime_config.clone(),
};
let result = judge::common::run_judge(&judge_config)?;
println!("Judge result: {:?}", result);
results.push(result);
}

println!("BatchJudge finished");
Ok(results)
});

match handle.await.unwrap() {
Ok(results) => Ok(HttpResponse::Ok().json(results)),
Err(e) => {
println!("Failed to await handle: {:?}", e);
Err(ServiceError::InternalError(anyhow::anyhow!("Judge failed")))
}
}
}
Loading