Skip to content

Commit

Permalink
feat: 🚧 draft submit endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
Eason0729 committed Nov 23, 2023
1 parent c30cd1c commit af83bc3
Show file tree
Hide file tree
Showing 15 changed files with 285 additions and 201 deletions.
1 change: 1 addition & 0 deletions backend/entity/src/submit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub struct Model {
pub memory: Option<u64>,
#[sea_orm(default_value = 0, indexed)]
pub pass_case: i32,
pub status: u32,
#[sea_orm(default_value = false)]
pub accept: bool,
#[sea_orm(default_value = 0, indexed)]
Expand Down
151 changes: 98 additions & 53 deletions backend/src/controller/submit.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
use std::{borrow::BorrowMut, sync::Arc};
use std::sync::Arc;

use sea_orm::{ActiveModelTrait, ActiveValue, EntityTrait, IntoActiveModel, QueryOrder};
use sea_orm::{ActiveModelTrait, ActiveValue, EntityTrait, QueryOrder};
use thiserror::Error;
use tokio_stream::StreamExt;

use crate::{
grpc::{
backend::SubmitStatus,
judger::{JudgeRequest, TestIo},
backend::{submit_status, JudgeResult as BackendResult, SubmitStatus},
judger::{judge_response, JudgeRequest, JudgeResponse, JudgeResult, JudgerCode, TestIo},
},
init::{config::CONFIG, db::DB},
};

use super::util::{pubsub::PubSub, router::*};
use super::util::{
code::Code,
pubsub::{PubGuard, PubSub},
router::*,
};
use entity::*;

type TonicStream<T> =
Expand Down Expand Up @@ -74,6 +78,28 @@ impl Submit {
}
}

impl From<i32> for SubmitStatus {
fn from(value: i32) -> Self {
SubmitStatus {
task: Some(submit_status::Task::Case(value)),
}
}
}

impl From<JudgeResult> for SubmitStatus {
fn from(value: JudgeResult) -> Self {
SubmitStatus {
task: Some(submit_status::Task::Result(BackendResult {
code: Into::<Code>::into(value.status()) as i32,
accuracy: Some(value.accuracy),
time: Some(value.time),
memory: Some(value.memory),
})),
}
}
}

