Files
KF-OnlineJudge/judge/crates/worker/src/lib.rs
T

120 lines
4.8 KiB
Rust

//! Ordinary Worker adapter. Privileged execution and artifact capabilities stay
//! in sandbox; comparison and the final summary stay in Engine.
#[cfg(target_os = "linux")]
pub mod native {
use kf_engine::{Case, CaseRequest, ExecutionKind, ExecutionPort, Observation, PortError};
use kf_sandbox::{
artifacts::ArtifactRoot,
outcome::{ExecutionOutcome, LimitKind, Measurement},
spec::{ExecutionProfile, ExecutionSpec, InputArtifact, ResourceLimits},
};
use serde::Deserialize;
use std::{
io::{self, Read},
path::{Path, PathBuf},
};
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Job {
pub request: CaseRequest,
pub expected_artifact: String,
}
pub struct SandboxPort {
socket: PathBuf,
artifacts: ArtifactRoot,
}
impl ExecutionPort for SandboxPort {
fn execute(&mut self, request: &CaseRequest) -> Result<Observation, PortError> {
let spec = ExecutionSpec {
schema_version: kf_contracts::SchemaVersion,
run_id: request.run_id.clone(),
profile: ExecutionProfile::Native,
argv: vec!["/work/main".into()],
env: std::collections::BTreeMap::new(),
cwd: ".".into(),
inputs: vec![InputArtifact {
artifact_id: request.program_artifact.clone(),
path: "main".into(),
}],
stdin_artifact: request.stdin_artifact.clone(),
outputs: vec![],
limits: ResourceLimits::for_profile(ExecutionProfile::Native),
};
let validated = spec
.clone()
.validate(&kf_sandbox::spec::SpecPolicy::default())
.map_err(|_| PortError)?;
let result = kf_sandbox::rpc::execute(&self.socket, spec, &self.artifacts)
.map_err(|_| PortError)?;
let outcome = result.report.classify(&validated).map_err(|_| PortError)?;
let kind = match outcome {
ExecutionOutcome::Exited { code } => ExecutionKind::Exited { code },
ExecutionOutcome::Signaled { .. } => ExecutionKind::Signaled,
ExecutionOutcome::LimitExceeded {
primary: LimitKind::Memory,
} => ExecutionKind::MemoryLimit,
ExecutionOutcome::LimitExceeded {
primary: LimitKind::CpuTime | LimitKind::WallTime,
} => ExecutionKind::TimeLimit,
ExecutionOutcome::LimitExceeded {
primary: LimitKind::Stdout | LimitKind::Stderr,
} => ExecutionKind::OutputLimit,
ExecutionOutcome::LimitExceeded { .. } => ExecutionKind::OtherLimit,
ExecutionOutcome::Cancelled => ExecutionKind::Cancelled,
ExecutionOutcome::InfrastructureFailure | ExecutionOutcome::NotStarted => {
ExecutionKind::InfrastructureFailure
}
};
let mut stdout = Vec::new();
result
.stdout
.reader()
.map_err(|_| PortError)?
.read_to_end(&mut stdout)
.map_err(|_| PortError)?;
let value = |m: Measurement<u64>| match m {
Measurement::Measured { value } => Some(value),
Measurement::Unavailable { .. } => None,
};
Ok(Observation {
kind,
stdout,
cpu_time_us: value(result.report.usage.cpu_time_us),
memory_peak_bytes: value(result.report.usage.memory_peak_bytes),
})
}
}
pub fn judge_once(
socket: &Path,
job_path: &Path,
artifact_path: &Path,
) -> io::Result<kf_engine::Judgement> {
let data =
kf_sandbox::execution::read_bounded_file(job_path, kf_sandbox::spec::MAX_SPEC_BYTES)?;
let job: Job =
serde_json::from_slice(&data).map_err(|_| io::Error::other("invalid native job"))?;
let artifacts = ArtifactRoot::open(artifact_path)?;
let expected = artifacts.snapshot_artifact(
&job.expected_artifact,
ResourceLimits::for_profile(ExecutionProfile::Native).stdout_bytes,
)?;
let mut bytes = Vec::new();
expected.reader()?.read_to_end(&mut bytes)?;
let case = Case {
request: job.request,
expected: bytes,
};
let mut port = SandboxPort {
socket: socket.to_owned(),
artifacts,
};
kf_engine::judge(&mut port, &[case]).map_err(|_| io::Error::other("invalid judge request"))
}
}
#[cfg(target_os = "linux")]
pub mod pipeline;
#[cfg(target_os = "linux")]
pub mod consumer;