Skip to content

Commit 644c0f9

Browse files
committedFeb 15, 2019
Added shutdown hook so we stop idle docker containers when stopping
the playground Also containers are now stopped in parallel
1 parent 5291b24 commit 644c0f9

File tree

4 files changed

+85
-24
lines changed

4 files changed

+85
-24
lines changed
 

‎ui/Cargo.lock

+24
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
@@ -31,6 +31,7 @@ router = "0.6.0"
3131
openssl-probe = "0.1.2"
3232
dotenv = "0.13.0"
3333
snafu = { version = "0.1.2", features = ["unstable_display_attribute"] }
34+
ctrlc = { version = "3.1.1", features = ["termination"] }
3435

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

‎ui/src/main.rs

+17-7
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use std::any::Any;
3131
use std::convert::{TryFrom, TryInto};
3232
use std::env;
3333
use std::path::PathBuf;
34+
use std::process;
3435
use std::sync::{Arc, Mutex};
3536
use std::time::{Duration, Instant};
3637

@@ -66,6 +67,13 @@ const ONE_YEAR_IN_SECONDS: u64 = 60 * 60 * 24 * 365;
6667

6768
const SANDBOX_CACHE_TIME_TO_LIVE_IN_SECONDS: u64 = ONE_HOUR_IN_SECONDS as u64;
6869