#[derive(Clone)]
pub struct SubmitController {
router: Arc<Router>,
pubsub: Arc<PubSub<Result<SubmitStatus, tonic::Status>, i32>>,
Expand All @@ -87,7 +113,69 @@ impl SubmitController {
pubsub: Arc::new(PubSub::new()),
})
}
async fn submit(&self, submit: Submit) -> Result<i32, Error> {
async fn stream(
ps_guard: PubGuard<Result<SubmitStatus, tonic::Status>, i32>,
mut stream: tonic::Streaming<JudgeResponse>,
mut model: submit::ActiveModel,
mut scores: Vec<u32>,
) {
let mut result = 0;
let mut running_case = 0;
let mut time = 0;
let mut mem = 0;
let mut status = JudgerCode::Ac;
scores.reverse();
while let Some(res) = stream.next().await {
if res.is_err() {
break;
}
let res = res.unwrap();
if res.task.is_none() {
log::warn!("mismatch proto(judger)");
continue;
}
let task = res.task.unwrap();
match task {
judge_response::Task::Case(case) => {
if ps_guard.send(Ok(case.clone().into())).is_err() {
log::trace!("client disconnected");
}
if case != (running_case + 1) {
log::warn!("mismatch proto(judger)");
}
running_case += 1;
}
judge_response::Task::Result(case) => {
if let Some(score) = scores.pop() {
if ps_guard.send(Ok(case.clone().into())).is_err() {
log::trace!("client disconnected");
}
if case.status() == JudgerCode::Ac {
result += score;
} else {
status = case.status();
mem += case.memory;
time += case.time;
break;
}
} else {
log::warn!("mismatch proto(judger), too many cases");
}
}
}
}
model.committed = ActiveValue::Set(true);
model.score = ActiveValue::Set(result);
model.status = ActiveValue::Set(Into::<Code>::into(status) as u32);
model.pass_case = ActiveValue::Set(running_case);
model.time = ActiveValue::Set(Some(time));
model.memory = ActiveValue::Set(Some(mem));

if let Err(err) = model.update(DB.get().unwrap()).await {
log::warn!("failed to commit the judge result: {}", err);
}
}
pub async fn submit(&self, submit: Submit) -> Result<i32, Error> {
let db = DB.get().unwrap();

let mut conn = self.router.get(&submit.lang).await?;
Expand All @@ -97,7 +185,7 @@ impl SubmitController {
.order_by_asc(test::Column::Score)
.all(db)
.await?;
let (mut problem, testcases) = binding.pop().ok_or(Error::BadArgument("problem id"))?;
let (problem, testcases) = binding.pop().ok_or(Error::BadArgument("problem id"))?;

// create uncommited submit
let submit_model = submit::ActiveModel {
Expand All @@ -113,9 +201,9 @@ impl SubmitController {
.await?;

let submit_id = submit_model.id.as_ref().to_owned();
let mut pubguard = self.pubsub.publish(submit_id);
let tx = self.pubsub.publish(submit_id);

let mut scores = testcases.iter().rev().map(|x| x.score).collect::<Vec<_>>();
let scores = testcases.iter().rev().map(|x| x.score).collect::<Vec<_>>();

let tests = testcases
.into_iter()
Expand All @@ -136,51 +224,8 @@ impl SubmitController {
})
.await?;

tokio::spawn(async move {
let mut state = crate::controller::util::state::State::default();
let mut res = res.into_inner();

while let Some(res) = res.next().await {
match res {
Ok(res) => {
state.parse_state(pubguard.borrow_mut(), res);
}
Err(err) => {
log::warn!("{}", err);
pubguard.send(Err(err)).ok();
break;
}
}
}
let pass = state.pass == scores.len();
let mut score = 0;
while let Some(x) = scores.pop() {
if state.pass == 0 {
break;
}
state.pass -= 1;
score += x;
}

let mut submit_model = submit_model.into_active_model();
submit_model.committed = ActiveValue::Set(true);
submit_model.time = ActiveValue::Set(Some(state.time));
submit_model.memory = ActiveValue::Set(Some(state.mem));
submit_model.score = ActiveValue::Set(score);
tokio::spawn(Self::stream(tx, res.into_inner(), submit_model, scores));

problem.submit_count += 1;
if pass {
problem.accept_count += 1;
}
problem.ac_rate = problem.accept_count as f32 / problem.submit_count as f32;

if let Err(err) = problem.into_active_model().save(db).await {
log::warn!("failed to update problem statistics: {}", err);
}
if let Err(err) = submit_model.save(db).await {
log::warn!("failed to commit the judge result: {}", err);
}
});
Ok(submit_id)
}
async fn follow(&self, submit_id: i32) -> Option<TonicStream<SubmitStatus>> {
Expand Down
2 changes: 1 addition & 1 deletion backend/src/controller/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ impl TokenController {
// }
// return Ok(None);
// }
// #[instrument(skip_all, name = "token_verify")]
#[instrument(skip_all, name = "token_verify")]
pub async fn verify(&self, token: &str) -> Result<Option<(i32, UserPermBytes)>, Error> {
let now = Local::now().naive_local();
let db = DB.get().unwrap();
Expand Down
101 changes: 101 additions & 0 deletions backend/src/controller/util/code.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use crate::grpc::backend::StateCode as BackendCode;
use crate::grpc::judger::JudgerCode;

#[repr(u32)]
pub enum Code {
Accepted = 1,
WrongAnswer = 2,
TimeLimitExceeded = 3,
MemoryLimitExceeded = 4,
RuntimeError = 5,
CompileError = 6,
SystemError = 7,
RestrictedFunction = 8,
Unknown = 9,
OutputLimitExceeded = 10,
}

impl TryFrom<u32> for Code {
type Error = ();

fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
1 => Ok(Code::Accepted),
2 => Ok(Code::WrongAnswer),
3 => Ok(Code::TimeLimitExceeded),
4 => Ok(Code::MemoryLimitExceeded),
5 => Ok(Code::RuntimeError),
6 => Ok(Code::CompileError),
7 => Ok(Code::SystemError),
8 => Ok(Code::RestrictedFunction),
9 => Ok(Code::Unknown),
10 => Ok(Code::OutputLimitExceeded),
_ => Err(()),
}
}
}

impl From<Code> for JudgerCode {
fn from(value: Code) -> Self {
match value {
Code::Accepted => JudgerCode::Ac,
Code::WrongAnswer => JudgerCode::Wa,
Code::TimeLimitExceeded => JudgerCode::Tle,
Code::MemoryLimitExceeded => JudgerCode::Mle,
Code::RuntimeError => JudgerCode::Re,
Code::CompileError => JudgerCode::Ce,
Code::SystemError => JudgerCode::Na,
Code::RestrictedFunction => JudgerCode::Rf,
Code::Unknown => JudgerCode::Na,
Code::OutputLimitExceeded => JudgerCode::Ole,
}
}
}

impl From<JudgerCode> for Code {
fn from(value: JudgerCode) -> Self {
match value {
JudgerCode::Re => Code::RuntimeError,
JudgerCode::Na => Code::Unknown,
JudgerCode::Wa => Code::WrongAnswer,
JudgerCode::Ce => Code::CompileError,
JudgerCode::Ac => Code::Accepted,
JudgerCode::Rf => Code::RestrictedFunction,
JudgerCode::Tle => Code::TimeLimitExceeded,
JudgerCode::Mle => Code::MemoryLimitExceeded,
JudgerCode::Ole => Code::OutputLimitExceeded,
}
}
}

impl From<Code> for BackendCode {
fn from(value: Code) -> Self {
match value {
Code::Accepted => BackendCode::Ac,
Code::WrongAnswer => BackendCode::Wa,
Code::TimeLimitExceeded => BackendCode::Tle,
Code::MemoryLimitExceeded => BackendCode::Mle,
Code::RuntimeError => BackendCode::Re,
Code::CompileError => BackendCode::Ce,
Code::SystemError => BackendCode::Na,
Code::RestrictedFunction => BackendCode::Rf,
Code::Unknown => BackendCode::Na,
Code::OutputLimitExceeded => BackendCode::Ole,
}
}
}
impl From<BackendCode> for Code {
fn from(value: BackendCode) -> Self {
match value {
BackendCode::Ac => Code::Accepted,
BackendCode::Wa => Code::WrongAnswer,
BackendCode::Tle => Code::TimeLimitExceeded,
BackendCode::Mle => Code::MemoryLimitExceeded,
BackendCode::Re => Code::RuntimeError,
BackendCode::Ce => Code::CompileError,
BackendCode::Na => Code::SystemError,
BackendCode::Rf => Code::RestrictedFunction,
BackendCode::Ole => Code::OutputLimitExceeded,
}
}
}
2 changes: 1 addition & 1 deletion backend/src/controller/util/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pub mod code;
pub mod pubsub;
pub mod router;
pub mod state;
Loading

0 comments on commit af83bc3

Please sign in to comment.