Skip to content

Commit 79dfcbf

Browse files
committed
work
1 parent ad7c150 commit 79dfcbf

10 files changed

Lines changed: 247 additions & 295 deletions

File tree

src/judge/src/controller.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -164,21 +164,21 @@ impl Controller {
164164
let engine = self.invoker_set.clone();
165165
let judge_cx = crate::request_handler::JudgeContext {
166166
events_tx: judge_events_tx,
167-
invoker: rpc::Client::new(rpc::box_engine(engine), "http://does-not-matter".to_string()),
167+
invoker: rpc::Client::new(
168+
rpc::box_engine(engine),
169+
"http://does-not-matter".to_string(),
170+
),
168171
};
169172

170173
// TODO: can we split into LoweredJudgeRequest and Extensions?
171-
let mut responses = worker
172-
.send(Request::Judge(low_req))
173-
.await
174-
.context("failed to submit lowered judge request")?;
174+
crate::request_handler::do_judge(judge_cx, low_req);
175175
loop {
176-
let message = responses
176+
let message = judge_events_rx
177177
.next()
178178
.await
179179
.context("failed to receive next worker message")?;
180180
match message {
181-
Response::JudgeDone(judge_outcome) => {
181+
Event::JudgeDone(judge_outcome) => {
182182
debug!("Publising: JudgeOutcome {:?}", &judge_outcome);
183183
let reason = match judge_outcome {
184184
JudgeOutcome::Fault => InvocationFinishReason::Fault,
@@ -191,13 +191,13 @@ impl Controller {
191191
.context("failed to set run outcome in DB")?;
192192
break;
193193
}
194-
Response::LiveScore(score) => {
194+
Event::LiveScore(score) => {
195195
exts.notifier.set_score(score).await;
196196
}
197-
Response::LiveTest(test) => {
197+
Event::LiveTest(test) => {
198198
exts.notifier.set_test(test).await;
199199
}
200-
Response::OutcomeHeader(header) => {
200+
Event::OutcomeHeader(header) => {
201201
req.callbacks
202202
.add_outcome_header(req.request.request_id, header)
203203
.await?;

src/judge/src/controller/task_loading.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use super::{
44
};
55
use crate::{
66
request_handler::{Command, LoweredJudgeRequest},
7-
worker,
87
};
98
use anyhow::Context;
109
use std::{

src/judge/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,4 @@ pub mod controller;
55
pub mod init;
66
mod invoker_set;
77
pub mod sources;
8-
pub mod worker;
98
mod request_handler;

src/judge/src/request_handler.rs

Lines changed: 183 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,22 @@ use self::{
88
compiler::{BuildOutcome, Compiler},
99
exec_test::{exec, ExecRequest},
1010
};
11-
use judging_apis::Status;
11+
use anyhow::Context;
12+
use judging_apis::{valuer_proto::ValuerResponse, Status};
1213
use serde::{Deserialize, Serialize};
1314
use std::{
1415
borrow::Cow,
1516
future::Future,
1617
path::{Path, PathBuf},
1718
pin::Pin,
1819
};
19-
use tracing::debug;
20-
20+
use tracing::{debug, error, instrument};
2121
/// Allows sending requests to invoker
2222
// TODO upstream BoxedEngine to jjs-commons
2323
pub type InvokerClient = rpc::Client<rpc::BoxEngine>;
2424

25-
pub(crate) struct JudgeContext {
26-
/// Can be used to send requests to invoker
27-
pub(crate) invoker: InvokerClient,
28-
/// Channel that should be used for sending updates
29-
pub(crate) events_tx: async_channel::Sender<Event>,
30-
}
31-
3225
/// Note: this is not `judging_apis::invoke::Command`, it is higher-level.
33-
#[derive(Debug)]
26+
#[derive(Debug, Default)]
3427
pub(crate) struct Command {
3528
pub argv: Vec<String>,
3629
pub env: Vec<String>,
@@ -100,3 +93,182 @@ pub(crate) enum JudgeOutcome {
10093
/// Maybe, several protocols were emitted, but results are neither precise nor complete
10194
Fault,
10295
}
96+
97+
pub(crate) struct JudgeContext {
98+
/// Can be used to send requests to invoker
99+
pub(crate) invoker: InvokerClient,
100+
/// Channel that should be used for sending updates
101+
pub(crate) events_tx: async_channel::Sender<Event>,
102+
}
103+
104+
#[instrument(skip(cx, judge_req), fields(id = %judge_req.judge_request_id))]
105+
pub fn do_judge(mut cx: JudgeContext, judge_req: LoweredJudgeRequest) {
106+
debug!("Got LoweredJudgeRequest: {:?}", &judge_req);
107+
tokio::task::spawn(async move {
108+
let outcome = match cx.judge(&judge_req).await {
109+
Ok(o) => o,
110+
Err(err) => {
111+
error!("Invoke failed: {:#}", err);
112+
cx.create_fake_protocols(
113+
&judge_req,
114+
&judging_apis::Status {
115+
kind: judging_apis::StatusKind::InternalError,
116+
code: judging_apis::status_codes::JUDGE_FAULT.to_string(),
117+
},
118+
)
119+
.await
120+
.ok();
121+
JudgeOutcome::Fault
122+
}
123+
};
124+
debug!("JudgeOutcome: {:?}", &outcome);
125+
cx.events_tx.send(Event::JudgeDone(outcome)).await;
126+
});
127+
}
128+
129+
impl JudgeContext {
130+
async fn judge(&mut self, req: &LoweredJudgeRequest) -> anyhow::Result<JudgeOutcome> {
131+
let compiler = Compiler { req };
132+
133+
if !req.run_source.exists() {
134+
anyhow::bail!("Run source file not exists");
135+
}
136+
137+
if !req.out_dir.exists() {
138+
anyhow::bail!("Run output dir not exists");
139+
}
140+
141+
let compiler_response = compiler.compile();
142+
143+
let outcome;
144+
145+
match compiler_response {
146+
Err(err) => return Err(err).context("compilation error"),
147+
Ok(BuildOutcome::Error(st)) => {
148+
self.create_fake_protocols(req, &st).await?;
149+
outcome = JudgeOutcome::CompileError(st);
150+
}
151+
Ok(BuildOutcome::Success) => {
152+
self.run_tests(req).await.context("failed to run tests")?;
153+
154+
outcome = JudgeOutcome::TestingDone;
155+
}
156+
};
157+
Ok(outcome)
158+
}
159+
160+
/// Used when we are unable to produce protocols, i.e. on compilation errors
161+
/// and judge faults.
162+
async fn create_fake_protocols(
163+
&mut self,
164+
req: &LoweredJudgeRequest,
165+
status: &judging_apis::Status,
166+
) -> anyhow::Result<()> {
167+
for kind in judging_apis::judge_log::JudgeLogKind::list() {
168+
let pseudo_valuer_proto = judging_apis::valuer_proto::JudgeLog {
169+
kind,
170+
tests: vec![],
171+
subtasks: vec![],
172+
score: 0,
173+
is_full: false,
174+
};
175+
let mut protocol = self.process_judge_log(&pseudo_valuer_proto, req, &[])?;
176+
protocol.status = status.clone();
177+
self.put_protocol(req, protocol).await?;
178+
}
179+
Ok(())
180+
}
181+
182+
async fn put_outcome(
183+
&mut self,
184+
score: u32,
185+
status: judging_apis::Status,
186+
kind: judging_apis::judge_log::JudgeLogKind,
187+
) {
188+
let header = judging_apis::JudgeOutcomeHeader {
189+
score: Some(score),
190+
status,
191+
kind,
192+
};
193+
self.events_tx.send(Event::OutcomeHeader(header)).await.ok();
194+
}
195+
196+
async fn put_protocol(
197+
&mut self,
198+
req: &LoweredJudgeRequest,
199+
protocol: judging_apis::judge_log::JudgeLog,
200+
) -> anyhow::Result<()> {
201+
let protocol_file_name = format!("protocol-{}.json", protocol.kind.as_str());
202+
let protocol_path = req.out_dir.join(protocol_file_name);
203+
debug!("Writing protocol to {}", protocol_path.display());
204+
let protocol_file = std::fs::File::create(&protocol_path)?;
205+
let protocol_file = std::io::BufWriter::new(protocol_file);
206+
serde_json::to_writer(protocol_file, &protocol)
207+
.context("failed to write judge log to file")?;
208+
self.put_outcome(protocol.score, protocol.status, protocol.kind)
209+
.await;
210+
Ok(())
211+
}
212+
213+
async fn run_tests(&mut self, req: &LoweredJudgeRequest) -> anyhow::Result<()> {
214+
let mut test_results = vec![];
215+
216+
let mut valuer = valuer::Valuer::new(req).context("failed to init valuer")?;
217+
valuer
218+
.write_problem_data(req)
219+
.await
220+
.context("failed to send problem data")?;
221+
loop {
222+
match valuer.poll().await? {
223+
ValuerResponse::Test { test_id: tid, live } => {
224+
if live {
225+
self.events_tx.send(Event::LiveTest(tid.get())).await;
226+
}
227+
let tid_u32: u32 = tid.into();
228+
let test = &req.problem.tests[(tid_u32 - 1u32) as usize];
229+
let exec_request = ExecRequest {
230+
test,
231+
test_id: tid.into(),
232+
};
233+
234+
let judge_response = exec_test::exec(&req, exec_request, self)
235+
.with_context(|| format!("failed to judge solution on test {}", tid))?;
236+
test_results.push((tid, judge_response.clone()));
237+
valuer
238+
.notify_test_done(judging_apis::valuer_proto::TestDoneNotification {
239+
test_id: tid,
240+
test_status: judge_response.status,
241+
})
242+
.await
243+
.with_context(|| {
244+
format!("failed to notify valuer that test {} is done", tid)
245+
})?;
246+
}
247+
ValuerResponse::Finish => {
248+
break;
249+
}
250+
ValuerResponse::LiveScore { score } => {
251+
self.events_tx.send(Event::LiveScore(score)).await;
252+
}
253+
ValuerResponse::JudgeLog(judge_log) => {
254+
let converted_judge_log = self
255+
.process_judge_log(&judge_log, req, &test_results)
256+
.context("failed to convert valuer judge log to invoker judge log")?;
257+
self.put_protocol(req, converted_judge_log)
258+
.await
259+
.context("failed to save protocol")?;
260+
}
261+
}
262+
}
263+
264+
Ok(())
265+
}
266+
267+
/// Creates `InputSource` that can be further sent to invoker
268+
async fn intern(&self, data: &[u8]) -> anyhow::Result<judging_apis::invoke::InputSource> {
269+
// TODO: optimize
270+
Ok(judging_apis::invoke::InputSource::Inline {
271+
data: data.to_vec(),
272+
})
273+
}
274+
}

src/judge/src/request_handler/compiler.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::request_handler::{invoke_util, LoweredJudgeRequest};
1+
use crate::request_handler::LoweredJudgeRequest;
22
use anyhow::Context;
33
use judging_apis::{status_codes, Status, StatusKind};
44
use std::fs;
@@ -11,14 +11,17 @@ pub(crate) enum BuildOutcome {
1111
/// Compiler turns SubmissionInfo into Artifact
1212
pub(crate) struct Compiler<'a> {
1313
pub(crate) req: &'a LoweredJudgeRequest,
14-
pub(crate) minion: &'a dyn minion::erased::Backend,
15-
pub(crate) config: &'a crate::config::JudgeConfig,
14+
// pub(crate) config: &'a crate::config::JudgeConfig,
1615
}
1716

1817
impl<'a> Compiler<'a> {
1918
pub(crate) fn compile(&self) -> anyhow::Result<BuildOutcome> {
20-
let sandbox = invoke_util::create_sandbox(self.req, None, self.minion, self.config)
21-
.context("failed to create sandbox")?;
19+
let mut graph = judging_apis::invoke::InvokeRequest {
20+
inputs: vec![],
21+
outputs: vec![],
22+
steps: vec![],
23+
};
24+
2225
let step_dir = self.req.step_dir(None);
2326
fs::copy(
2427
&self.req.run_source,

src/judge/src/request_handler/exec_test.rs

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
mod checker_proto;
22

3-
use super::{invoke_util, JudgeContext, LoweredJudgeRequest};
3+
use super::{JudgeContext, LoweredJudgeRequest};
44
use anyhow::Context;
55
use judging_apis::{
6-
invoke::{Command, Action, Step, Stdio, FileId},
6+
invoke::{Action, Command, FileId, Input, InputSource, Stdio, Step},
77
status_codes, Status, StatusKind,
88
};
99
use std::{fs, io::Write, path::PathBuf};
@@ -51,17 +51,47 @@ fn map_checker_outcome_to_status(out: checker_proto::Output) -> Status {
5151
}
5252

5353
/// Runs Artifact on one test and produces output
54-
pub fn exec(judge_req: &LoweredJudgeRequest, ctx: &JudgeContext) -> anyhow::Result<ExecOutcome> {
54+
pub async fn exec(
55+
judge_req: &LoweredJudgeRequest,
56+
exec_req: ExecRequest<'_>,
57+
cx: &JudgeContext,
58+
) -> anyhow::Result<ExecOutcome> {
5559
let mut invoke_request = judging_apis::invoke::InvokeRequest {
5660
steps: vec![],
5761
inputs: vec![],
5862
outputs: vec![],
5963
};
64+
let input_file = judge_req.resolve_asset(&exec_req.test.path);
65+
let test_data = std::fs::read(input_file).context("failed to read test")?;
6066
const EXEC_SOLUTION_GENERATION: u32 = 0;
61-
const EXEC_SOLUTION_OUTPUT_FILE: FileId = FileId(0);
62-
const EXEC_SOLUTION_ERROR_FILE: FileId = FileId(1);
63-
67+
const TEST_DATA_INPUT_FILE: &str = "test-data";
68+
const EXEC_SOLUTION_OUTPUT_FILE: &str = "solution-output";
69+
const EXEC_SOLUTION_ERROR_FILE: &str = "solution-error";
70+
6471
const EXEC_CHECKER_GENERATION: u32 = 1;
72+
// create an input with the test date
73+
{
74+
let test_data_input = Input {
75+
id: FileId(TEST_DATA_INPUT_FILE.to_string()),
76+
source: cx.intern(&test_data).await?,
77+
};
78+
invoke_request.inputs.push(test_data_input);
79+
}
80+
// prepare files for stdout & stderr
81+
{
82+
invoke_request.steps.push(Step {
83+
generation: EXEC_SOLUTION_GENERATION,
84+
action: Action::CreateFile {
85+
id: FileId(EXEC_SOLUTION_OUTPUT_FILE.to_string()),
86+
},
87+
});
88+
invoke_request.steps.push(Step {
89+
generation: EXEC_SOLUTION_GENERATION,
90+
action: Action::CreateFile {
91+
id: FileId(EXEC_SOLUTION_ERROR_FILE.to_string()),
92+
},
93+
})
94+
}
6595
// produce a step for executing solution
6696
{
6797
let exec_solution_step = Step {
@@ -71,13 +101,14 @@ pub fn exec(judge_req: &LoweredJudgeRequest, ctx: &JudgeContext) -> anyhow::Resu
71101
env: judge_req.execute_command.env.clone(),
72102
cwd: judge_req.execute_command.cwd.clone(),
73103
stdio: Stdio {
74-
75-
}
104+
stdin: FileId(TEST_DATA_INPUT_FILE.to_string()),
105+
stdout: FileId(EXEC_SOLUTION_OUTPUT_FILE.to_string()),
106+
stderr: FileId(EXEC_SOLUTION_ERROR_FILE.to_string()),
107+
},
76108
}),
77109
};
110+
invoke_request.steps.push(exec_solution_step);
78111
}
79-
let input_file = self.req.resolve_asset(&self.exec.test.path);
80-
let test_data = std::fs::read(input_file).context("failed to read test")?;
81112
let run_outcome = self.run_solution(&test_data, self.exec.test_id)?;
82113
let sol_file_path = match run_outcome.var {
83114
RunOutcomeVar::Success { out_data_path } => out_data_path,

0 commit comments

Comments
 (0)