Skip to content

Commit

Permalink
add adjustments for windows and add js scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander Lyon authored and Alexander Lyon committed Sep 13, 2023
1 parent 93559d4 commit 7acd1ff
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 31 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions cli/internal/ffi/proto/messages.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/turborepo-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ which = { workspace = true }
uds_windows = "1.0.2"
async-io = "1.12.0"

[target.'cfg(target_os = "windows")'.dev-dependencies]
winapi = "0.3.9"

[build-dependencies]
capnpc = "0.17.2"
tonic-build = "0.8.4"
60 changes: 45 additions & 15 deletions crates/turborepo-lib/src/process/child.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
//! them when the manager is closed.

use std::{
io,
sync::{Arc, Mutex},
time::Duration,
};
Expand Down Expand Up @@ -47,6 +48,9 @@ impl ChildState {
pub enum ChildExit {
Finished(Option<i32>),
Killed,
/// The child process was killed by someone else. Note that on
/// windows, it is not possible to distinguish between whether
/// the process exited normally or was killed
KilledExternal,
Failed,
}
Expand Down Expand Up @@ -115,8 +119,10 @@ impl ShutdownStyle {
#[cfg(windows)]
{
debug!("timeout not supported on windows, killing");
child.kill().await?;
Ok(ChildState::Killed)
match child.kill().await {
Ok(_) => ChildState::Exited(ChildExit::Killed),
Err(_) => ChildState::Exited(ChildExit::Failed),
}
}
}
ShutdownStyle::Kill => match child.kill().await {
Expand Down Expand Up @@ -168,8 +174,8 @@ pub enum ChildCommand {
impl Child {
/// Start a child process, returning a handle that can be used to interact
/// with it. The command will be started immediately.
pub fn spawn(mut command: Command, shutdown_style: ShutdownStyle) -> Self {
let group = command.group().spawn().expect("failed to start child");
pub fn spawn(mut command: Command, shutdown_style: ShutdownStyle) -> io::Result<Self> {
let group = command.group().spawn()?;

let gid = group.id();
let mut child = group.into_inner();
Expand Down Expand Up @@ -227,6 +233,7 @@ impl Child {
}
}
status = child.wait() => {
debug!("child process exited normally");
// the child process exited
let child_exit = match status.map(|s| s.code()) {
Ok(Some(c)) => ChildExit::Finished(Some(c)),
Expand All @@ -246,18 +253,18 @@ impl Child {
}
}

debug!("child process exited");
debug!("child process stopped");
});

Self {
Ok(Self {
pid,
gid,
state,
exit_channel: exit_rx,
stdin: Arc::new(Mutex::new(stdin)),
stdout: Arc::new(Mutex::new(stdout)),
stderr: Arc::new(Mutex::new(stderr)),
}
})
}

/// Wait for the `Child` to exit, returning the exit code.
Expand Down Expand Up @@ -357,7 +364,7 @@ mod test {
async fn test_pid() {
let mut cmd = Command::new("node");
cmd.args(["./test/scripts/hello_world.js"]);
let mut child = Child::spawn(cmd, ShutdownStyle::Kill);
let mut child = Child::spawn(cmd, ShutdownStyle::Kill).unwrap();

assert_matches!(child.pid(), Some(_));
child.stop().await;
Expand All @@ -376,7 +383,7 @@ mod test {
cmd
};

let mut child = Child::spawn(cmd, ShutdownStyle::Kill);
let mut child = Child::spawn(cmd, ShutdownStyle::Kill).unwrap();

{
let state = child.state.read().await;
Expand All @@ -398,7 +405,7 @@ mod test {
let mut cmd = Command::new("node");
cmd.args(["./test/scripts/hello_world.js"]);
cmd.stdout(Stdio::piped());
let mut child = Child::spawn(cmd, ShutdownStyle::Kill);
let mut child = Child::spawn(cmd, ShutdownStyle::Kill).unwrap();

tokio::time::sleep(STARTUP_DELAY).await;

Expand Down Expand Up @@ -429,7 +436,7 @@ mod test {
cmd.args(["./test/scripts/stdin_stdout.js"]);
cmd.stdout(Stdio::piped());
cmd.stdin(Stdio::piped());
let mut child = Child::spawn(cmd, ShutdownStyle::Kill);
let mut child = Child::spawn(cmd, ShutdownStyle::Kill).unwrap();

let mut stdout = child.stdout().unwrap();

Expand Down Expand Up @@ -464,7 +471,8 @@ mod test {
cmd
};

let mut child = Child::spawn(cmd, ShutdownStyle::Graceful(Duration::from_millis(500)));
let mut child =
Child::spawn(cmd, ShutdownStyle::Graceful(Duration::from_millis(500))).unwrap();

// give it a moment to register the signal handler
tokio::time::sleep(STARTUP_DELAY).await;
Expand All @@ -486,7 +494,8 @@ mod test {
cmd
};

let mut child = Child::spawn(cmd, ShutdownStyle::Graceful(Duration::from_millis(500)));
let mut child =
Child::spawn(cmd, ShutdownStyle::Graceful(Duration::from_millis(500))).unwrap();

tokio::time::sleep(STARTUP_DELAY).await;

Expand All @@ -512,7 +521,8 @@ mod test {
cmd
};

let mut child = Child::spawn(cmd, ShutdownStyle::Graceful(Duration::from_millis(500)));
let mut child =
Child::spawn(cmd, ShutdownStyle::Graceful(Duration::from_millis(500))).unwrap();

tokio::time::sleep(STARTUP_DELAY).await;

Expand All @@ -522,11 +532,31 @@ mod test {
libc::kill(pid as i32, libc::SIGINT);
}
}
#[cfg(windows)]
if let Some(pid) = child.pid() {
unsafe {
println!("killing");
winapi::um::processthreadsapi::TerminateProcess(
winapi::um::processthreadsapi::OpenProcess(
winapi::um::winnt::PROCESS_TERMINATE,
0,
pid,
),
3,
);
}
}

child.wait().await;

let state = child.state.read().await;

assert_matches!(&*state, ChildState::Exited(ChildExit::KilledExternal));
let expected = if cfg!(unix) {
ChildExit::KilledExternal
} else {
ChildExit::Finished(Some(1))
};

assert_matches!(&*state, ChildState::Exited(expected));
}
}
38 changes: 22 additions & 16 deletions crates/turborepo-lib/src/process/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
mod child;

use std::{
sync::{atomic::AtomicBool, Arc, Mutex},
io,
sync::{Arc, Mutex},
time::Duration,
};

Expand Down Expand Up @@ -51,14 +52,22 @@ impl ProcessManager {
/// The handle of the child can be either waited or stopped by the caller,
/// as well as the entire process manager.
///
/// If spawn returns None,
pub fn spawn(&self, command: child::Command, stop_timeout: Duration) -> Option<child::Child> {
/// If spawn returns None, the process manager is closed and the child
/// process was not spawned. If spawn returns Some(Err), the process
/// manager is open, but the child process failed to spawn.
pub fn spawn(
&self,
command: child::Command,
stop_timeout: Duration,
) -> Option<io::Result<child::Child>> {
let mut lock = self.0.lock().unwrap();
if lock.is_closing {
return None;
}
let child = child::Child::spawn(command, child::ShutdownStyle::Graceful(stop_timeout));
lock.children.push(child.clone());
if let Ok(child) = &child {
lock.children.push(child.clone());
}
Some(child)
}

Expand Down Expand Up @@ -170,7 +179,8 @@ mod test {
let manager = ProcessManager::new();
let mut child = manager
.spawn(get_command(), Duration::from_secs(2))
.expect("running");
.unwrap()
.unwrap();

sleep(Duration::from_millis(100)).await;

Expand All @@ -186,7 +196,8 @@ mod test {
let manager = ProcessManager::new();
let mut child = manager
.spawn(get_command(), Duration::from_secs(2))
.expect("running");
.unwrap()
.unwrap();

sleep(Duration::from_millis(100)).await;

Expand All @@ -211,13 +222,13 @@ mod test {

manager.stop().await;

assert!(manager.children.lock().unwrap().is_empty());
assert!(manager.0.lock().unwrap().children.is_empty());

// idempotent
manager.stop().await;
}

#[test_case("stop", ChildExit::Finished(None))]
#[test_case("stop", if cfg!(windows) {ChildExit::Killed} else {ChildExit::Finished(None)})] // windows doesn't support graceful stop
#[test_case("wait", ChildExit::Finished(Some(0)))]
#[tokio::test]
async fn test_stop_multiple_tasks_shared(strat: &str, expected: ChildExit) {
Expand All @@ -227,11 +238,9 @@ mod test {
for _ in 0..10 {
let manager = manager.clone();
tasks.push(tokio::spawn(async move {
let mut command = super::child::Command::new("sleep");
command.arg("1");

manager
.spawn(command, Duration::from_secs(1))
.spawn(get_command(), Duration::from_secs(1))
.unwrap()
.unwrap()
.wait()
.await
Expand Down Expand Up @@ -259,10 +268,7 @@ mod test {
async fn test_wait_multiple_tasks() {
let manager = ProcessManager::new();

let mut command = super::child::Command::new("sleep");
command.arg("1");

manager.spawn(command, Duration::from_secs(1));
manager.spawn(get_command(), Duration::from_secs(1));

// let the task start
tokio::time::sleep(Duration::from_millis(50)).await;
Expand Down
1 change: 1 addition & 0 deletions crates/turborepo-lib/test/scripts/hello_world.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("hello world");
13 changes: 13 additions & 0 deletions crates/turborepo-lib/test/scripts/sleep_5_ignore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
process.on("SIGINT", () => {
console.log("received SIGINT, ignoring");
});

function delay(time) {
return new Promise((resolve) => setTimeout(resolve, time));
}

async function run() {
await delay(5000);
}

run();
Loading

0 comments on commit 7acd1ff

Please sign in to comment.