Skip to content

Commit 61f8369

Browse files
committed
Added shutdown hook so we stop idle docker containers when stopping
the playground Also containers are now stopped in parallel
1 parent 7376910 commit 61f8369

File tree

4 files changed

+91
-25
lines changed

4 files changed

+91
-25
lines changed

ui/Cargo.lock

+30
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ router = "0.6.0"
3232
openssl-probe = "0.1.2"
3333
dotenv = "0.13.0"
3434
snafu = "0.2.0"
35+
ctrlc = { version = "3.1.1", features = ["termination"] }
3536

3637
[dependencies.playground-middleware]
3738
git = "https://github.com/integer32llc/playground-middleware"

ui/src/main.rs

+17-7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use std::{
2222
convert::{TryFrom, TryInto},
2323
env,
2424
path::PathBuf,
25+
process,
2526
sync::{Arc, Mutex},
2627
time::{Duration, Instant},
2728
};
@@ -43,6 +44,13 @@ const ONE_YEAR_IN_SECONDS: u64 = 60 * 60 * 24 * 365;
4344

4445
const SANDBOX_CACHE_TIME_TO_LIVE_IN_SECONDS: u64 = ONE_HOUR_IN_SECONDS as u64;
4546

47+
fn set_graceful_shutdown_hook(containers: Arc<Mutex<DockerContainers>>) {
48+
ctrlc::set_handler(move || {
49+
containers.lock().unwrap().terminate();
50+
process::exit(0);
51+
}).expect("Error setting Ctrl-C handler");
52+
}
53+
4654
fn main() {
4755
// Dotenv may be unable to load environment variables, but that's ok in production
4856
let _ = dotenv::dotenv();
@@ -58,7 +66,9 @@ fn main() {
5866
let cors_enabled = env::var_os("PLAYGROUND_CORS_ENABLED").is_some();
5967
let docker_containers_pool_size = env::var("DOCKER_CONTAINER_POOL_SIZE").ok().and_then(|v| v.parse().ok()).unwrap_or(DEFAULT_DOCKER_CONTAINER_POOL_SIZE);
6068

61-
let containers = Arc::new(DockerContainers::new(docker_containers_pool_size));
69+
let containers = Arc::new(Mutex::new(DockerContainers::new(docker_containers_pool_size)));
70+
71+
set_graceful_shutdown_hook(containers.clone());
6272

6373
let files = Staticfile::new(&root).expect("Unable to open root directory");
6474
let mut files = Chain::new(files);
@@ -142,21 +152,21 @@ impl iron::typemap::Key for GhToken {
142152
type Value = Self;
143153
}
144154

145-
fn compile(req: &mut Request<'_, '_>, containers: &DockerContainers) -> IronResult<Response> {
155+
fn compile(req: &mut Request<'_, '_>, containers: &Mutex<DockerContainers>) -> IronResult<Response> {
146156
with_sandbox(req, |sandbox, req: CompileRequest| {
147157
let req: sandbox::CompileRequest = req.try_into()?;
148-
let container = containers.pop(req.channel).unwrap();
158+
let container = containers.lock().unwrap().pop(req.channel).unwrap();
149159
sandbox
150160
.compile(&req, &container)
151161
.map(CompileResponse::from)
152162
.eager_context(Compilation)
153163
})
154164
}
155165

156-
fn handle(req: &mut Request<'_, '_>, containers: &DockerContainers) -> IronResult<Response> {
166+
fn handle(req: &mut Request<'_, '_>, containers: &Mutex<DockerContainers>) -> IronResult<Response> {
157167
with_sandbox(req, |sandbox, req: ExecuteRequest| {
158168
let req: sandbox::ExecuteRequest = req.try_into()?;
159-
let container = containers.pop(req.channel).unwrap();
169+
let container = containers.lock().unwrap().pop(req.channel).unwrap();
160170
sandbox
161171
.execute(&req, &container)
162172
.map(ExecuteResponse::from)
@@ -271,10 +281,10 @@ fn meta_gist_get(req: &mut Request<'_, '_>) -> IronResult<Response> {
271281

272282
// This is a backwards compatibilty shim. The Rust homepage and the
273283
// documentation use this to run code in place.
274-
fn evaluate(req: &mut Request<'_, '_>, containers: &DockerContainers) -> IronResult<Response> {
284+
fn evaluate(req: &mut Request<'_, '_>, containers: &Mutex<DockerContainers>) -> IronResult<Response> {
275285
with_sandbox(req, |sandbox, req: EvaluateRequest| {
276286
let req = req.try_into()?;
277-
let container = containers.pop(Channel::Stable).unwrap();
287+
let container = containers.lock().unwrap().pop(Channel::Stable).unwrap();
278288
sandbox
279289
.execute(&req, &container)
280290
.map(EvaluateResponse::from)

ui/src/sandbox.rs

+43-18
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use std::{
1212
sync::{Mutex, Arc},
1313
sync::mpsc::{self, Receiver, RecvError},
1414
time::Duration,
15-
thread
15+
thread::{self, JoinHandle}
1616
};
1717
use tempdir::TempDir;
1818

@@ -396,17 +396,33 @@ pub struct DockerContainer {
396396
_temp_dir: TempDir, //we need to keep this, or it's dropped (= deleted)
397397
pub src_dir: PathBuf,
398398
pub output_dir: PathBuf,
399+
terminated: Arc<Mutex<bool>>
399400
}
400401

401402
impl DockerContainer {
402403

403-
pub fn terminate(&self) {
404-
let mut cmd = Command::new("docker");
405-
cmd
406-
.arg("exec")
407-
.arg(&self.id)
408-
.args(&["pkill", "sleep"])
409-
.status();
404+
pub fn terminate(&mut self) -> JoinHandle<()> {
405+
let mut terminated = self.terminated.lock().unwrap();
406+
*terminated = true;
407+
408+
let container_id = self.id.clone();
409+
thread::spawn(move || {
410+
let mut cmd = Command::new("docker");
411+
cmd
412+
.arg("exec")
413+
.arg(&container_id)
414+
.args(&["pkill", "sleep"])
415+
.status()
416+
.expect(&format!("Unable to kill container with id {}", container_id));
417+
})
418+
}
419+
420+
// this was initially used to implement the Drop trait, keeping it for future reference
421+
#[allow(dead_code)]
422+
fn is_terminated(&self) -> bool {
423+
let terminated = &self.terminated.clone();
424+
let terminated = terminated.lock().unwrap();
425+
*terminated
410426
}
411427

412428
}
@@ -431,7 +447,7 @@ impl DockerContainers {
431447
thread::spawn(move || {
432448
let mut active = true;
433449
while active {
434-
sender.send(DockerContainers::start_container(channel)).unwrap_or(());
450+
sender.send(Self::start_container(channel)).unwrap_or(());
435451
active = *active_mutex.lock().unwrap();
436452
}
437453
});
@@ -474,7 +490,7 @@ impl DockerContainers {
474490
Ok(child) => {
475491
let id = Self::container_id_from_stdout(child.stdout.unwrap());
476492

477-
DockerContainer { id, _temp_dir: temp_dir, src_dir, output_dir }
493+
DockerContainer { id, _temp_dir: temp_dir, src_dir, output_dir, terminated: Arc::new(Mutex::new(false)) }
478494
}
479495
Err(err) => panic!("Error starting container {}: {}", channel.container_name(), err)
480496
}
@@ -483,19 +499,28 @@ impl DockerContainers {
483499
pub fn pop(&self, channel: Channel) -> Result<DockerContainer, RecvError> {
484500
self.receivers.get(&channel).unwrap().lock().unwrap().recv()
485501
}
486-
}
487-
488-
impl Drop for DockerContainers {
489502

490-
fn drop(&mut self) {
503+
pub fn terminate(&self) {
504+
log::info!("Shutting down docker containers pool...");
491505
let mut active = self.active.lock().unwrap();
492506
*active = false;
493507

508+
let mut terminate_handles = Vec::new();
494509
for (_, receiver) in self.receivers.iter() {
495-
while let Ok(container) = receiver.lock().unwrap().recv_timeout(Duration::new(1, 0)) {
496-
container.terminate();
510+
while let Ok(mut container) = receiver.lock().unwrap().recv_timeout(Duration::new(1, 0)) {
511+
terminate_handles.push(container.terminate());
497512
}
498513
}
514+
for handle in terminate_handles.into_iter() {
515+
handle.join().expect("error when joining");
516+
}
517+
}
518+
}
519+
520+
impl Drop for DockerContainers {
521+
522+
fn drop(&mut self) {
523+
self.terminate();
499524
}
500525

501526
}
@@ -1447,8 +1472,8 @@ mod test {
14471472
let containers = DockerContainers::new(0);
14481473
thread::sleep(Duration::new(2, 0));
14491474

1450-
let container = containers.pop(Channel::Stable).unwrap();
1475+
let mut container = containers.pop(Channel::Stable).unwrap();
14511476
assert_eq!(false, container.id.is_empty());
1452-
container.terminate();
1477+
container.terminate().join().expect("error when joining");
14531478
}
14541479
}

0 commit comments

Comments
 (0)