eval_stack/engine/
runtime.rs

1use 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}