diff --git a/dokku-setup.yml b/dokku-setup.yml index 57818e3..bf2b8a1 100644 --- a/dokku-setup.yml +++ b/dokku-setup.yml @@ -55,8 +55,9 @@ default: "hermesAPITokenUnsecure" - name: ALLOWED_ORIGIN - prompt: "Enter domain you need CORS (format: https://yourdomain.com)" + prompt: "Enter domain you need CORS" private: no + default: "https://execute.tyxc.org" - name: RUST_LOG prompt: "Enter you preferred log settings (error, warn, info [default], debug, trace, full, off)" diff --git a/src/api/handlers.rs b/src/api/handlers.rs index 2f1f601..f0ef247 100644 --- a/src/api/handlers.rs +++ b/src/api/handlers.rs @@ -70,11 +70,12 @@ async fn run_execution(state: Arc, ) -> Result { tokio::task::spawn_blocking(move || { - let manager = &state.box_manager; - let isolate_box = manager.acquire(); - let run_result = execute_code(&isolate_box, req, auth_token); - manager.release(isolate_box); + let compiler_pool = Arc::clone(&state.compiler_pool); + let executor_pool = Arc::clone(&state.executor_pool); + + let run_result = execute_code(&compiler_pool, &executor_pool, req, auth_token); + run_result }).await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? diff --git a/src/core/exe.rs b/src/core/exe.rs index 412a0b7..596023f 100644 --- a/src/core/exe.rs +++ b/src/core/exe.rs @@ -1,12 +1,16 @@ use tracing::instrument; use crate::languages::get_handler; use crate::core::runner::safe_execute; -use crate::core::workers::IsolateBox; +use crate::core::workers::{BoxManager, EphemeralBox, PersistentBox}; use crate::config::models::{ReqMulti, Resp}; use crate::config::utils::get_lang_config; #[instrument(level = "debug", skip(passed_token))] -pub fn execute_code(isolate_box: &IsolateBox, req: ReqMulti, passed_token: Option) -> Result{ +pub fn execute_code(compiler_pool: &BoxManager, + executor_pool: &BoxManager, + req: ReqMulti, + passed_token: Option) -> Result{ + let lang_config = match get_lang_config(&req.language) { Ok(config) => config, Err(e) => return Ok( @@ -37,9 +41,11 @@ pub fn execute_code(isolate_box: &IsolateBox, req: ReqMulti, passed_token: Optio } } + let executor_box = executor_pool.acquire(); + let handler = get_handler(&req.language).map_err(|e| e)?; - let work_dir = &isolate_box.path; + let work_dir = &executor_box.path; let program = match handler.prepare(work_dir, &req) { Ok(p) => p, @@ -55,7 +61,7 @@ pub fn execute_code(isolate_box: &IsolateBox, req: ReqMulti, passed_token: Optio if lang_config.compile { let compile_args = handler.compile_cmd(&program); - let (out, log, code, _) = safe_execute(isolate_box, lang_config, &compile_args)?; + let (out, log, code, _) = safe_execute(&executor_box, lang_config, &compile_args)?; if code != 0 { return Ok(Resp { output: out, @@ -69,7 +75,9 @@ pub fn execute_code(isolate_box: &IsolateBox, req: ReqMulti, passed_token: Optio let run_args = handler.run_cmd(&program); let (output, std_log, code, time_ms) = - safe_execute(&isolate_box, &lang_config, &run_args)?; + safe_execute(&executor_box, &lang_config, &run_args)?; + + executor_pool.release(executor_box); Ok(Resp{output, std_log, code, time_ms}) } diff --git a/src/core/runner.rs b/src/core/runner.rs index 2030119..243a807 100644 --- a/src/core/runner.rs +++ b/src/core/runner.rs @@ -3,12 +3,12 @@ use std::process::Command; use tracing::instrument; use tokio::time::Instant; -use crate::core::workers::IsolateBox; +use crate::core::workers::{Sandbox}; use crate::config::models::LangConfig; use crate::config::constants::IS_DEBUG; -#[instrument(level = "debug")] -pub fn safe_execute(isolate_box: &IsolateBox, +#[instrument(level = "debug", skip(config))] +pub fn safe_execute(sand_box: &impl Sandbox, config: &LangConfig, run_args: &[String] ) -> Result<(String, String, i32, u128), String> { @@ -17,7 +17,7 @@ pub fn safe_execute(isolate_box: &IsolateBox, let mut cmd = Command::new("isolate"); // Box config - cmd.arg("--box-id").arg(&isolate_box.id.to_string()); + cmd.arg("--box-id").arg(&sand_box.id().to_string()); cmd.arg("--cg"); cmd.args(&config.isolate_args); @@ -43,7 +43,7 @@ pub fn safe_execute(isolate_box: &IsolateBox, // Metafile for job // TODO: read exit code - let meta_path = format!("/tmp/isolate_{}.meta", isolate_box.id); + let meta_path = format!("/tmp/isolate_{}.meta", sand_box.id()); cmd.arg(format!("--meta={}", &meta_path)); cmd.arg("--run"); diff --git a/src/core/workers.rs b/src/core/workers.rs index e4b292d..d8c4ea2 100644 --- a/src/core/workers.rs +++ b/src/core/workers.rs @@ -1,30 +1,45 @@ +use std::fmt::Debug; use std::fs; use std::io::Result; +use std::ops::Range; use std::path::PathBuf; use std::process::Command; use crossbeam_channel::{bounded, Receiver, Sender}; use tracing::info; +pub trait Sandbox: Debug{ + fn id(&self) -> usize; + fn path(&self) -> PathBuf; + fn cleanup(&self) -> Result<()>; +} + + #[derive(Debug)] -pub struct IsolateBox { +pub struct EphemeralBox { pub id: usize, pub path: PathBuf, } -pub struct BoxManager { - box_pool: Receiver, - sender: Sender, -} - -impl IsolateBox { - pub fn new(id: usize) -> IsolateBox { +impl EphemeralBox { + pub fn new(id: usize) -> Self { Self { id, path: PathBuf::from(format!("/var/lib/isolate/{}/box", id)), } } - pub fn cleanup(&self) -> Result<()>{ +} + +impl Sandbox for EphemeralBox { + fn id(&self) -> usize { + self.id + } + + fn path(&self) -> PathBuf { + self.path.clone() + } + + fn cleanup(&self) -> Result<()>{ // Remove all temp files without unmounting. if self.path.exists() { for entry in fs::read_dir(&self.path)? { @@ -38,34 +53,78 @@ impl IsolateBox { } } // Kill all process for that user. + let target_user = format!("isolate-{}", self.id); let _ = Command::new("pkill") - .args(["-9", "-u", format!("isolate-{}", self.id.to_string()).as_str()]) + .args(["-9", "-u"]) + .arg(&target_user) .output(); Ok(()) } } +#[derive(Debug)] +pub struct PersistentBox { + pub id: usize, + pub path: PathBuf, +} + +impl PersistentBox { + pub fn new(id: usize) -> Self { + Self { + id, + path: PathBuf::from(format!("/var/lib/isolate/{}/box", id)), + } + } +} + +impl Sandbox for PersistentBox { + fn id(&self) -> usize { + self.id + } + + fn path(&self) -> PathBuf { + self.path.clone() + } + + fn cleanup(&self) -> Result<()>{ + Ok(()) + } +} + +#[derive(Debug)] +pub struct BoxManager { + box_pool: Receiver, + sender: Sender, +} + + + +impl BoxManager { + pub fn new (id_range: Range, init_box: F) -> Self + where + F: Fn(usize) -> T, + { + + let count = id_range.len(); -impl BoxManager { - pub fn new(count: usize) -> Self { - let (tx, rx) = bounded(count as usize); + let (tx, rx) = bounded(count); - for i in 0..count{ + for id in id_range { // Clean up previous boxes on restart. let _ = Command::new("isolate") - .args(["--box-id", &i.to_string(), "--cg", "--cleanup"]) + .args(["--box-id", &id.to_string(), "--cg", "--cleanup"]) .status(); //TODO: Add warning for control group per kernel version let status = Command::new("isolate") - .args(["--box-id", &i.to_string(), "--cg", "--init"]).status() + .args(["--box-id", &id.to_string(), "--cg", "--init"]).status() .expect("Failed to execute command. Verify isolate installation."); if !status.success() { - panic!("{}", format!("Failed to initialize box {}", i.to_string())); + panic!("{}", format!("Failed to initialize box {}", id.to_string())); } - let b = IsolateBox::new(i); + let b = init_box(id); tx.send(b).unwrap(); } @@ -77,14 +136,14 @@ impl BoxManager { } } - pub fn acquire(&self) -> IsolateBox { + pub fn acquire(&self) -> T { self.box_pool.recv().expect("Box pool closed.") } - pub fn release(&self, isolate_box: IsolateBox) { - if let Err(e) = isolate_box.cleanup(){ - eprintln!("Cleanup failed for box: {} with: {}", isolate_box.id, e); + pub fn release(&self, target_box: T) { + if let Err(e) = target_box.cleanup(){ + eprintln!("Cleanup failed for box: {} with: {}", target_box.id(), e); } - self.sender.send(isolate_box).unwrap(); + self.sender.send(target_box).unwrap(); } } diff --git a/src/state.rs b/src/state.rs index eb26f8d..06a0cd1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,15 +1,22 @@ use std::sync::Arc; -use crate::core::workers::BoxManager; +use crate::core::workers::{BoxManager, EphemeralBox, PersistentBox}; pub struct AppState { - pub box_manager: Arc, + pub executor_pool: Arc>, + pub compiler_pool: Arc> } impl AppState { pub fn new(worker_count: usize) -> Self { + + let ten_percent = (worker_count as f64 * 0.1).ceil() as usize; AppState { - box_manager: Arc::new(BoxManager::new(worker_count)) + executor_pool: Arc::new(BoxManager::new(0..worker_count, |id| EphemeralBox::new(id))), + compiler_pool: Arc::new( + BoxManager::new(worker_count..worker_count + ten_percent, |id| PersistentBox::new(id) + ) + ), } } }