feat: correct non blocking test for process.create

This commit is contained in:
Erica Marigold 2024-06-24 18:25:09 +05:30
parent aa10e88495
commit bd71b5e4b8
No known key found for this signature in database
GPG key ID: 2768CC0C23D245D1
3 changed files with 40 additions and 72 deletions

View file

@ -79,7 +79,7 @@ pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
.with_value("env", env_tab)? .with_value("env", env_tab)?
.with_value("exit", process_exit)? .with_value("exit", process_exit)?
.with_async_function("exec", process_exec)? .with_async_function("exec", process_exec)?
.with_async_function("create", process_spawn)? .with_function("create", process_spawn)?
.build_readonly() .build_readonly()
} }
@ -153,7 +153,7 @@ async fn process_exec(
) -> LuaResult<LuaTable> { ) -> LuaResult<LuaTable> {
let res = lua let res = lua
.spawn(async move { .spawn(async move {
let cmd = spawn_command(program, args, options.clone()).await?; let cmd = spawn_command_with_stdin(program, args, options.clone()).await?;
wait_for_child(cmd, options.stdio.stdout, options.stdio.stderr).await wait_for_child(cmd, options.stdio.stdout, options.stdio.stderr).await
}) })
.await?; .await?;
@ -180,7 +180,7 @@ async fn process_exec(
} }
#[allow(clippy::await_holding_refcell_ref)] #[allow(clippy::await_holding_refcell_ref)]
async fn process_spawn( fn process_spawn(
lua: &Lua, lua: &Lua,
(program, args, options): (String, Option<Vec<String>>, ProcessSpawnOptions), (program, args, options): (String, Option<Vec<String>>, ProcessSpawnOptions),
) -> LuaResult<LuaTable> { ) -> LuaResult<LuaTable> {
@ -189,26 +189,15 @@ async fn process_spawn(
let mut spawn_options = options.clone(); let mut spawn_options = options.clone();
spawn_options.stdio = ProcessSpawnOptionsStdio::default(); spawn_options.stdio = ProcessSpawnOptionsStdio::default();
let (stdin_tx, stdin_rx) = tokio::sync::oneshot::channel();
let (stdout_tx, stdout_rx) = tokio::sync::oneshot::channel();
let (stderr_tx, stderr_rx) = tokio::sync::oneshot::channel();
let (code_tx, code_rx) = tokio::sync::broadcast::channel(4); let (code_tx, code_rx) = tokio::sync::broadcast::channel(4);
let code_rx_rc = Rc::new(RefCell::new(code_rx)); let code_rx_rc = Rc::new(RefCell::new(code_rx));
tokio::spawn(async move { let mut child = spawn_command(program, args, spawn_options)?;
let mut child = spawn_command(program, args, spawn_options) let stdin = child.stdin.take().unwrap();
.await let stdout = child.stdout.take().unwrap();
.expect("Could not spawn child process"); let stderr = child.stderr.take().unwrap();
stdin_tx
.send(child.stdin.take())
.expect("Stdin receiver was unexpectedly dropped");
stdout_tx
.send(child.stdout.take())
.expect("Stdout receiver was unexpectedly dropped");
stderr_tx
.send(child.stderr.take())
.expect("Stderr receiver was unexpectedly dropped");
tokio::spawn(async move {
let res = child let res = child
.wait_with_output() .wait_with_output()
.await .await
@ -225,33 +214,9 @@ async fn process_spawn(
}); });
TableBuilder::new(lua)? TableBuilder::new(lua)?
.with_value( .with_value("stdout", ChildProcessReader(stdout))?
"stdout", .with_value("stderr", ChildProcessReader(stderr))?
ChildProcessReader( .with_value("stdin", ChildProcessWriter(stdin))?
stdout_rx
.await
.expect("Stdout sender unexpectedly dropped")
.unwrap(),
),
)?
.with_value(
"stderr",
ChildProcessReader(
stderr_rx
.await
.expect("Stderr sender unexpectedly dropped")
.unwrap(),
),
)?
.with_value(
"stdin",
ChildProcessWriter(
stdin_rx
.await
.expect("Stdin sender unexpectedly dropped")
.unwrap(),
),
)?
.with_async_function("status", move |lua, ()| { .with_async_function("status", move |lua, ()| {
let code_rx_rc_clone = Rc::clone(&code_rx_rc); let code_rx_rc_clone = Rc::clone(&code_rx_rc);
async move { async move {
@ -270,21 +235,14 @@ async fn process_spawn(
.build_readonly() .build_readonly()
} }
async fn spawn_command( async fn spawn_command_with_stdin(
program: String, program: String,
args: Option<Vec<String>>, args: Option<Vec<String>>,
mut options: ProcessSpawnOptions, mut options: ProcessSpawnOptions,
) -> LuaResult<Child> { ) -> LuaResult<Child> {
let stdout = options.stdio.stdout;
let stderr = options.stdio.stderr;
let stdin = options.stdio.stdin.take(); let stdin = options.stdio.stdin.take();
let mut child = options let mut child = spawn_command(program, args, options)?;
.into_command(program, args)
.stdin(Stdio::piped())
.stdout(stdout.as_stdio())
.stderr(stderr.as_stdio())
.spawn()?;
if let Some(stdin) = stdin { if let Some(stdin) = stdin {
let mut child_stdin = child.stdin.take().unwrap(); let mut child_stdin = child.stdin.take().unwrap();
@ -293,3 +251,21 @@ async fn spawn_command(
Ok(child) Ok(child)
} }
fn spawn_command(
program: String,
args: Option<Vec<String>>,
options: ProcessSpawnOptions,
) -> LuaResult<Child> {
let stdout = options.stdio.stdout;
let stderr = options.stdio.stderr;
let child = options
.into_command(program, args)
.stdin(Stdio::piped())
.stdout(stdout.as_stdio())
.stderr(stderr.as_stdio())
.spawn()?;
Ok(child)
}

View file

@ -1,18 +1,13 @@
local process = require("@lune/process") local process = require("@lune/process")
-- Spawning a child process should not block the main thread -- Spawning a child process should not block the thread
local SAMPLES = 400 local childThread = coroutine.create(process.create)
for _ = 1, SAMPLES do local ok, err = coroutine.resume(childThread, "echo", { "hello, world" })
local start = os.time() assert(ok, err)
local child = process.create("echo", { "hello, world" })
assert(child ~= nil, "Failed to spawn child process") assert(
coroutine.status(childThread) == "dead",
local delta = os.time() - start "Child process should not block the thread it is running on"
assert( )
delta <= 1,
`Spawning a child process should not block the main thread, process.spawn took {delta}s to return when it should return immediately`
)
end

View file

@ -15,7 +15,4 @@ local echoChild = if process.os == "windows"
then process.create("/c", { "echo", msg, "1>&2" }, { shell = "cmd" }) then process.create("/c", { "echo", msg, "1>&2" }, { shell = "cmd" })
else process.create("echo", { msg, ">>/dev/stderr" }, { shell = true }) else process.create("echo", { msg, ">>/dev/stderr" }, { shell = true })
assert( assert(msg == echoChild.stderr:read(#msg), "Failed to read from stderr of child process")
msg == echoChild.stderr:read(#msg),
"Failed to read from stderr of child process"
)