70+
fn set_graceful_shutdown_hook(containers: Arc<Mutex<DockerContainers>>) {
71+
ctrlc::set_handler(move || {
72+
containers.lock().unwrap().terminate();
73+
process::exit(0);
74+
}).expect("Error setting Ctrl-C handler");
75+
}
76+
6977
fn main() {
7078
// Dotenv may be unable to load environment variables, but that's ok in production
7179
let _ = dotenv::dotenv();
@@ -81,7 +89,9 @@ fn main() {
8189
let cors_enabled = env::var_os("PLAYGROUND_CORS_ENABLED").is_some();
8290
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);
8391

84-
let containers = Arc::new(DockerContainers::new(docker_containers_pool_size));
92+
let containers = Arc::new(Mutex::new(DockerContainers::new(docker_containers_pool_size)));
93+
94+
set_graceful_shutdown_hook(containers.clone());
8595

8696
let files = Staticfile::new(&root).expect("Unable to open root directory");
8797
let mut files = Chain::new(files);
@@ -165,21 +175,21 @@ impl iron::typemap::Key for GhToken {
165175
type Value = Self;
166176
}
167177

168-
fn compile(req: &mut Request, containers: &DockerContainers) -> IronResult<Response> {
178+
fn compile(req: &mut Request, containers: &Mutex<DockerContainers>) -> IronResult<Response> {
169179
with_sandbox(req, |sandbox, req: CompileRequest| {
170180
let req: sandbox::CompileRequest = try!(req.try_into());
171-
let container = containers.pop(req.channel).unwrap();
181+
let container = containers.lock().unwrap().pop(req.channel).unwrap();
172182
sandbox
173183
.compile(&req, &container)
174184
.map(CompileResponse::from)
175185
.eager_context(Compilation)
176186
})
177187
}
178188

179-
fn handle(req: &mut Request, containers: &DockerContainers) -> IronResult<Response> {
189+
fn handle(req: &mut Request, containers: &Mutex<DockerContainers>) -> IronResult<Response> {
180190
with_sandbox(req, |sandbox, req: ExecuteRequest| {
181191
let req: sandbox::ExecuteRequest = try!(req.try_into());
182-
let container = containers.pop(req.channel).unwrap();
192+
let container = containers.lock().unwrap().pop(req.channel).unwrap();
183193
sandbox
184194
.execute(&req, &container)
185195
.map(ExecuteResponse::from)
@@ -294,10 +304,10 @@ fn meta_gist_get(req: &mut Request) -> IronResult<Response> {
294304

295305
// This is a backwards compatibilty shim. The Rust homepage and the
296306
// documentation use this to run code in place.
297-
fn evaluate(req: &mut Request, containers: &DockerContainers) -> IronResult<Response> {
307+
fn evaluate(req: &mut Request, containers: &Mutex<DockerContainers>) -> IronResult<Response> {
298308
with_sandbox(req, |sandbox, req: EvaluateRequest| {
299309
let req = req.try_into()?;
300-
let container = containers.pop(Channel::Stable).unwrap();
310+
let container = containers.lock().unwrap().pop(Channel::Stable).unwrap();
301311
sandbox
302312
.execute(&req, &container)
303313
.map(EvaluateResponse::from)

‎ui/src/sandbox.rs

+43-17
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use std::thread;
1515

1616
use tempdir::TempDir;
1717
use std::time::Duration;
18+
use std::thread::JoinHandle;
1819

1920
#[derive(Debug, Deserialize)]
2021
struct CrateInformationInner {
@@ -396,17 +397,33 @@ pub struct DockerContainer {
396397
_temp_dir: TempDir, //we need to keep this, or it's dropped (= deleted)
397398
pub src_dir: PathBuf,
398399
pub output_dir: PathBuf,
400+
terminated: Arc<Mutex<bool>>
399401
}
400402

401403
impl DockerContainer {
402404

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

412429
}
@@ -431,7 +448,7 @@ impl DockerContainers {
431448
thread::spawn(move || {
432449
let mut active = true;
433450
while active {
434-
sender.send(DockerContainers::start_container(channel)).unwrap_or(());
451+
sender.send(Self::start_container(channel)).unwrap_or(());
435452
active = *active_mutex.lock().unwrap();
436453
}
437454
});
@@ -474,7 +491,7 @@ impl DockerContainers {
474491
Ok(child) => {
475492
let id = Self::container_id_from_stdout(child.stdout.unwrap());
476493

477-
DockerContainer { id, _temp_dir: temp_dir, src_dir, output_dir }
494+
DockerContainer { id, _temp_dir: temp_dir, src_dir, output_dir, terminated: Arc::new(Mutex::new(false)) }
478495
}
479496
Err(err) => panic!("Error starting container {}: {}", channel.container_name(), err)
480497
}
@@ -483,19 +500,28 @@ impl DockerContainers {
483500
pub fn pop(&self, channel: Channel) -> Result<DockerContainer, RecvError> {
484501
self.receivers.get(&channel).unwrap().lock().unwrap().recv()
485502
}
486-
}
487-
488-
impl Drop for DockerContainers {
489503

490-
fn drop(&mut self) {
504+
pub fn terminate(&self) {
505+
info!("Shutting down docker containers pool...");
491506
let mut active = self.active.lock().unwrap();
492507
*active = false;
493508

509+
let mut terminate_handles = Vec::new();
494510
for (_, receiver) in self.receivers.iter() {
495-
while let Ok(container) = receiver.lock().unwrap().recv_timeout(Duration::new(1, 0)) {
496-
container.terminate();
511+
while let Ok(mut container) = receiver.lock().unwrap().recv_timeout(Duration::new(1, 0)) {
512+
terminate_handles.push(container.terminate());
497513
}
498514
}
515+
for handle in terminate_handles.into_iter() {
516+
handle.join().expect("error when joining");
517+
}
518+
}
519+
}
520+
521+
impl Drop for DockerContainers {
522+
523+
fn drop(&mut self) {
524+
self.terminate();
499525
}
500526

501527
}
@@ -1447,8 +1473,8 @@ mod test {
14471473
let containers = DockerContainers::new(0);
14481474
thread::sleep(Duration::new(2, 0));
14491475

1450-
let container = containers.pop(Channel::Stable).unwrap();
1476+
let mut container = containers.pop(Channel::Stable).unwrap();
14511477
assert_eq!(false, container.id.is_empty());
1452-
container.terminate();
1478+
container.terminate().join().expect("error when joining");
14531479
}
14541480
}

0 commit comments

Comments
 (0)
Please sign in to comment.