eval_stack/engine/
runtime.rs1use std::fs::create_dir_all;
2use std::sync::LazyLock;
3use std::time::Duration;
4
5use anyhow::Result;
6use futures::StreamExt;
7use surrealdb::engine::remote::ws::Ws;
8use surrealdb::opt::auth::Root;
9use surrealdb::{engine::remote::ws::Client, Surreal};
10use surrealdb::{Action, Notification};
11use tokio::fs::File;
12use tokio::io::AsyncWriteExt;
13
14use crate::case::run_test_cases;
15use crate::compile::Language;
16use crate::config::JudgeOptions;
17use crate::engine::models::Status;
18use crate::judge::{JudgeResult, JudgeStatus};
19
20use super::models::Submission;
21
22static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init);
23
24const LIVE_QUERY: &str = r#"
25LIVE SELECT *, problem.test_cases.{ input: input.path, output: output.path } AS test_cases
26FROM submission
27WHERE status = "in_queue"
28"#;
29pub async fn listen_for_submissions() -> Result<()> {
30 DB.connect::<Ws>("127.0.0.1:5177").await?;
31 DB.signin(Root {
32 username: "root",
33 password: "root",
34 })
35 .await?;
36 DB.use_ns("main").use_db("acm").await?;
37
38 println!("Listening for submissions...");
39 let mut stream = DB
40 .query(LIVE_QUERY)
41 .await?
42 .stream::<Notification<Submission>>(0)?;
43
44 while let Some(submission) = stream.next().await {
45 tokio::spawn(handle_submission(submission));
46 }
47
48 Ok(())
49}
50
51pub async fn handle_submission(
52 submission: surrealdb::Result<Notification<Submission>>,
53) -> Result<()> {
54 let submission = submission?;
55
56 if !matches!(submission.action, Action::Create) {
57 return Ok(());
58 }
59
60 let submission = submission.data;
61
62 DB.query("UPDATE $submission SET status = $status")
63 .bind(("submission", submission.id.clone()))
64 .bind(("status", Status::Judging))
65 .await?;
66
67 let base_path = std::env::current_dir().unwrap();
68 let workspace = base_path
69 .join("workspaces")
70 .join(submission.id.id.to_string());
71 if !workspace.exists() {
72 create_dir_all(&workspace)?;
73 }
74
75 let source_file_path = workspace.join(match submission.lang {
76 Language::C => "main.c",
77 Language::CPP => "main.cpp",
78 Language::Java => "Main.java",
79 Language::Python => "main.py",
80 Language::Rust => "main.rs",
81 Language::NodeJs => "main.js",
82 Language::Golang => "main.go",
83 });
84 let mut file = File::create(&source_file_path).await?;
85 file.write_all(submission.code.as_bytes()).await?;
86
87 let results = run_test_cases(
88 submission.lang,
89 workspace,
90 source_file_path,
91 JudgeOptions {
92 time_limit: Duration::from_secs(1),
93 memory_limit: 128 * 1024 * 1024,
94 fail_fast: true,
95 no_startup_limits: false,
96 unsafe_mode: true,
97 },
98 submission
99 .test_cases
100 .into_iter()
101 .map(|tc| (tc.input, tc.output))
102 .collect(),
103 true,
104 )
105 .await?;
106
107 let mut result = JudgeResult::default();
108 for res in &results {
109 result.memory_used = result.memory_used.max(res.memory_used);
110 result.time_used = result.time_used.max(res.time_used);
111 if !matches!(res.status, JudgeStatus::Accepted) {
112 result = res.clone();
113 break;
114 };
115 }
116
117 DB.query(
118 "UPDATE $submission SET status = $status, judge_details = $results, judge_result = $result",
119 )
120 .bind(("submission", submission.id.clone()))
121 .bind(("status", Status::Ready))
122 .bind(("results", results))
123 .bind(("result", result))
124 .await?;
125
126 Ok(())
127}