Skip to content

Commit

Permalink
Adjust verdict decide policy (#149)
Browse files Browse the repository at this point in the history
* Adjust verdict decide policy

* Support TimeLimitExceeded verdict

* Bump to nix 0.28
  • Loading branch information
slhmy authored Mar 16, 2024
1 parent 00553f3 commit 0fe9d32
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 30 deletions.
2 changes: 1 addition & 1 deletion judge-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ description = "A judge library for online judge system"
[dependencies]
libc = "0.2"
libseccomp = "0.3"
nix = { version = "0.27", features = ["event", "fs", "process", "resource"] }
nix = { version = "0.28", features = ["event", "fs", "process", "resource"] }
log = "0.4"
anyhow = "1.0"
serde = "1"
Expand Down
2 changes: 1 addition & 1 deletion judge-core/src/judge/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ fn run_user(
let user_time = get_run_time(&user_result);
let max_mem = get_max_mem(&user_result);
Ok((
check_user_result(&user_result),
check_user_result(config, &user_result),
user_time,
max_mem,
user_result.exit_status,
Expand Down
53 changes: 32 additions & 21 deletions judge-core/src/judge/interact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::utils::get_pathbuf_str;

use nix::errno::Errno;
use nix::fcntl::{fcntl, FcntlArg, OFlag};
use nix::sys::epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags};
use nix::sys::epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags, EpollTimeout};
use nix::unistd::{pipe, read, write};
use std::fs::File;
use std::os::fd::BorrowedFd;
Expand All @@ -31,15 +31,18 @@ fn set_fd_non_blocking(fd: RawFd) -> Result<libc::c_int, JudgeCoreError> {
/// write the content of `from` to `to`, record to output.
/// `from` will be set to non-blocking mode.
fn pump_proxy_pipe(from: RawFd, to: RawFd, output: RawFd) -> Result<(), JudgeCoreError> {
log::debug!("Pumping from {} to {} with output {}", from, to, output);
set_fd_non_blocking(from)?;

let mut buf = [0; 1024];
loop {
match read(from, &mut buf) {
Ok(nread) => {
log::debug!("{} read. {} -> {}", nread, from, to);
write(to, &buf[..nread])?;
write(output, &buf[..nread])?;
// We should be really careful here
// not using OwnedFd here because it will close the fd
write(unsafe { BorrowedFd::borrow_raw(to) }, &buf[..nread])?;
write(unsafe { BorrowedFd::borrow_raw(output) }, &buf[..nread])?;
}
Err(e) => {
if e == Errno::EAGAIN || e == Errno::EWOULDBLOCK {
Expand Down Expand Up @@ -104,19 +107,19 @@ pub fn run_interact(
let (interactor_read_proxy, proxy_write_interactor) = pipe()?;

log::debug!("Adding read proxy fds to epoll");
add_epoll_fd(&epoll, proxy_read_user)?;
add_epoll_fd(&epoll, proxy_read_interactor)?;
add_epoll_fd(&epoll, proxy_read_user.as_raw_fd())?;
add_epoll_fd(&epoll, proxy_read_interactor.as_raw_fd())?;

log::debug!("Creating exit report pipes with epoll");
let (user_exit_read, user_exit_write) = pipe()?;
let (interactor_exit_read, interactor_exit_write) = pipe()?;
add_epoll_fd(&epoll, user_exit_read)?;
add_epoll_fd(&epoll, interactor_exit_read)?;
add_epoll_fd(&epoll, user_exit_read.as_raw_fd())?;
add_epoll_fd(&epoll, interactor_exit_read.as_raw_fd())?;

let mut user_listener = ProcessListener::new()?;
let mut interact_listener = ProcessListener::new()?;
user_listener.setup_exit_report(user_exit_write, USER_EXIT_SIGNAL);
interact_listener.setup_exit_report(interactor_exit_write, INTERACTOR_EXIT_SIGNAL);
user_listener.setup_exit_report(user_exit_write.as_raw_fd(), USER_EXIT_SIGNAL);
interact_listener.setup_exit_report(interactor_exit_write.as_raw_fd(), INTERACTOR_EXIT_SIGNAL);

if !PathBuf::from(&output_path).exists() {
File::create(output_path)?;
Expand All @@ -130,8 +133,8 @@ pub fn run_interact(
let mut user_sandbox = Sandbox::new(
config.program.executor.clone(),
config.runtime.rlimit_configs.clone(),
Some(user_read_proxy),
Some(user_write_proxy),
Some(user_read_proxy.as_raw_fd()),
Some(user_write_proxy.as_raw_fd()),
true,
)?;
user_listener.spawn_with_sandbox(&mut user_sandbox)?;
Expand All @@ -147,8 +150,8 @@ pub fn run_interact(
let mut interact_sandbox = Sandbox::new(
interactor_executor,
SCRIPT_LIMIT_CONFIG.clone(),
Some(interactor_read_proxy),
Some(interactor_write_proxy),
Some(interactor_read_proxy.as_raw_fd()),
Some(interactor_write_proxy.as_raw_fd()),
false,
)?;
interact_listener.spawn_with_sandbox(&mut interact_sandbox)?;
Expand All @@ -159,30 +162,38 @@ pub fn run_interact(
let mut interactor_exited = false;
let mut option_user_result: Option<RawRunResultInfo> = None;
loop {
let num_events = epoll.wait(&mut events, -1)?;
let num_events = epoll.wait(&mut events, EpollTimeout::NONE)?;
log::debug!("{} events found!", num_events);

for event in events.iter().take(num_events) {
log::debug!("Event: {:?}", event);
let fd = event.data() as RawFd;
if fd == user_exit_read {
if fd == user_exit_read.as_raw_fd() {
log::debug!("{:?} user fd exited", fd);
user_exited = true;
let exit_msg = read_msg_from_fd(fd)?;
option_user_result = exit_msg.option_run_result;
}
if fd == interactor_exit_read {
if fd == interactor_exit_read.as_raw_fd() {
log::debug!("{:?} interactor fd exited", fd);
interactor_exited = true;
let _interactor_result: ProcessExitMessage = read_msg_from_fd(fd)?;
}
if fd == proxy_read_user {
if fd == proxy_read_user.as_raw_fd() {
log::debug!("proxy_read_user {} fd read", fd);
pump_proxy_pipe(proxy_read_user, proxy_write_interactor, output_raw_fd)?;
pump_proxy_pipe(
proxy_read_user.as_raw_fd(),
proxy_write_interactor.as_raw_fd(),
output_raw_fd.as_raw_fd(),
)?;
}
if fd == proxy_read_interactor {
if fd == proxy_read_interactor.as_raw_fd() {
log::debug!("proxy_read_interactor {} fd read", fd);
pump_proxy_pipe(proxy_read_interactor, proxy_write_user, output_raw_fd)?;
pump_proxy_pipe(
proxy_read_interactor.as_raw_fd(),
proxy_write_user.as_raw_fd(),
output_raw_fd.as_raw_fd(),
)?;
}
}
if user_exited && interactor_exited {
Expand All @@ -193,7 +204,7 @@ pub fn run_interact(
log::debug!("Epoll finished!");

if let Some(user_result) = option_user_result {
let option_user_verdict = check_user_result(&user_result);
let option_user_verdict = check_user_result(config, &user_result);
if let Some(verdict) = option_user_verdict {
return Ok(Some(JudgeResultInfo {
verdict,
Expand Down
20 changes: 16 additions & 4 deletions judge-core/src/judge/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use serde_derive::Serialize;
use crate::run::sandbox::RawRunResultInfo;
use std::{fmt, ops::Add, time::Duration};

use super::JudgeConfig;

#[derive(Debug, Serialize)]
pub struct JudgeResultInfo {
pub verdict: JudgeVerdict,
Expand Down Expand Up @@ -41,14 +43,24 @@ pub fn get_max_mem(raw_info: &RawRunResultInfo) -> i64 {
rusage.max_rss
}

pub fn check_user_result(raw_info: &RawRunResultInfo) -> Option<JudgeVerdict> {
pub fn check_user_result(
config: &JudgeConfig,
raw_info: &RawRunResultInfo,
) -> Option<JudgeVerdict> {
if let Some(time_limit) = config.runtime.rlimit_configs.get_cpu_limit_duration() {
let run_time = get_run_time(raw_info);
if run_time > time_limit {
log::debug!("User program run time: {:?}", run_time);
log::debug!("Time limit: {:?}", time_limit);
return Some(JudgeVerdict::TimeLimitExceeded);
}
}

let exit_status = raw_info.exit_status;
log::debug!("User program exit status: {}", exit_status);
match exit_status {
0 => None,
11 => Some(JudgeVerdict::RuntimeError),
152 | 24 => Some(JudgeVerdict::TimeLimitExceeded),
_ => Some(JudgeVerdict::SystemError),
_ => Some(JudgeVerdict::RuntimeError),
}
}

Expand Down
12 changes: 11 additions & 1 deletion judge-core/src/run/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ pub struct RlimitConfigs {
}

impl RlimitConfigs {
/// Load the rlimit configs to the current process.
///
/// One thing should be noted is that `RLIMIT_CPU` is set to +1 second of the given value.
/// This is because rlimit will kills the process when CPU almost reaches the limit,
/// which can have a few milliseconds of deviation.
pub fn load(&self) -> Result<(), JudgeCoreError> {
if let Some(stack_limit) = self.stack_limit {
log::debug!("Set stack limit: {:?}", stack_limit);
Expand All @@ -47,8 +52,13 @@ impl RlimitConfigs {
}
if let Some(cpu_limit) = self.cpu_limit {
log::debug!("Set cpu limit: {:?}", cpu_limit);
setrlimit(RLIMIT_CPU, cpu_limit.0, cpu_limit.1)?;
setrlimit(RLIMIT_CPU, cpu_limit.0 + 1, cpu_limit.1 + 1)?;
}
Ok(())
}

pub fn get_cpu_limit_duration(&self) -> Option<std::time::Duration> {
self.cpu_limit
.map(|(soft, _)| std::time::Duration::from_secs(soft))
}
}
6 changes: 4 additions & 2 deletions judge-core/src/run/process_listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::sandbox::{RawRunResultInfo, Sandbox};
use crate::error::JudgeCoreError;
use nix::unistd::{fork, write, ForkResult};
use serde_derive::{Deserialize, Serialize};
use std::os::unix::io::RawFd;
use std::os::{fd::BorrowedFd, unix::io::RawFd};

pub struct ProcessListener {
child_exit_fd: i32,
Expand Down Expand Up @@ -31,7 +31,9 @@ impl ProcessListener {
option_run_result,
};
let buf = serde_json::to_vec(&msg).expect("Serialize failed.");
write(self.child_exit_fd, &buf).unwrap();
// We should be really careful here
// not using OwnedFd here because it will close the fd
write(unsafe { BorrowedFd::borrow_raw(self.child_exit_fd) }, &buf).unwrap();
}
}

Expand Down

0 comments on commit 0fe9d32

Please sign in to comment.